Skip to content

Instantly share code, notes, and snippets.

@silverweed
Last active August 29, 2015 14:21
Show Gist options
  • Save silverweed/6a1abee2ae421fb65b60 to your computer and use it in GitHub Desktop.
Save silverweed/6a1abee2ae421fb65b60 to your computer and use it in GitHub Desktop.
ROCK interpreter
-- 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
-- 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