# Rule for making rules lua block ".." |compiler:def("rule %spec %body", function(compiler, vars) | return compiler:def(vars.spec, vars.body) |end) # Macros rule "macro %spec %body": lua block ".." |compiler:defmacro(vars.spec, vars.body) rule "macro block %spec %body": lua block ".." |do | local spec, body = vars.spec, vars.body | local wrapper = function(compiler, vars, kind) | if kind == "Expression" then | compiler:error("Macro: "..spec.." was defined to be a block, but is being used as an expression.") | end | return body(compiler, vars, kind), true | end | compiler:defmacro(spec, wrapper) | return nil |end # Compiler tools rule ["eval %code", "run %code"]: lua expr "compiler:run(vars.code)" macro "source code %code": lua expr "utils.repr(vars.code.src, true)" rule "run file %filename": lua block ".." |do | local file = io.open(vars.filename) | return compiler:run(file:read("*a")) |end # Macro helper functions rule "%tree as lua block": lua block [..] "do return compiler:tree_to_lua(", %tree, ", 'Statement'), true end" rule "%tree as lua expr": lua expr [..] "compiler:tree_to_lua(", %tree, ", 'Expression')" # String functions rule "join %strs": lua block ".." |local str_bits = {} |for i,bit in ipairs(vars.strs) do str_bits[i] = utils.repr(bit) end |do return table.concat(str_bits) end rule "join %strs with glue %glue": lua block ".." |local str_bits = {} |for i,bit in ipairs(vars.strs) do str_bits[i] = utils.repr(bit) end |do return table.concat(str_bits, vars.glue) end rule ["capitalize %str", "%str capitalized"]: lua expr ".."|vars.str:gsub("%l", string.upper, 1) # Variable assignment macro block "let %varname = %value": ".."|vars[\%varname as lua expr\] = \%value as lua expr\ # Operators macro ["true","yes"]: "true" macro ["false","no"]: "false" macro ["nil","null"]: "nil" macro block ["nop", "pass"]: "" macro "%a + %b": ".."|(\%a as lua expr\ + \%b as lua expr\) macro "%a + %b + %c": ".."|(\%a as lua expr\ + \%b as lua expr\ + \%c as lua expr\) macro "%a + %b + %c + %d": ".."|(\%a as lua expr\ + \%b as lua expr\ + \%c as lua expr\ + \%d as lua expr\) macro "%a - %b": ".."|(\%a as lua expr\ - \%b as lua expr\) macro "%a * %b": ".."|(\%a as lua expr\ * \%b as lua expr\) macro "%a * %b * %c": ".."|(\%a as lua expr\ * \%b as lua expr\ * \%c as lua expr\) macro "%a * %b * %c * %d": ".."|(\%a as lua expr\ * \%b as lua expr\ * \%c as lua expr\ * \%d as lua expr\) macro "%a / %b": ".."|(\%a as lua expr\ / \%b as lua expr\) macro "%a === %b": ".."|(\%a as lua expr\ == \%b as lua expr\) macro "%a !== %b": ".."|(\%a as lua expr\ ~= \%b as lua expr\) macro "%a < %b": ".."|(\%a as lua expr\ < \%b as lua expr\) macro "%a < %b < %c": ".."|((\%a as lua expr\ < \%b as lua expr\) and (\%b as lua expr\ < \%c as lua expr\)) macro "%a <= %b < %c": ".."|((\%a as lua expr\ <= \%b as lua expr\) and (\%b as lua expr\ < \%c as lua expr\)) macro "%a <= %b <= %c": ".."|((\%a as lua expr\ <= \%b as lua expr\) and (\%b as lua expr\ <= \%c as lua expr\)) macro "%a < %b <= %c": ".."|((\%a as lua expr\ < \%b as lua expr\) and (\%b as lua expr\ <= \%c as lua expr\)) macro "%a > %b > %c": ".."|((\%a as lua expr\ > \%b as lua expr\) and (\%b as lua expr\ > \%c as lua expr\)) macro "%a >= %b > %c": ".."|((\%a as lua expr\ >= \%b as lua expr\) and (\%b as lua expr\ > \%c as lua expr\)) macro "%a >= %b >= %c": ".."|((\%a as lua expr\ >= \%b as lua expr\) and (\%b as lua expr\ >= \%c as lua expr\)) macro "%a > %b >= %c": ".."|((\%a as lua expr\ > \%b as lua expr\) and (\%b as lua expr\ >= \%c as lua expr\)) macro "%a <= %b": ".."|(\%a as lua expr\ <= \%b as lua expr\) macro "%a > %b": ".."|(\%a as lua expr\ > \%b as lua expr\) macro "%a >= %b": ".."|(\%a as lua expr\ >= \%b as lua expr\) macro "%a ^ %b": ".."|(\%a as lua expr\ ^ \%b as lua expr\) macro "%a and %b": ".."|(\%a as lua expr\ and \%b as lua expr\) macro "%a and %b and %c": ".."|(\%a as lua expr\ and \%b as lua expr\ and \%c as lua expr\) macro "%a and %b and %c and %d": ".."|(\%a as lua expr\ and \%b as lua expr\ and \%c as lua expr\ and \%d as lua expr\) macro "%a or %b": ".."|(\%a as lua expr\ or \%b as lua expr\) macro "%a or %b or %c": ".."|(\%a as lua expr\ or \%b as lua expr\ or \%c as lua expr\) macro "%a or %b or %c or %d": ".."|(\%a as lua expr\ or \%b as lua expr\ or \%c as lua expr\ or \%d as lua expr\) macro "%a mod %b": ".."|(\%a as lua expr\ mod \%b as lua expr\) macro "- %a": ".."|-(\%a as lua expr\) macro "not %a": ".."|not (\%a as lua expr\) rule "%a == %b": lua expr "((vars.a == vars.b) or utils.equivalent(vars.a, vars.b))" rule "%a != %b": lua expr "((vars.a ~= vars.b) or not utils.equivalent(vars.a, vars.b))" macro "say %str": ".."|print(utils.repr(\%str as lua expr\)) # Control flow rule "do %action": lua expr "vars.action(compiler, setmetatable({}, {__index=vars}))" macro block "return %return-value": ".."|do return \%return-value as lua expr\ end macro block "return": "do return nil end" # Conditionals macro block "if %condition %if_body": ".."|if \%condition as lua expr\ then | \(lua expr "vars.if_body.value.value") as lua block\ |end macro block "if %condition %if_body else %else_body": ".."|if \%condition as lua expr\ then | \(lua expr "vars.if_body.value.value") as lua block\ |else | \(lua expr "vars.else_body.value.value") as lua block\ |end # Ternary operator macro "%if_expr if %condition else %else_expr": ".."|(function(compiler, vars) | if \%condition as lua expr\ then | return \%if_expr as lua expr\ | else | return \%else_expr as lua expr\ | end |end)(compiler, vars) # For loop macro block "for %varname in %iterable %body": let "varname" = (%varname as lua expr) ".."|do | local old_loopval = vars[\%varname\] | for i,value in ipairs(\%iterable as lua expr\) do | vars[\%varname\] = value | \(lua expr "vars.body.value.value") as lua block\ | end | vars[\%varname\] = old_loopval |end macro block "for all %iterable %body": ".."|do | local old_loopval = vars.it | for i,value in ipairs(\%iterable as lua expr\) do | vars.it = value | \(lua expr "vars.body.value.value") as lua block\ | end | vars.it = old_loopval |end # List Comprehension # TODO: maybe make this lazy, or a lazy version? macro "%expression for %varname in %iterable": ".."|(function(game, vars) | local comprehension = {} | for i,value in ipairs(\%iterable as lua expr\) do | vars[\%varname as lua expr\] = value | comprehension[i] = \%expression as lua expr\ | end | return comprehension |end)(game, setmetatable({}, {__index=vars})) macro "%expression for all %iterable": ".."|(function(game, vars) | local comprehension = {} | for i,value in ipairs(\%iterable as lua expr\) do | vars.it = value | comprehension[i] = \%expression as lua expr\ | end | return comprehension |end)(game, setmetatable({}, {__index=vars})) # Dict comprehension macro "%key = %value for %varname in %iterable": ".."|(function(game, vars) | local comprehension = {} | for i,value in ipairs(\%iterable as lua expr\) do | vars[\%varname as lua expr\] = value | comprehension[\%key as lua expr\] = \%value as lua expr\ | end | return comprehension |end)(game, setmetatable({}, {__index=vars})) macro "%key = %value for all %iterable": ".."|(function(game, vars) | local comprehension = {} | for i,value in ipairs(\%iterable as lua expr\) do | vars.it = value | comprehension[\%key as lua expr\] = \%value as lua expr\ | end | return comprehension |end)(game, setmetatable({}, {__index=vars})) # Number ranges rule "%start up to %stop": lua expr "utils.range(vars.start,vars.stop-1)" rule ["%start thru %stop", "%start through %stop"]: lua expr "utils.range(vars.start,vars.stop)" rule "%start down to %stop": lua expr "utils.range(vars.start,vars.stop+1,-1)" rule ["%start down thru %stop", "%start down through %stop"]: lua expr "utils.range(vars.start,vars.stop,-1)" rule "%start up to %stop via %step": lua expr "utils.range(vars.start,vars.stop-1,vars.step)" rule ["%start thru %stop via %step", "%start through %stop via %step"]: lua expr "utils.range(vars.start,vars.stop,vars.step)" rule "%start down to %stop via %step": lua expr "utils.range(vars.start,vars.stop+1,-vars.step)" rule ["%start down thru %stop via %step", "%start down through %stop via %step"]: lua expr "utils.range(vars.start,vars.stop,-vars.step)" # Common utility functions rule ["random number"]: lua expr "math.random()" rule ["sum of %items"]: lua expr "utils.sum(vars.items)" rule ["product of %items"]: lua expr "utils.product(vars.items)" rule ["all of %items"]: lua expr "utils.all(vars.items)" rule ["any of %items"]: lua expr "utils.any(vars.items)" rule ["avg of %items", "average of %items"]: lua expr "(utils.sum(vars.items)/#vars.items)" rule ["min of %items", "smallest of %items", "lowest of %items"]: lua expr "utils.min(vars.items)" rule ["max of %items", "biggest of %items", "largest of %items", "highest of %items"]: lua expr "utils.min(vars.items)" rule ["min of %items with respect to %keys"]: lua expr "utils.min(vars.items, vars.keys)" rule ["max of %items with respect to %keys"]: lua expr "utils.max(vars.items, vars.keys)" # List/dict functions macro [..] "%list 's %index", "%index st in %list", "%index nd in %list", "%index rd in %list" "%index th in %list", "%index in %list" ..: ".."|\%list as lua expr\[\%index as lua expr\] macro block [..] "%list 's %index = %value", "%index st in %list = %value", "%index nd in %list = %value" "%index rd in %list = %value", "%index th in %list = %value", "%index in %list = %value" ..: ".."|\%list as lua expr\[\%index as lua expr\] = \%value as lua expr\ macro ["%item is in %list", "%list contains %item"]: ".."|(\%list as lua expr\[\%index as lua expr\] ~= nil) macro ["length of %list", "size of %list", "number of %list"]: ".."|#(\%list as lua expr\) rule "dict %items": let "dict" = [] for "pair" in %items: lua block "vars.dict[vars.pair[1]] = vars.pair[2]" return %dict # Permission functions rule "restrict %fn to within %whitelist": lua block ".." |local fns = compiler:get_invocations(vars.fn) |local whitelist = compiler:get_invocations(vars.whitelist) |local whiteset = {} |for _,w in ipairs(whitelist) do | if not compiler.defs[w] then | compiler:error("Undefined function: "..tostring(w)) | else whiteset[w] = true | end |end |for _,fn in ipairs(fns) do | local fn_info = compiler.defs[fn] | if fn_info == nil then | compiler:error("Undefined function: "..tostring(fn)) | elseif not compiler:check_permission(fn) then | print("You do not have permission to restrict function: "..tostring(fn)) | else | compiler.defs[fn].whiteset = whiteset | end |end rule "allow %whitelist to use %fn": lua block ".." |local fns = compiler:get_invocations(vars.fn) |local whitelist = compiler:get_invocations(vars.whitelist) |for _,w in ipairs(whitelist) do | if not compiler.defs[w] then | compiler:error("Undefined function: "..tostring(w)) | end |end |for _,fn in ipairs(fns) do | local fn_info = compiler.defs[fn] | if fn_info == nil then | compiler:error("Undefined function: "..tostring(fn)) | elseif fn_info.whiteset == nil then | print("Function is already allowed by everyone: "..tostring(fn)) | elseif not compiler:check_permission(fn) then | print("You do not have permission to grant permissions for function: "..tostring(fn)) | else | for _,w in ipairs(whitelist) do | fn_info.whiteset[w] = true | end | end |end rule "forbid %blacklist to use %fn": lua block ".." |local fns = compiler:get_invocations(vars.fn) |local blacklist = compiler:get_invocations(vars.blacklist) |for _,b in ipairs(blacklist) do | if not compiler.defs[b] then | compiler:error("Undefined function: "..tostring(b)) | end |end |for _,fn in ipairs(fns) do | local fn_info = compiler.defs[fn] | if fn_info == nil then | compiler:error("Undefined function: "..tostring(fn)) | elseif fn_info.whiteset == nil then | print("Cannot remove items from a whitelist when there is no whitelist on function: "..tostring(fn)) | elseif not compiler:check_permission(fn) then | print("You do not have permission to restrict function: "..tostring(fn)) | else | for _,b in ipairs(blacklist) do fn_info.whiteset[b] = nil end | end |end # Error functions rule "error!": lua block ".." |table.remove(compiler.callstack) |compiler:error() rule "error %msg": lua block ".." |table.remove(compiler.callstack) |compiler:error(vars.msg) macro block "test %code yields %expected": let "generated" = (lua expr "utils.repr(compiler:stringify_tree(vars.code.value.value), true)") let "expected" = (%expected as lua expr) if (%generated != %expected): say "Test failed!" say "Expected:" say %expected say "But got:" say %generated error! return ""