Last active
August 29, 2015 14:21
-
-
Save silverweed/6a1abee2ae421fb65b60 to your computer and use it in GitHub Desktop.
ROCK interpreter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Example ROCK program | |
-- Print all primes up to limit. | |
-- Note: the function calling convention is that: | |
-- 1- arguments are passed via the $arg, $arg2, ... variables. | |
-- 2- return values are passed via the $res, $res2, ... variables. | |
-- 3- all those variables must be declared by the callee. | |
-- 4- internally, all functions use labels with names prefixed by | |
-- function_name_* | |
-- 5- internally, all functions call variables with a leading _. | |
-- This means that all variables starting with a _ should be considered | |
-- clobber-able by any function call. | |
-- 6- a function should NOT clobber any variable not starting with | |
-- a _ which isn't an $arg or a $res it required. | |
-- 7- a function should NOT redefine $arg and $res, but only reassign | |
-- them with '=' (this in order to avoid typos etc) | |
-- It's good norm that all required $arg, $res, etc are declared on top | |
-- of the main. | |
limit := 1000 | |
i := 2 | |
p := " is prime | |
-- these are used to communicates with subroutines | |
$arg := 0 | |
$res := 0 | |
-- main loop | |
loop: | |
$arg = i | |
call is_prime | |
jumpif noprint $res is false | |
say i .. p | |
noprint: | |
jumpif end i == limit | |
i = i + 1 | |
jump loop | |
end: | |
die | |
-- is_prime calculates whether the number contained | |
-- in $arg is prime and returns a bool value in $res. | |
is_prime: | |
-- square root of $arg | |
jumpif is_prime_true $arg is 2 | |
_lim := $arg ^ 0.5 | |
_lim = floor _lim | |
_lim = _lim + 1 | |
_i := 2 | |
is_prime_loop: | |
_tmp := $arg % _i | |
jumpif is_prime_false _tmp is 0 | |
jumpif is_prime_true _i == _lim | |
_i = _i + 1 | |
jump is_prime_loop | |
is_prime_false: | |
$res = false | |
jump is_prime_end | |
is_prime_true: | |
$res = true | |
is_prime_end: | |
-- back to the callee | |
jump @$ra |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- ROCK, a really silly language | |
-- by silverweed | |
-- | |
-- A ROCK program is made by a series of lines and labels, like assembly. | |
-- A line may only consist of: | |
-- *) a label: | |
-- mylabel: | |
-- *) a var definition: | |
-- myvar := expr | |
-- *) a var reassign: | |
-- myvar = expr | |
-- *) a jump directive: | |
-- jump label | |
-- OR | |
-- jump #lineno | |
-- OR | |
-- jump @varname (varname must contain the lineno) | |
-- *) a conditional jump directive: | |
-- jumpif label expr | |
-- OR | |
-- jumpif #lineno expr | |
-- OR | |
-- jump @varname expr (varname must contain the lineno) | |
-- *) a call directive (like jump, but fills variable $ra with lineno+1, used | |
-- for 'function' calls) | |
-- call label | |
-- *) a builtin statement: | |
-- say expr | |
-- where expr may be: | |
-- *) number | |
-- *) "string | |
-- *) variable name | |
-- *) operand1 <op> operand2 | |
-- *) ? variable name (checks for existance) | |
-- The default context contains the 'true', 'false', and 'nil' variables | |
-- (which may be reassigned) | |
-- the whole program | |
export _program = {} | |
-- map labels -> lineno | |
export _labels = {} | |
-- symbol table | |
export _context = { | |
'true': true | |
'false': false | |
'nil': nil | |
} | |
-- current lineno | |
export _lineno = 1 | |
-- reads the whole program in a table { lineno: line } | |
read_program = (fname) -> | |
i = 1 | |
for line in io.lines fname | |
line = trim line | |
continue if handle_label_or_comment line, i | |
_program[i] = line | |
i += 1 | |
-- skips lines beginning with -- and assigns labels to line numbers | |
export handle_label_or_comment = (line, lineno) -> | |
return true if #line < 1 or line\sub(0, 2) == '--' | |
tok = split line | |
fst = tok[1] | |
if fst\sub(#fst) == ':' -- label | |
_labels[fst\sub 0, #fst-1] = lineno | |
debug "New label: #{fst\sub 0, #fst-1} @ line #{lineno}" | |
return true | |
return false | |
-- executes line #lineno and returns new lineno to execute | |
execute_line = (lineno, line) -> | |
tok = split line | |
fst = tok[1] | |
if debuglv > 0 | |
io.stderr\write "toks: " | |
for i=1,#tok | |
io.stderr\write "#{tok[i]}, " | |
io.stderr\write "\n" | |
if fst == 'jump' | |
-- unconditional jump (jump label or jump #lineno or jump @varname) | |
fstch = tok[2]\sub 0, 1 | |
if fstch == '#' | |
return tonumber tok[2]\sub 2 | |
if fstch == '@' | |
return _context[tok[2]\sub 2] | |
if _labels[tok[2]] == nil | |
err "Label #{tok[2]} not found!" | |
debug "Jump to label #{tok[2]} @ line #{_labels[tok[2]]}" | |
return _labels[tok[2]] | |
if fst == 'jumpif' | |
-- conditional jump (jumpif label expr or jumpif #lineno expr or jumpif @varname expr) | |
labelno = 0 | |
fstch = tok[2]\sub 0, 1 | |
if fstch == '#' | |
labelno = tonumber tok[2]\sub 2 | |
elseif fstch == '@' | |
labelno = _context[tok[2]\sub 2] | |
else | |
if _labels[tok[2]] == nil | |
err "Label #{tok[2]} not found!" | |
else | |
labelno = _labels[tok[2]] | |
if evaluate slice tok, 3 | |
debug "Condition true on jump to label #{tok[2]} @ line #{_context[tok[2]]}" | |
return labelno | |
else | |
debug "Condition false on jump to label #{tok[2]} @ line #{_context[tok[2]]}" | |
return lineno + 1 | |
if fst == 'call' | |
-- like jump, but also sets variable $ra to (lineno + 1) to provide a return point | |
-- to the subroutine called (this only accepts a label as argument) | |
if _labels[tok[2]] == nil | |
err "Label #{tok[2]} not found!" | |
_context['$ra'] = lineno + 1 | |
return _labels[tok[2]] | |
if fst == 'del' | |
-- delete variable (del a) | |
_context[tok[2]] = nil | |
debug "Deleted variable #{tok[2]} from context" | |
return lineno + 1 | |
if fst == 'die' | |
return -1 | |
if fst == 'say' | |
print evaluate slice tok, 2 | |
return lineno + 1 | |
if tok[2] == '=' | |
-- var (re)assign (a = expr) | |
if _context[fst] == nil | |
err "Variable #{fst} not in context!" | |
_context[fst] = evaluate slice tok, 3 | |
debug "Var reassign: #{fst} = #{_context[fst]}" | |
return lineno + 1 | |
if tok[2] == ':=' | |
--var first assign (a := expr) - may shadow a pre-existing variable | |
_context[fst] = evaluate slice tok, 3 | |
debug "Var define: #{fst} := #{_context[fst]}" | |
return lineno + 1 | |
if _interactive | |
print evaluate tok | |
else | |
err "[aborted] Invalid line: #{line} @ #{lineno}" | |
return lineno + 1 | |
get = (name) -> | |
if tonumber(name) ~= nil | |
return tonumber name | |
return _context[name] -- may be nil | |
-- evaluates a series of tokens, which may be: | |
-- var1 (arith-op) var2 | |
-- number | |
-- "string with spaces too | |
-- variable | |
export evaluate = (toks) -> | |
debug "tok: #{toks[1]}" | |
-- string | |
if toks[1]\sub(0, 1) == '"' | |
toks[1] = toks[1]\sub 2 | |
return join toks | |
-- builtin | |
switch toks[1] | |
when '?' | |
return _context[toks[2]] ~= nil | |
when 'floor' | |
return math.floor get toks[2] | |
-- number or variable | |
if #toks == 1 | |
return get toks[1] | |
-- binary operation | |
op1 = get toks[1] | |
op2 = get toks[3] | |
debug "op1: #{op1}, op2: #{op2}, op: #{toks[2]}" | |
switch toks[2] | |
when '+' | |
return op1 + op2 | |
when '-' | |
return op1 - op2 | |
when '*' | |
return op1 * op2 | |
when '/' | |
return op1 / op2 | |
when '^' | |
return op1 ^ op2 | |
when '..' | |
return op1 .. op2 | |
when '&&' | |
return op1 and op2 | |
when '||' | |
return op1 or op2 | |
when '>', 'gt' | |
return op1 > op2 | |
when '==', 'is' | |
return op1 == op2 | |
when '<', 'lt' | |
return op1 < op2 | |
when '<>', 'ne', 'isnt' | |
return op1 ~= op2 | |
when '>=', 'ge' | |
return op1 >= op2 | |
when '<=', 'le' | |
return op1 <= op2 | |
when '%' | |
return op1 % op2 | |
err "Invalid operation: #{join toks}" | |
-- utils | |
export split = (string, sep = "%s") -> | |
t = {} | |
i = 1 | |
for str in string.gmatch string, "([^#{sep}]+)" do | |
t[i] = str | |
i += 1 | |
return t | |
export slice = (ary, st = 0, en = #ary) -> | |
return {} if en < st | |
cpy = {} | |
j = 1 | |
for i=st,en | |
cpy[j] = ary[i] | |
j += 1 | |
return cpy | |
export join = (ary, st=1) -> | |
str = "" | |
str ..= "#{ary[i]} " for i=st,#ary | |
return str\sub 0, #str-1 | |
export trim = (s) -> | |
s\gsub("^%s*(.-)%s*$", "%1") | |
export err = (msg) -> | |
io.stderr\write "[#{_lineno}] ERR: #{msg}\n" | |
os.exit! | |
export debug = (msg, lv = 1) -> | |
io.stderr\write msg .. "\n" if debuglv >= lv | |
dump_program = -> | |
io.stderr\write "[#{i}] #{_program[i]}\n" for i=1,#_program | |
-- main | |
export debuglv = 0 | |
main = (fname, interactive) -> | |
if fname ~= nil | |
debug "Reading file #{fname}" | |
read_program fname | |
dump_program! | |
while _lineno > 0 | |
if _lineno > #_program | |
if interactive | |
io.stdout\write "[#{_lineno}] " | |
line = io.read! | |
if handle_label_or_comment line, _lineno | |
continue | |
_program[_lineno] = line | |
else | |
break | |
debug "Executing [#{_lineno}] #{_program[_lineno]}" | |
_lineno = execute_line _lineno, _program[_lineno] | |
export _interactive = false | |
fname = nil | |
for i=1,#arg | |
if arg[i] == '-i' | |
_interactive = true | |
elseif arg[i] == '-v' | |
debuglv += 1 | |
else | |
fname = arg[i] | |
main fname, _interactive |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment