diff --git a/core.nom b/core.nom index ae4c22a..0b0193b 100644 --- a/core.nom +++ b/core.nom @@ -1,10 +1,14 @@ # Rule for making rules lua block ".." - |compiler:def("rule %spec = %body", function(compiler, vars) - | return compiler:def(vars.spec, vars.body) + |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.value, vars) + | return ("compiler:def("..compiler.utils.repr(spec,true)..", "..compiler:tree_to_lua(vars.body, vars)..")"), true |end) -rule "help %invocation" =: +rule: help %invocation ..=: lua block ".." |local fn_info = compiler.defs[vars.invocation] |if not fn_info then @@ -16,7 +20,7 @@ rule "help %invocation" =: # Macros lua block ".." |local add_macro = function(compiler, vars, kind) - | local spec = compiler:tree_to_value(vars.spec, vars) + | local spec = compiler:get_invocations_from_definition(vars.spec.value, vars) | local fn = compiler:tree_to_value(vars.body, vars) | compiler:defmacro(spec, fn, vars.body.src) | return "", true @@ -24,7 +28,7 @@ lua block ".." |compiler:defmacro("macro %spec = %body", add_macro) | |local add_macro_block = function(compiler, vars, kind) - | local spec = compiler:tree_to_value(vars.spec, vars) + | local spec = compiler:get_invocations_from_definition(vars.spec.value, vars) | local fn = compiler:tree_to_value(vars.body, vars) | local wrapper = function(compiler, vars, kind) | if kind == "Expression" then @@ -38,53 +42,56 @@ lua block ".." |compiler:defmacro("macro block %spec = %body", add_macro_block) # Compiler tools -rule ["eval %code", "run %code"] =: +rule: + eval %code + run %code +..=: lua expr "compiler:run(vars.code)" -macro "source code %code" =: +macro: source code %code ..=: lua block ".." |if vars.code.value.type ~= "Thunk" then | compiler:error("'source code %' only takes code blocks, not "..vars.code.value.type) |end lua expr "compiler.utils.repr(vars.code.value.value.src, true)" -rule "run file %filename" =: +rule: run file %filename ..=: lua block ".." |local file = io.open(vars.filename) |return compiler:run(file:read("*a")) # Error functions -rule "error!" =: +rule: error! ..=: lua block ".." |table.remove(compiler.callstack) |compiler:error() -rule "error %msg" =: +rule: error %msg ..=: lua block ".." |table.remove(compiler.callstack) |compiler:error(vars.msg) -macro "as lua %block" =: +macro: as lua %block ..=: lua expr "compiler.utils.repr(compiler:tree_to_lua(vars.block.value.value, vars), true)" -macro block "show generated lua %block" =: +macro block: show generated lua %block ..=: ".."|compiler:writeln(\lua expr "compiler.utils.repr(compiler:tree_to_lua(vars.block.value.value, vars), true)"\) # Macro helper functions -rule "%tree as value" =: +rule: %tree as value ..=: lua expr ".." |compiler:tree_to_value(vars.tree, vars) -rule "%tree as lua block" =: +rule: %tree as lua block ..=: lua block ".." |return compiler:tree_to_lua(vars.tree, 'Statement'), true -rule "%tree as lua expr" =: +rule: %tree as lua expr ..=: lua expr ".." |compiler:tree_to_lua(vars.tree, 'Expression') # Moonscript! -macro block "moonscript block %moonscript_code" =: +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) @@ -98,7 +105,7 @@ macro block "moonscript block %moonscript_code" =: |end |return "do\\n"..lua_code.."\\nend" -macro "moonscript %moonscript_code" =: +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) @@ -113,24 +120,27 @@ macro "moonscript %moonscript_code" =: |return "(function(compiler, vars)\\n"..lua_code.."\\nend)(compiler, vars)" # String functions -rule "join %strs" =: +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" =: +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"] =: +rule: + capitalize %str + %str capitalized +..=: lua expr ".."|vars.str:gsub("%l", string.upper, 1) # Variable assignment #.. - macro block "%var = %value" =: + macro block: %var = %value ..=: lua block ".." |if vars.var.value.type ~= "Var" then | compiler:error("Assignment operation has the wrong type for the left hand side. " @@ -138,81 +148,107 @@ rule ["capitalize %str", "%str capitalized"] =: |end ".."|\%var as lua expr\ = \%value as lua expr\ -macro block "%var = %value" =: - lua block ".." - |if vars.var.value.type ~= "Var" then - | compiler:error("Assignment operation has the wrong type for the left hand side. " - | .."Expected Var, but got: "..vars.var.value.type) - |end - lua block ".." - |if vars.value.value.type ~= "Thunk" then - | compiler:error("Assignment operation has the wrong type for the right hand side. " - | .."Expected Thunk, but got: "..vars.value.value.type.."\\nMaybe you used '=' instead of '=:'?") - |end - ".."|do - | local ret - | \lua expr "compiler:tree_to_lua(vars.value.value.value, 'Statement')"\ - | \%var as lua expr\ = ret - |end +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.value.type ~= "Var" then + | compiler:error("Assignment operation has the wrong type for the left hand side. " + | .."Expected Var, but got: "..vars.var.value.type) + | end + | if vars.value.value.type ~= "Thunk" then + | compiler:error("Assignment operation has the wrong type for the right hand side. " + | .."Expected Thunk, but got: "..vars.value.value.type.."\\nMaybe you used '=' instead of '=:'?") + | end + | local ret = "do\\n local ret" + | ret = ret .. "\\n "..compiler:tree_to_lua(vars.value.value.value, "Statement") + | ret = ret .. "\\n "..callback(compiler:tree_to_lua(vars.var, "Expression")) + | return (ret.."\\nend"), true + | end + |end + |compiler:defmacro("%var = %value", helper(function(var) return var.." = ret" end)) + |compiler:defmacro("%var += %value", helper(function(var) return var.." = "..var.." + ret" end)) + |compiler:defmacro("%var -= %value", helper(function(var) return var.." = "..var.." - ret" end)) + |compiler:defmacro("%var *= %value", helper(function(var) return var.." = "..var.." * ret" end)) + |compiler:defmacro("%var /= %value", helper(function(var) return var.." = "..var.." / ret" end)) + |compiler:defmacro("%var ^= %value", helper(function(var) return var.." = "..var.." ^ ret" end)) + |compiler:defmacro("%var and= %value", helper(function(var) return var.." = "..var.." and ret" end)) + |compiler:defmacro("%var or= %value", helper(function(var) return var.." = "..var.." or ret" end)) + |compiler:defmacro("%var concat= %value", helper(function(var) return var.." = "..var.." .. ret" end)) + |compiler:defmacro("%var mod= %value", helper(function(var) return var.." = "..var.." % ret" 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" =: +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" =: +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" =: +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" =: +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\) +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" =: +rule: %a == %b ..=: lua expr "((vars.a == vars.b) or compiler.utils.equivalent(vars.a, vars.b))" -rule "%a != %b" =: +rule: %a != %b ..=: lua expr "((vars.a ~= vars.b) or not compiler.utils.equivalent(vars.a, vars.b))" -macro "repr %obj" =: +macro: repr %obj ..=: ".."|compiler.utils.repr(\%obj as lua expr\, true) -macro "say %str" =: +macro: say %str ..=: ".."|compiler:writeln(compiler.utils.repr(\%str as lua expr\)) # Control flow -rule "do %action" =: +rule: do %action ..=: lua expr "vars.action(compiler, setmetatable({}, {__index=vars}))" -macro block "return %return_value" =: +macro block: return %return_value ..=: lua block ".." |if vars.return_value.value.type ~= "Thunk" then | compiler:error("Assignment operation has the wrong type for the right hand side. " @@ -224,16 +260,16 @@ macro block "return %return_value" =: | return ret |end -macro block "return" =: +macro block: return ..=: "return nil" # Conditionals -macro block "if %condition %if_body" =: +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" =: +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 @@ -241,7 +277,7 @@ macro block "if %condition %if_body else %else_body" =: |end # Ternary operator -macro "%if_expr if %condition else %else_expr" =: +macro: %if_expr if %condition else %else_expr ..=: ".."|(function(compiler, vars) | if \%condition as lua expr\ then | return \%if_expr as lua expr\ @@ -250,8 +286,51 @@ macro "%if_expr if %condition else %else_expr" =: | end |end)(compiler, vars) -# For loop -macro block "for %var in %iterable %body" =: +# 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.value).."::", true + |end) + |compiler:defmacro("go to %label", function(compiler, vars, kind) + | return "goto "..lua_label(vars.label.value), true + |end) + +# While loops +macro block: repeat %body ..=: + ".."|while true do + | \(lua expr "vars.body.value.value") as lua block\ + |end +macro block: repeat while %condition %body ..=: + ".."|while \%condition as lua expr\ do + | \(lua expr "vars.body.value.value") as lua block\ + |end +macro block: repeat until %condition %body ..=: + ".."|while not (\%condition as lua expr\) do + | \(lua expr "vars.body.value.value") as lua block\ + |end + +# For loops +macro block: for %var in %iterable %body ..=: %var-type =: lua expr "vars.var.value.type" if (%var-type != "Var"): error ".." @@ -264,7 +343,7 @@ macro block "for %var in %iterable %body" =: |end |\%var-code\ = old_loopval -macro block "for all %iterable %body" =: +macro block: for all %iterable %body ..=: ".."|local old_loopval = vars.it |for i,value in ipairs(\%iterable as lua expr\) do | vars.it = value @@ -274,7 +353,7 @@ macro block "for all %iterable %body" =: # List Comprehension # TODO: maybe make this lazy, or a lazy version? -macro "%expression for %var in %iterable" =: +macro: %expression for %var in %iterable ..=: %var-type =: lua expr "vars.var.value.type" if (%var-type != "Var"): error ".." @@ -289,7 +368,7 @@ macro "%expression for %var in %iterable" =: | return comprehension |end)(game, setmetatable({}, {__index=vars})) -macro "%expression for all %iterable" =: +macro: %expression for all %iterable ..=: ".."|(function(game, vars) | local comprehension = {} | for i,value in ipairs(\%iterable as lua expr\) do @@ -300,7 +379,7 @@ macro "%expression for all %iterable" =: |end)(game, setmetatable({}, {__index=vars})) # Dict comprehension -macro "%key -> %value for %var in %iterable" =: +macro: %key -> %value for %var in %iterable ..=: %var-type =: lua expr "vars.var.value.type" if (%var-type != "Var"): error ".." @@ -315,7 +394,7 @@ macro "%key -> %value for %var in %iterable" =: | return comprehension |end)(game, setmetatable({}, {__index=vars})) -macro "%key -> %value for all %iterable" =: +macro: %key -> %value for all %iterable ..=: ".."|(function(game, vars) | local comprehension = {} | for i,value in ipairs(\%iterable as lua expr\) do @@ -326,58 +405,91 @@ macro "%key -> %value for all %iterable" =: |end)(game, setmetatable({}, {__index=vars})) # Number ranges -rule "%start up to %stop" =: +rule: %start up to %stop ..=: lua expr "compiler.utils.range(vars.start,vars.stop-1)" -rule ["%start thru %stop", "%start through %stop"] =: +rule: + %start thru %stop + %start through %stop +..=: lua expr "compiler.utils.range(vars.start,vars.stop)" -rule "%start down to %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"] =: +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" =: +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"] =: +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" =: +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"] =: +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"] =: +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"] =: +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"] =: +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"] =: +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" +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\] -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" - "%index = %value in %list", "%list -> %index = %value" +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 -> %index = %value ..=: lua block ".." |if vars.value.value.type ~= "Thunk" then @@ -390,20 +502,27 @@ macro block [..] | \%list as lua expr\[\%index as lua expr\] = ret |end -macro ["%item is in %list", "%list contains %item"] =: +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"] =: +macro: + length of %list + size of %list + number of %list +..=: ".."|#(\%list as lua expr\) -rule "dict %items" =: +rule: dict %items ..=: %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" =: +rule: restrict %fn to within %whitelist ..=: lua block ".." |local fns = compiler:get_invocations(vars.fn) |local whitelist = compiler:get_invocations(vars.whitelist) @@ -425,7 +544,7 @@ rule "restrict %fn to within %whitelist" =: | end |end -rule "allow %whitelist to use %fn" =: +rule: allow %whitelist to use %fn ..=: lua block ".." |local fns = compiler:get_invocations(vars.fn) |local whitelist = compiler:get_invocations(vars.whitelist) @@ -449,7 +568,7 @@ rule "allow %whitelist to use %fn" =: | end |end -rule "forbid %blacklist to use %fn" =: +rule: forbid %blacklist to use %fn ..=: lua block ".." |local fns = compiler:get_invocations(vars.fn) |local blacklist = compiler:get_invocations(vars.blacklist) @@ -472,7 +591,7 @@ rule "forbid %blacklist to use %fn" =: |end # For unit testing -macro block "test %code yields %expected" =: +macro block: test %code yields %expected ..=: %generated =: lua expr "compiler.utils.repr(compiler:stringify_tree(vars.code.value.value), true)" %expected =: %expected as lua expr if (%generated != %expected): diff --git a/examples/parser_tests.nom b/examples/parser_tests.nom index 000b8de..81608b7 100644 --- a/examples/parser_tests.nom +++ b/examples/parser_tests.nom @@ -9,21 +9,23 @@ test: say (4) ..yields ".." | 4 test: - rule "fart" =: say "poot" + rule: fart ..=: say "poot" ..yields ".." |Call [rule % = %]: - | "fart" + | Thunk: + | Call [fart]! | Thunk: | Call [say %]: | "poot" test: - rule "doublefart": + rule: doublefart ..=: say "poot" say "poot" ..yields ".." - |Call [rule % %]: - | "doublefart" + |Call [rule % = %]: + | Thunk: + | Call [doublefart]! | Thunk: | Call [say %]: | "poot" diff --git a/examples/sample_code.nom b/examples/sample_code.nom index 2917be1..4f4d2dd 100644 --- a/examples/sample_code.nom +++ b/examples/sample_code.nom @@ -11,7 +11,7 @@ say (4) #.. "rule" is just a function that takes a function call spec and a block of code to run, and stores the function definition -rule "fart": say "poot" +rule: fart ..=: say "poot" fart @@ -33,13 +33,13 @@ say ".." |(done) | -rule "doublefart": # this farts twice +rule: doublefart ..=: # this farts twice say "poot" say "poot" doublefart -rule "subex work": return "subexpressions work" +rule: subex work ..=: "subexpressions work" say (subex work) @@ -54,7 +54,7 @@ say [..] 1, 2 3 -rule "say both %one and %two": +rule: say both %one and %two ..=: say %one say %two @@ -68,7 +68,7 @@ say both.. "hello" and "world" -rule "three": return 3 +rule: three ..=: 3 say both .. "a list:" and [..] @@ -79,16 +79,16 @@ if 1: yes if 1: yes ..else: no -say (do: return 5) +say (do: return: 5) -rule "do %one also %two": +rule: do %one also %two ..=: do %one do %two do: say "one liner" ..also: say "another one liner" -say (do: return "wow") +say (do: return: "wow") say (1 + (-(2 * 3))) @@ -110,27 +110,23 @@ say ".." | with multiple lines | and an interpolated expression: \2 + 5\ -rule "%n bottles": - lua block [..] - ".." - |do - | print("running raw lua code...") - | local n = - .., %n, ".." - | - | for i=n,1,-1 do - | print(tostring(i).." bottles of beer on the wall. Take one down, pass it around,") - | end - | print("no more bottles of beer on the wall.") - |end +rule: %n bottles ..=: + lua block ".." + |do + | print("running raw lua code...") + | for i=\%n\,1,-1 do + | print(tostring(i).." bottles of beer on the wall. Take one down, pass it around,") + | end + | print("no more bottles of beer on the wall.") + |end nil 9 bottles -rule "dumsum %nums": - let "sum" = 0 - for "n" in %nums: - let "sum" = (%sum + %n) - return %sum +rule: dumsum %nums ..=: + %sum =: 0 + for %n in %nums: + %sum +=: %n + return: %sum say (dumsum [1,2,3]) diff --git a/examples/sample_game.nom b/examples/sample_game.nom index a316b77..87f2c60 100644 --- a/examples/sample_game.nom +++ b/examples/sample_game.nom @@ -1,4 +1,155 @@ run file "core.nom" +run file "lib/secrets.nom" + + +with secrets: + rule: pending proposal ..=: + secret %pending + + rule: propose source %src ..=: + secret %pending =: %src + say ".." + |Proposal\%src\ + + macro block: propose %action ..=: ".." + |compiler:call("propose source %", \repr (%action's "src")\) + + rule: approve ..=: + if (pending proposal): + say ".." + |Running\pending proposal\ + do (..) + eval ".." + |return: \pending proposal\ + ..else: + say "No action pending" + +propose: + say "motion passed." + rule: fart ..=: + say "poot" + +approve +fart + +# Users: +with secrets: + secret %users =: lua expr "require('users')" + rule: find user %name ..=: + lua expr "secrets.users.by_name(vars.name) or compiler:error('Failed to find user: '..tostring(vars.name))" + rule: add user %name ..=: + lua expr "secrets.users.add(vars.name)" + macro: @ %name_block ..=: + %name_str =: lua expr "vars.name_block.value.value.src" + ".."|compiler:call("find user %", \repr %name_str\) + +say ".."|A user looks like: \@:spill\ +say ".."|A user looks like: \@:spill\ +add user "dave" +say ".."|A user looks like: \@:dave\ +add user "moloch" +say ".."|A user looks like: \@:moloch\ +add user "bruce" + +# Inventory: +with secrets: + secret %inventory =: lua expr ".." + |setmetatable({}, {__index=function(self,key) + | local t = {} + | self[key] = t + | return t + |end}) + with secrets: + lua block ".." + |local endings = setmetatable({x="es",c="es",s="es"}, {__index=function() return "s" end}) + |secrets.plurals = setmetatable({}, {__index=function(self,key) + | return key..endings[key:sub(-1)] + |end}) + |secrets.singulars = setmetatable({}, {__index=function(self,key) + | if key:sub(-2) == "es" and rawget(endings, key:sub(-3,-3)) then return key:sub(1,-3) end + | if key:sub(-1) == "s" then return key:sub(1,-2) end + | return key + |end}) + |secrets.canonicals = setmetatable({}, {__index=function(self,key) + | if key:sub(-1) == "s" then return secrets.singulars[key] end + | return key + |end}) + + rule: the plural of %singular is %plural ..=: + (secret %plurals)->%singular =: %plural + (secret %singulars)->%plural =: %singular + (secret %canonicals)->%plural =: %singular + + rule: singular %plural ..=: + %plural in (secret %singulars) + + rule: plural %singular ..=: + %singular in (secret %plurals) + + rule: canonicalize %item-name ..=: + %item-name in (secret %canonicals) + + rule: %person's inventory ..=: + (secret %inventory)->%person + + rule: %person's stock of %item ..=: + %item =: canonicalize %item + ((%person's inventory)->%item) or 0 + + rule: %person's stock of %item as str ..=: + %item =: canonicalize %item + %count =: %person's stock of %item + ".." + |\%count\ \(singular %item) if (%count == 1) else (plural %item)\ + + rule: give %person %count %item ..=: + %item =: canonicalize %item + (%person's inventory)-> %item =: (%person's stock of %item) + %count + + rule: give %person %count %item from %donor ..=: + %item =: canonicalize %item + if ((%donor's stock of %item) < %count): + say ".." + |\%donor\ does not have enough \%item\ to transfer + ..else: + (%person's inventory)->%item =: (%person's stock of %item) + %count + (%donor's inventory)->%item =: (%donor's stock of %item) - %count + + rule: + %person has %item + %person has a %item + %person has an %item + ..=: + (%person's stock of %item) > 0 + +give (@:bruce) 3 "hats" +give (@:bruce) 3 "hats" +give (@:dave) 1 "hat" from (@:bruce) +say ".."|Bruce has \(@:bruce)'s stock of "hats" as str\ +say ".."|Dave has \(@:dave)'s stock of "hats" as str\ +give (@:dave) 1 "box" +say ".."|Dave has \(@:dave)'s stock of "boxes" as str\ +the plural of "goose" is "geese" +give (@:dave) 1 "goose" +say ".."|Dave has \(@:dave)'s stock of "geese" as str\ +give (@:dave) 1 "goose" +say ".."|Dave has \(@:dave)'s stock of "geese" as str\ + +do: + lua block "math.randomseed(os.time())" + repeat until ((random number) < 0.01): + say "tick" + say "tock" + + + + + + + + + + # A basic key-value store that's only accessible through these functions: lua block ".." |do -- Use a closure to hide this behind the accessor rules diff --git a/examples/tutorial.nom b/examples/tutorial.nom index 7106f94..5f0acb3 100644 --- a/examples/tutorial.nom +++ b/examples/tutorial.nom @@ -25,7 +25,7 @@ run file "core.nom" 5 6,7,8 -# Dicts: +# Dictionaries (AKA hash maps): dict [["x", 99], ["y", 101]] dict [..] ["z", 222] @@ -34,22 +34,105 @@ dict [..] # Function calls: say "Hello world!" +# Variables use the % sign: +%str =: "Hello again" +say %str + +# There are +=:, -=:, *=:, /=:, ^=:, and=:, or=:, and mod=: operators for updating variables +%x =: 1 +%x +=: 100 +say %x + +# Conditionals look like this: +if (1 < 10): + say "1 is indeed < 10" + +if (1 > 10): + say "this won't print" +..else: + say "this will print" + +if (1 > 3): + say "this won't print" +..else: if (1 < 2): + say "this won't print" +..else: + say "this will print" + +# Loops look like this: +for %x in [1,2,3]: + say ".."|for loop over list #\%x\ + +# If you want to iterate over a numeric range, you can use some built in functions like this: +for %x in (1 through 3): + say ".."|for loop over number range #\%x\ + +# While loops: +%x =: 1 +repeat while (%x <= 3): + say ".."|repeat while loop #\%x\ + %x +=: 1 + +%x =: 1 +repeat until (%x > 3): + say ".."|repeat until loop #\%x\ + %x +=: 1 + +# Infinite loop: +%x =: 1 +repeat: + say ".."|repeat loop #\%x\ + %x +=: 1 + if (%x > 3): + break + +# GOTOs: +do: + %x =: 1 + -> %again + say ".."|go to loop #\%x\ + %x +=: 1 + if (%x <= 3): + go to %again + say "finished going to" + + # Function definition: -rule "say both %first and also %second": - # Variables use the "%" sign: +rule: + say both %first and also %second +..=: + # Function arguments are accessed just like variables say %first say %second -rule "get x from %dict": - #.. Functions can return values explicitly, but if not, the last line in the function - is the return value. - return (%dict's "x") +# The last line of a function is the return value +rule: + add %x and %y +..=: + %result =: %x + %y + %result + +# Functions can use "return" to return a value early +rule: + first fibonacci above %n +..=: + %f1 =: 0 + %f2 =: 1 + repeat: + %tmp =: %f1 + %f2 + %f1 =: %f2 + %f2 =: %tmp + if (%f2 > %n): + return: %f2 + +say (first fibonacci above 10) # Functions can have aliases, which may or may not have the arguments in different order -rule [..] - "I hate %worse-things more than %better-things", "I think %worse-things are worse than %better-things" - "I like %better-things more than %worse-things" -..: +rule: + I hate %worse-things more than %better-things + I think %worse-things are worse than %better-things + I like %better-things more than %worse-things +..=: say ".."|\%better-things capitalized\ rule and \%worse-things\ drool! I like "dogs" more than "cats" @@ -61,24 +144,33 @@ I think "chihuahuas" are worse than "corgis" say both "Hello" and also "again!" # Functions can even have their name at the end: -rule "%what-she-said is what she said": +rule: + %what-she-said is what she said +..=: say %what-she-said say "-- she said" + "Howdy pardner" is what she said #.. The language only reserves []{}().,:;% as special characters, so functions and variables can have really funky names! -rule ">> %foo-bar$$$^ --> %@@& _~-^-~_~-^ %1 !": +rule: + >> %foo-bar$$$^ --> %@@& _~-^-~_~-^ %1 ! +..=: say %foo-bar$$$^ say %@@& say %1 + >> "wow" --> "so flexible!" _~-^-~_~-^ "even numbers can be variables!" ! -# Though literals can't be used in function names # Math and logic operations are just treated the same as function calls in the syntax say (2 + 3) # So it's easy to define your own operators -rule "%a ++ %b": 2 * (%a + %b) +rule: + %a ++ %b +..=: + 2 * (%a + %b) + say (2 ++ 3) @@ -121,7 +213,7 @@ say both ".." ..and also.. "-- Abraham Lincoln" -rule "my favorite number": return 23 +rule: my favorite number ..=: 21 + 2 # Subexpressions are wrapped in parentheses: say (my favorite number) @@ -139,48 +231,10 @@ say ".." number ..\, but this time it uses an indented subexpression! -#.. There's a few macros in the language for things like conditional branches and logic/math - operations, but they can be thought of as basically the same as functions. - There are no keywords in the language! -if (1 < 10): - say "One is less than ten" -..else: - say "One is not less than ten" - -#.. Breakdown of the above: - Function call (actually a macro) to "if % % else %" - First argument is a subexpression that is a function call (also a macro) to "% < %" - that performs a comparison on its arguments, 1 and 10 - Second argument is a block of code that includes a function call to "say %", the "if" body - Third argument is a block of code that includes a different function call to "say %", the "else" body - -# Line continuations can be used for "elseif" -if (1 > 10): - say "First condition" -..else: if (1 > 5): - say "Second condition" -..else: - say "Last condition" - -# ^that's the same as: -if (1 > 10): - say "First condition" -..else: - if (1 > 5): - say "Second condition" - ..else: - say "Last condition" - -# Variables are modified with a macro, "let % = %" -let "numbers" = [5,6,7] - -# Looping: -say ".."|Looping over \%numbers\: -for "number" in %numbers: - say (%number + 100) - -rule "sing %starting-bottles bottles of beer": - for "n" in (%starting-bottles down through 0): +rule: + sing %starting-bottles bottles of beer +..=: + for %n in (%starting-bottles down through 0): say ".." |\%n if (%n > 0) else "No more"\ \"bottle" if (%n == 1) else "bottles"\ of beer on the wall. |\"" if (%n == 0) else " Take one down, pass it around..."\ @@ -206,7 +260,7 @@ any of [0,0,0,0,1,0,0] # Macros: # The "lua block %" and "lua expr %" macros can be used to write raw lua code: -rule "say the time": +rule: say the time ..=: lua block ".." |io.write("The OS time is: ") |io.write(tostring(os.time()).."\\n") @@ -215,24 +269,23 @@ say ".."|Math expression result is: \lua expr "(1 + 2*3 + 3*4)^2"\ #.. In the lua environment, "vars" can be used to get local variables/function args, and "compiler" can be used to access the compiler, function defs, and other things -rule "square root of %n": - return (lua expr "math.sqrt(vars.n)") +rule: square root of %n ..=: + lua expr "math.sqrt(vars.n)" say ".."|The square root of 2 is \square root of 2\ # Macros can be defined as functions that take unprocessed syntax trees and return lua code # "macro block %" is for defining macros that produce blocks of code, not values -macro block "unless %condition %body": - ".." - |if not (\%condition as lua expr\) then - | \(lua expr "vars.body.value.value") as lua block\ - |end +macro block: unless %condition %body ..=: ".." + |if not (\%condition as lua expr\) then + | \(lua expr "vars.body.value.value") as lua block\ + |end unless (1 > 10): say "Macros work!" say "It looks like a keyword, but there's no magic here!" # and "macro %" is for defining macros that produce an expression -macro "%value as a boolean": - ".."|(not not (\%value as lua expr\)) -macro "yep": "true" +macro: %value as a boolean ..=: ".." + |(not not (\%value as lua expr\)) +macro: yep ..=: "true" diff --git a/nomsu.lua b/nomsu.lua index 443b663..6a4793d 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -203,7 +203,43 @@ do self.defs[invocation] = fn_info end end, + get_invocations_from_definition = function(self, def, vars) + if def.type == "String" or def.type == "List" then + return self:tree_to_value(def, vars) + end + if def.type ~= "Thunk" then + self:error("Trying to get invocations from " .. tostring(def.type) .. ", but expected Thunk.") + end + local invocations = { } + local _list_0 = def.value.value + for _index_0 = 1, #_list_0 do + local statement = _list_0[_index_0] + if statement.value.type ~= "FunctionCall" then + self:error("Invalid statement type: " .. tostring(statement.value.type) .. ", expected FunctionCall") + end + local name_bits = { } + local _list_1 = statement.value.value + for _index_1 = 1, #_list_1 do + local token = _list_1[_index_1] + if token.type == "Word" then + table.insert(name_bits, token.value) + elseif token.value.type == "Var" then + table.insert(name_bits, token.value.src) + else + self:error("Unexpected token type in definition: " .. tostring(token.value.type) .. " (expected Word or Var)") + end + end + table.insert(invocations, table.concat(name_bits, " ")) + end + return invocations + end, get_invocations = function(self, text) + if not text then + self:error("No text provided!") + end + if type(text) == 'function' then + error("Function passed to get_invocations") + end if type(text) == 'string' then text = { text @@ -764,11 +800,21 @@ do if kind == "Expression" then error("Expected to be in statement.") end - return "do\n" .. self:tree_to_value(vars.lua_code, vars) .. "\nend", true + local inner_vars = setmetatable({ }, { + __index = function(_, key) + return "vars[" .. tostring(utils.repr(key, true)) .. "]" + end + }) + return "do\n" .. self:tree_to_value(vars.lua_code, inner_vars) .. "\nend", true end) self:defmacro([[lua expr %lua_code]], function(self, vars, kind) local lua_code = vars.lua_code.value - return self:tree_to_value(vars.lua_code, vars) + local inner_vars = setmetatable({ }, { + __index = function(_, key) + return "vars[" .. tostring(utils.repr(key, true)) .. "]" + end + }) + return self:tree_to_value(vars.lua_code, inner_vars) end) return self:def("run file %filename", function(self, vars) local file = io.open(vars.filename) diff --git a/nomsu.moon b/nomsu.moon index 1a0b370..c8e339e 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -9,6 +9,7 @@ utils = require 'utils' -- better scoping? -- first-class functions -- better error reporting +-- versions of rules with auto-supplied arguments -- type checking? INDENT = " " @@ -137,8 +138,33 @@ class NomsuCompiler fn_info = {:fn, :arg_names, :invocations, :src, is_macro:false} for invocation in *invocations @defs[invocation] = fn_info + + get_invocations_from_definition:(def, vars)=> + if def.type == "String" or def.type == "List" + return @tree_to_value(def, vars) + + if def.type != "Thunk" + @error "Trying to get invocations from #{def.type}, but expected Thunk." + invocations = {} + for statement in *def.value.value + if statement.value.type != "FunctionCall" + @error "Invalid statement type: #{statement.value.type}, expected FunctionCall" + name_bits = {} + for token in *statement.value.value + if token.type == "Word" + table.insert name_bits, token.value + elseif token.value.type == "Var" + table.insert name_bits, token.value.src + else + @error "Unexpected token type in definition: #{token.value.type} (expected Word or Var)" + table.insert invocations, table.concat(name_bits, " ") + return invocations get_invocations:(text)=> + if not text + @error "No text provided!" + if type(text) == 'function' + error "Function passed to get_invocations" if type(text) == 'string' then text = {text} invocations = {} arg_names = {} @@ -559,11 +585,13 @@ class NomsuCompiler @defmacro [[lua block %lua_code]], (vars, kind)=> if kind == "Expression" then error("Expected to be in statement.") - return "do\n"..@tree_to_value(vars.lua_code, vars).."\nend", true + inner_vars = setmetatable({}, {__index:(_,key)-> "vars[#{utils.repr(key,true)}]"}) + return "do\n"..@tree_to_value(vars.lua_code, inner_vars).."\nend", true @defmacro [[lua expr %lua_code]], (vars, kind)=> lua_code = vars.lua_code.value - return @tree_to_value(vars.lua_code, vars) + inner_vars = setmetatable({}, {__index:(_,key)-> "vars[#{utils.repr(key,true)}]"}) + return @tree_to_value(vars.lua_code, inner_vars) @def "run file %filename", (vars)=> file = io.open(vars.filename)