# Rule for making rules lua block ".." |compiler:defmacro("rule %spec = %body", function(compiler, vars, kind) | if kind == "Expression" then | compiler:error("Cannot use rule definitions as an expression.") | end | local spec = compiler:get_invocations_from_definition(vars.spec, vars) | return ("compiler:def("..compiler.utils.repr(spec,true)..", "..compiler:tree_to_lua(vars.body, vars)..")"), true |end) rule [help %invocation] =: lua block ".." |local fn_info = compiler.defs[vars.invocation] |if not fn_info then | compiler:writeln("Function not found: "..compiler.utils.repr(vars.invocation, true)) |else | compiler:writeln("rule "..compiler.utils.repr(fn_info.invocations).." ="..(fn_info.src or ":\\n ")) |end # Macros lua block ".." |local add_macro = function(compiler, vars, kind) | local spec = compiler:get_invocations_from_definition(vars.spec, vars) | local fn = compiler:tree_to_value(vars.body, vars) | compiler:defmacro(spec, fn, vars.body.src) | return "", true |end |compiler:defmacro("macro %spec = %body", add_macro) | |local add_macro_block = function(compiler, vars, kind) | local spec = compiler:get_invocations_from_definition(vars.spec, vars) | local fn = compiler:tree_to_value(vars.body, vars) | 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 ("do\\n"..fn(compiler, vars, kind).."\\nend"), true | end | compiler:defmacro(spec, wrapper, vars.body.src) | return "", true |end |compiler:defmacro("macro block %spec = %body", add_macro_block) # Compiler tools rule [eval %code, run %code] =: lua expr "compiler:run(vars.code)" rule [source code from tree %tree] =: lua block ".." |local _,_,leading_space = vars.tree.src:find("\\n(%s*)%S") |if leading_space then | local chunk1, chunk2 = vars.tree.src:match(":%s*([^\\n]*)(\\n.*)") | chunk2 = chunk2:gsub("\\n"..leading_space, "\\n") | vars.source = chunk1..chunk2.."\\n" |else | vars.source = vars.tree.src:match(":%s*(%S.*)").."\\n" |end %source macro [source code %body] =: lua expr ".." |compiler.utils.repr(compiler:call("source code from tree %", vars.body), true) rule [run file %filename] =: lua block ".." |local file = io.open(vars.filename) |return compiler:run(file:read("*a")) # 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) # TODO: Make useful macro [as lua %block] =: lua expr "compiler.utils.repr(compiler:tree_to_lua(vars.block.value, 'Statement'), true)" macro block [show generated lua %block] =: ".."|compiler:writeln(\lua expr "compiler.utils.repr(compiler:tree_to_lua(vars.block.value, 'Statement'), true)"\) # Macro helper functions rule [%tree as value] =: lua expr ".." |compiler:tree_to_value(vars.tree, vars) rule [%tree as lua block] =: lua block ".." |return compiler:tree_to_lua(vars.tree, 'Statement'), true rule [%tree as lua expr] =: lua expr ".." |compiler:tree_to_lua(vars.tree, 'Expression') # Moonscript! macro block [moonscript block %moonscript_code] =: lua block ".." |local parse, compile = require('moonscript.parse'), require('moonscript.compile') |local moon_code = compiler:tree_to_value(vars.moonscript_code, vars) |local tree, err = parse.string(moon_code) |if not tree then | compiler:error("Failed to parse moonscript: "..err) |end |local lua_code, err, pos = compile.tree(tree) |if not lua_code then | compiler:error(compile.format_error(err, pos, moon_code)) |end |return "do\\n"..lua_code.."\\nend" macro [moonscript %moonscript_code] =: lua block ".." |local parse, compile = require('moonscript.parse'), require('moonscript.compile') |local moon_code = compiler:tree_to_value(vars.moonscript_code, vars) |local tree, err = parse.string(moon_code) |if not tree then | compiler:error("Failed to parse moonscript: "..err) |end |local lua_code, err, pos = compile.tree(tree) |if not lua_code then | compiler:error(compile.format_error(err, pos, moon_code)) |end |return "(function(compiler, vars)\\n"..lua_code.."\\nend)(compiler, vars)" # String functions rule [join %strs] =: lua block ".." |local str_bits = {} |for i,bit in ipairs(vars.strs) do str_bits[i] = compiler.utils.repr(bit) end |return table.concat(str_bits) rule [join %strs with glue %glue] =: lua block ".." |local str_bits = {} |for i,bit in ipairs(vars.strs) do str_bits[i] = compiler.utils.repr(bit) end |return table.concat(str_bits, vars.glue) rule [capitalize %str, %str capitalized] =: lua expr ".."|vars.str:gsub("%l", string.upper, 1) # Variable assignment #.. macro block [%var = %value] =: lua block ".." |if vars.var.type ~= "Var" then | compiler:error("Assignment operation has the wrong type for the left hand side. " | .."Expected Var, but got: "..vars.var.type) |end ".."|\%var as lua expr\ = \%value as lua expr\ lua block ".." |local function helper(callback) | return function(compiler, vars, kind) | if kind == "Expression" then | compiler:error("Cannot use an assignment operation as an expression value.") | end | if vars.var.type ~= "Var" then | compiler:error("Assignment operation has the wrong type for the left hand side. " | .."Expected Var, but got: "..vars.var.type) | end | if vars.rhs.type ~= "Thunk" then | compiler:error("Assignment operation has the wrong type for the right hand side. " | .."Expected Thunk, but got: "..vars.rhs.type.."\\nMaybe you used '=' instead of '=:'?") | end | if #vars.rhs.value.value == 1 then | return callback(compiler:tree_to_lua(vars.var, "Expression"), | compiler:tree_to_lua(vars.rhs.value.value[1].value, "Expression")), true | else | local ret = "do\\n local ret" | ret = ret .. "\\n "..compiler:tree_to_lua(vars.rhs.value, "Statement") | ret = ret .. "\\n "..callback(compiler:tree_to_lua(vars.var, "Expression"), "ret") | return (ret.."\\nend"), true | end | end |end |compiler:defmacro("%var = %rhs", helper(function(var,result) return var.." = "..result end)) |compiler:defmacro("%var += %rhs", helper(function(var,result) return var.." = "..var.." + "..result end)) |compiler:defmacro("%var -= %rhs", helper(function(var,result) return var.." = "..var.." - "..result end)) |compiler:defmacro("%var *= %rhs", helper(function(var,result) return var.." = "..var.." * "..result end)) |compiler:defmacro("%var /= %rhs", helper(function(var,result) return var.." = "..var.." / "..result end)) |compiler:defmacro("%var ^= %rhs", helper(function(var,result) return var.." = "..var.." ^ "..result end)) |compiler:defmacro("%var and= %rhs", helper(function(var,result) return var.." = "..var.." and "..result end)) |compiler:defmacro("%var or= %rhs", helper(function(var,result) return var.." = "..var.." or "..result end)) |compiler:defmacro("%var concat= %rhs", helper(function(var,result) return var.." = "..var.." .. "..result end)) |compiler:defmacro("%var mod= %rhs", helper(function(var,result) return var.." = "..var.." % "..result end)) # 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 compiler.utils.equivalent(vars.a, vars.b))" rule [%a != %b] =: lua expr "((vars.a ~= vars.b) or not compiler.utils.equivalent(vars.a, vars.b))" macro [repr %obj] =: ".."|compiler.utils.repr(\%obj as lua expr\, true) macro [say %str] =: ".."|compiler:writeln(compiler.utils.repr(\%str as lua expr\)) # Control flow rule [do %action] =: lua expr "vars.action(compiler, setmetatable({}, {__index=vars}))" macro block [return %return_value] =: lua block ".." |if vars.return_value.type ~= "Thunk" then | compiler:error("Assignment operation has the wrong type for the right hand side. " | .."Expected Thunk, but got: "..vars.return_value.type.."\\nMaybe you used '=' instead of '=:'?") |end ".."|do | local ret | \lua expr "compiler:tree_to_lua(vars.return_value.value, 'Statement')"\ | return ret |end macro block [return] =: "return nil" # Conditionals macro block [if %condition %if_body] =: ".."|if \%condition as lua expr\ then | \(lua expr "vars.if_body.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") as lua block\ |else | \(lua expr "vars.else_body.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) # Loop control flow macro block [break] =: "break" macro block [continue] =: "continue" # TODO: add labeled break/continue? # GOTOs lua block ".." |local function lua_label(label) | if label.type ~= "Var" then | compiler:error("Goto label has the wrong type for the label. Expected Var, but got: "..label.type) | end | local bits = "abcdefghijklmnop" | local lua_identifier = "label_" | for i=1,#label.value do | local byte = string.byte(label.value, i) | local low = byte % 16 | local high = (byte - low) / 16 | lua_identifier = lua_identifier .. bits:sub(low+1,low+1) .. bits:sub(high+1,high+1) | end | return lua_identifier |end | |compiler:defmacro("-> %label", function(compiler, vars, kind) | return "::"..lua_label(vars.label).."::", true |end) |compiler:defmacro("go to %label", function(compiler, vars, kind) | return "goto "..lua_label(vars.label), true |end) # While loops macro block [repeat %body] =: ".."|while true do | \(lua expr "vars.body.value") as lua block\ |end macro block [repeat while %condition %body] =: ".."|while \%condition as lua expr\ do | \(lua expr "vars.body.value") as lua block\ |end macro block [repeat until %condition %body] =: ".."|while not (\%condition as lua expr\) do | \(lua expr "vars.body.value") as lua block\ |end # For loops macro block [for %var in %iterable %body] =: %var-type =: lua expr "vars.var.type" if (%var-type != "Var"): error ".." |For loop has the wrong type for the loop variable. Expected Var, but got: \%var-type\ %var-code =: %var as lua expr ".."|local old_loopval = \%var-code\ |for i,value in ipairs(\%iterable as lua expr\) do | \%var-code\ = value | \(lua expr "vars.body.value") as lua block\ |end |\%var-code\ = old_loopval macro block [for all %iterable %body] =: ".."|local old_loopval = vars.it |for i,value in ipairs(\%iterable as lua expr\) do | vars.it = value | \(lua expr "vars.body.value") as lua block\ |end |vars.it = old_loopval # Switch statement/multi-branch if macro block [when %body] =: %result =: "" for %statement in (lua expr "vars.body.value.value"): %func-call =: lua expr "vars.statement.value" if ((lua expr "vars['func-call'].type") != "FunctionCall"): error "Invalid format for 'when' statement" %tokens =: lua expr "vars['func-call'].value" %star =: lua expr "vars.tokens[1]" if (((lua expr "vars.star.type") != "Word") or ((lua expr "vars.star.value") != "*")): error "Invalid format for 'when' statement" %thunk =: lua expr "vars.tokens[#vars.tokens]" if ((lua expr "vars.thunk.type") != "Thunk"): error "Invalid format for 'when' statement" %condition-bits =: [] for %i in (2 up to (lua expr "#vars.tokens")): lua block "table.insert(vars['condition-bits'], vars.tokens[vars.i])" %condition =: dict [..] ["type",lua expr "vars['func-call'].type"] ["src",lua expr "vars['func-call'].src"] ["value", %condition-bits] if ((lua expr "#vars.condition.value") == 0): %result concat=: ".." | |do | local ret | \(lua expr "vars.thunk.value") as lua block\ | return ret |end ..else: %result concat=: ".." | |if \%condition as lua expr\ then | local ret | \(lua expr "vars.thunk.value") as lua block\ | return ret |end %result # List Comprehension # TODO: maybe make this lazy, or a lazy version? macro [%expression for %var in %iterable] =: %var-type =: lua expr "vars.var.type" if (%var-type != "Var"): error ".." |List comprehension has the wrong type for the loop variable. Expected Var, but got: \%var-type\ %var-code =: %var as lua expr ".."|(function(game, vars) | local comprehension = {} | for i,value in ipairs(\%iterable as lua expr\) do | \%var-code\ = 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 %var in %iterable] =: %var-type =: lua expr "vars.var.type" if (%var-type != "Var"): error ".." |Dict comprehension has the wrong type for the loop variable. Expected Var, but got: \%var-type\ %var-code =: %var as lua expr ".."|(function(game, vars) | local comprehension = {} | for i,value in ipairs(\%iterable as lua expr\) do | \%var-code\ = 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 "compiler.utils.range(vars.start,vars.stop-1)" rule [%start thru %stop, %start through %stop] =: lua expr "compiler.utils.range(vars.start,vars.stop)" rule [%start down to %stop] =: lua expr "compiler.utils.range(vars.start,vars.stop+1,-1)" rule [%start down thru %stop, %start down through %stop] =: lua expr "compiler.utils.range(vars.start,vars.stop,-1)" rule [%start up to %stop via %step] =: lua expr "compiler.utils.range(vars.start,vars.stop-1,vars.step)" rule [%start thru %stop via %step, %start through %stop via %step] =: lua expr "compiler.utils.range(vars.start,vars.stop,vars.step)" rule [%start down to %stop via %step] =: lua expr "compiler.utils.range(vars.start,vars.stop+1,-vars.step)" rule [%start down thru %stop via %step, %start down through %stop via %step] =: lua expr "compiler.utils.range(vars.start,vars.stop,-vars.step)" # Common utility functions rule [random number] =: lua expr "math.random()" rule [sum of %items] =: lua expr "compiler.utils.sum(vars.items)" rule [product of %items] =: lua expr "compiler.utils.product(vars.items)" rule [all of %items] =: lua expr "compiler.utils.all(vars.items)" rule [any of %items] =: lua expr "compiler.utils.any(vars.items)" rule [avg of %items, average of %items] =: lua expr "(compiler.utils.sum(vars.items)/#vars.items)" rule [min of %items, smallest of %items, lowest of %items] =: lua expr "compiler.utils.min(vars.items)" rule [max of %items, biggest of %items, largest of %items, highest of %items] =: lua expr "compiler.utils.min(vars.items)" rule [min of %items with respect to %keys] =: lua expr "compiler.utils.min(vars.items, vars.keys)" rule [max of %items with respect to %keys] =: lua expr "compiler.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 -> %index ..=: ".."|\%list as lua expr\[\%index as lua expr\] ".."|\%list as lua expr\[\%index as lua expr\] macro [first in %list] =: ".."|\%list as lua expr\[1] macro [..] %index st to last in %list, %index nd to last in %list, %index rd to last in %list %index th to last in %list ..=: ".."|compiler.utils.nth_to_last(\%list as lua expr\, \%index as lua expr\) macro [last in %list] =: ".."|compiler.utils.nth_to_last(\%list as lua expr\, 1) macro block [..] %list's %index = %new_value, %index st in %list = %new_value, %index nd in %list = %new_value %index rd in %list = %new_value, %index th in %list = %new_value, %index in %list = %new_value %list -> %index = %new_value ..=: lua block ".." |if vars.new_value.type ~= "Thunk" then | compiler:error("Dict assignment operation has the wrong type for the right hand side. " | .."Expected Thunk, but got: "..vars.new_value.type.."\\nMaybe you used '=' instead of '=:'?") |end ".."|do | local ret | \lua expr "compiler:tree_to_lua(vars.new_value.value, 'Statement')"\ | \%list as lua expr\[\%index as lua expr\] = ret |end macro [append %item to %list, add %item to %list] =: ".."|table.insert(\%list as lua expr\, \%item as lua expr\) rule [flatten %lists] =: %flat =: [] for %list in %lists: for %item in %list: add %item to %flat %flat 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] =: %dict =: [] for %pair in %items: lua block "vars.dict[vars.pair[1]] = vars.pair[2]" return: %dict rule [entries in %dict] =: lua block ".." |local items = {} |for k,v in pairs(vars.dict) do | table.insert(items, {key=k,value=v}) |end |return items # 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 | compiler:writeln("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 | compiler:writeln("Function is already allowed by everyone: "..tostring(fn)) | elseif not compiler:check_permission(fn) then | compiler:writeln("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 | compiler:writeln("Cannot remove items from a whitelist when there is no whitelist on function: "..tostring(fn)) | elseif not compiler:check_permission(fn) then | compiler:writeln("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 # For unit testing macro [parse tree %code] =: lua expr "compiler.utils.repr(compiler:stringify_tree(vars.code.value), true)" macro block [test %code yields %expected] =: %generated =: lua expr "compiler.utils.repr(compiler:stringify_tree(vars.code.value), true)" %expected =: %expected as lua expr if (%generated != %expected): say "Test failed!" say "Expected:" say %expected say "But got:" say %generated error! return: ""