From 371548150618d5b3501f388972077b5d035f7d8a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Thu, 21 Sep 2017 00:10:26 -0700 Subject: Another overhaul, this time pulling all the chunks of the core lib into their own files. --- lib/collections.nom | 139 ++++++++++ lib/control_flow.nom | 193 ++++++++++++++ lib/core.nom | 689 +----------------------------------------------- lib/metaprogramming.nom | 105 ++++++++ lib/moonscript.nom | 30 +++ lib/operators.nom | 125 +++++++++ lib/permissions.nom | 42 +++ lib/testing.nom | 14 + lib/utils.nom | 85 ++++++ nomsu.lua | 56 ++-- nomsu.moon | 54 ++-- utils.lua | 22 +- utils.moon | 16 +- 13 files changed, 833 insertions(+), 737 deletions(-) create mode 100644 lib/collections.nom create mode 100644 lib/control_flow.nom create mode 100644 lib/metaprogramming.nom create mode 100644 lib/moonscript.nom create mode 100644 lib/operators.nom create mode 100644 lib/permissions.nom create mode 100644 lib/testing.nom create mode 100644 lib/utils.nom diff --git a/lib/collections.nom b/lib/collections.nom new file mode 100644 index 0000000..3751d27 --- /dev/null +++ b/lib/collections.nom @@ -0,0 +1,139 @@ +require "lib/metaprogramming.nom" +require "lib/control_flow.nom" +require "lib/operators.nom" + +# List/dict functions: + +# Indexing +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 [..] + %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 [first in %list, first %list] =: + ".."|\%list as lua expr\[1] +macro [last in %list, last %list] =: + ".."|compiler.utils.nth_to_last(\%list as lua expr\, 1) + +# Dict iteration convenience function. This could also be accomplished with: for all (entries in %dict): ... +macro block [for %key -> %value in %dict %body] =: + assert ((%key's "type") == "Var") ".." + |For loop has the wrong type for the key variable. Expected Var, but got: \%key's "type"\ + assert ((%value's "type") == "Var") ".." + |For loop has the wrong type for the value variable. Expected Var, but got: \%value's "type"\ + ".." + |do + | local vars = setmetatable({}, {__index=vars}) + | for k, v in pairs(\%dict as lua expr\) do + | \%key as lua expr\, \%value as lua expr\ = k, v + | \%body as lua block\ + | end + |end + +# Membership testing +rule [%item is in %list, %list contains %item, %list has %item] =: + for %key -> %value in %list: + if (%key == %item): return (yes) + return (no) +macro [%list has key %index, %list has index %index] =: ".." + |(\%list as lua expr\[\%index as lua expr\] ~= nil) + +rule [..] + %item isn't in %list, %item is not in %list + %list doesn't contain %item, %list does not contain %item + %list doesn't have %item, %list does not have %item +..=: + for %key -> %value in %list: + if (%key == %item): return (no) + return (yes) + +macro [..] + %list doesn't have key %index, %list does not have key %index + %list doesn't have index %index, %list does not have index %index +..=: ".."|(\%list as lua expr\[\%index as lua expr\] ~= nil) + +macro [length of %list, size of %list, number of %list, len %list] =: + ".."|#(\%list as lua expr\) + +# Chained lookup +macro [%list ->* %indices] =: + %ret =: %list as lua expr + for %index in %indices: + %ret join=: ".."|[\%index as lua expr\] + %ret + +# Assignment +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 +..=: + assert ((%new_value's "type") == "Thunk") ".." + |Dict assignment operation has the wrong type for the right hand side. + |Expected Thunk, but got \%new_value's "type"\. + |Maybe you used "=" instead of "=:"? + if ((size of (%new_value ->*["value","value"])) > 1): + error ".."|Dict assignment operation has too many values on the right hand side. + ".."|\%list as lua expr\[\%index as lua expr\] = \(%new_value ->*["value","value",1]) as lua expr\ + +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 + +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 + +# List Comprehension +macro [%expression for %var in %iterable] =: + assert ((%var's "type") == "Var") ".." + |List comprehension has the wrong type for the loop variable. Expected Var, but got: \%var's "type"\ + ".." + |(function(game, vars) + | local comprehension = {} + | for i,value in ipairs(\%iterable as lua expr\) do + | \%var 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})) + +# TODO: maybe make a generator/coroutine? + +#.. Dict comprehensions can be accomplished okay by doing: + dict ([new_key using (%it's "key"), new_value using (%it's "value")] for all (entries in %dict)) + or something similar +# TODO: fix compiler bugs +pass diff --git a/lib/control_flow.nom b/lib/control_flow.nom new file mode 100644 index 0000000..e2caa2d --- /dev/null +++ b/lib/control_flow.nom @@ -0,0 +1,193 @@ +require "lib/metaprogramming.nom" +require "lib/operators.nom" +require "lib/utils.nom" + +# 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 + +# Return +macro block [return] =: "return nil" +macro block [return %return-value] =: ".." + |do return \%return-value as lua expr\ end + +macro [do %action] =: ".." + |(\%action as lua expr\)(compiler, setmetatable({}, {__index=vars})) + + +# GOTOs +macro block [-> %label] =: ".." + |::label_\compiler "var_to_lua_identifier" [%label]\:: +macro block [go to %label] =: ".." + |goto label_\compiler "var_to_lua_identifier" [%label]\ + +# Loop control flow +macro block [break] =: "break" +macro block [break for] =: "goto break_for" +macro block [break for-all] =: "goto break_for_all" +macro block [break repeat] =: "goto break_repeat" +macro block [break repeat-until] =: "goto break_repeat_until" +macro block [break repeat-while] =: "goto break_repeat_while" +macro block [break %var, stop getting %var, no more %var] =: ".." + |goto break_\compiler "var_to_lua_identifier" [%var]\ + +macro block [continue] =: "continue" +macro block [continue for] =: "goto continue_for" +macro block [continue for-all] =: "goto continue_for_all" +macro block [continue repeat] =: "goto continue_repeat" +macro block [continue repeat-until] =: "goto continue_repeat_until" +macro block [continue repeat-while] =: "goto continue_repeat_while" +macro block [continue %var, go to next %var, on to the next %var] =: ".." + |goto continue_\compiler "var_to_lua_identifier" [%var]\ +# TODO: add labeled break/continue? + +# While loops +macro block [repeat %body] =: + ".."|do + | while true do + | \(lua expr "vars.body.value") as lua block\ + | ::continue_repeat:: + | end + | ::break_repeat:: + |end +macro block [repeat while %condition %body] =: + ".."|do + | while \%condition as lua expr\ do + | \(lua expr "vars.body.value") as lua block\ + | ::continue_repeat_while:: + | end + | ::break_repeat_while:: + |end +macro block [repeat until %condition %body] =: + ".."|do + | while not (\%condition as lua expr\) do + | \(lua expr "vars.body.value") as lua block\ + | ::continue_repeat_until:: + | end + | ::break_repeat_until:: + |end + +# For loops +macro block [for %var in %iterable %body] =: + %var-type =: lua expr "vars.var.type" + assert (%var-type == "Var") ".." + |For loop has the wrong type for the loop variable. Expected Var, but got: \%var-type\ + ".." + |do + | local vars = setmetatable({}, {__index=vars}) + | for i,value in ipairs(\%iterable as lua expr\) do + | \%var as lua expr\ = value + | \(lua expr "vars.body.value") as lua block\ + | ::continue_for:: + | ::continue_\compiler "var_to_lua_identifier" [%var]\:: + | end + | ::break_for:: + | ::break_\compiler "var_to_lua_identifier" [%var]\:: + |end + +macro block [for all %iterable %body] =: + ".."|do + | local vars = setmetatable({}, {__index=vars}) + | for i,value in ipairs(\%iterable as lua expr\) do + | vars.it = value + | \(lua expr "vars.body.value") as lua block\ + | ::continue_for_all:: + | end + | ::break_for_all:: + |end + +# 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" + assert ((lua expr "vars['func-call'].type") != "FunctionCall") ".." + |Invalid format for 'when' statement. Only '?' blocks are allowed. + %tokens =: lua expr "vars['func-call'].value" + %q =: lua expr "vars.tokens[1]" + assert (((lua expr "vars.q.type") != "Word") or ((lua expr "vars.q.value") != "?")) ".." + |Invalid format for 'when' statement. Lines must begin with '?' + %thunk =: lua expr "vars.tokens[#vars.tokens]" + assert ((lua expr "vars.thunk.type") != "Thunk") ".." + |Invalid format for 'when' statement. Lines must have a body. + %condition_bits =: [] + for %i in (2 up to (lua expr "#vars.tokens")): + lua block "table.insert(vars['condition_bits'], vars.tokens[vars.i])" + + if (lua expr "#vars.condition_bits == 0"): + %result join=: ".." + | + |do + | local ret + | \(lua expr "vars.thunk.value") as lua block\ + | return ret + |end + ..else: + if (lua expr "#vars.condition_bits == 1 and vars.condition_bits[1].type ~= 'Word'"): + %condition =: lua expr "vars.condition_bits[1]" + ..else: + %condition =: dict [..] + ["type",lua expr "vars['func-call'].type"] + ["src",lua expr "vars['func-call'].src"] + ["value", %condition_bits] + %result join=: ".." + | + |if \%condition as lua expr\ then + | local ret + | \(lua expr "vars.thunk.value") as lua block\ + | return ret + |end + + %result + +# Switch statement +macro block [when %branch-value %body] =: + %result =: ".."|local branch_value = \%branch-value as lua expr\ + for %statement in (lua expr "vars.body.value.value"): + %func-call =: lua expr "vars.statement.value" + assert ((lua expr "vars['func-call'].type") != "FunctionCall") ".." + |Invalid format for 'when' statement. Only == blocks are allowed. + %tokens =: lua expr "vars['func-call'].value" + %eq =: lua expr "vars.tokens[1]" + assert (((lua expr "vars.eq.type") != "Word") or ((lua expr "vars.eq.value") != "==")) ".." + |Invalid format for 'when' statement. Lines must begin with '==' + %thunk =: lua expr "vars.tokens[#vars.tokens]" + assert ((lua expr "vars.thunk.type") != "Thunk") ".." + |Invalid format for 'when' statement. Lines must have a body. + %condition_bits =: [] + for %i in (2 up to (lua expr "#vars.tokens")): + lua block "table.insert(vars.condition_bits, vars.tokens[vars.i])" + if (lua expr "#vars.condition_bits == 0"): + %result join=: ".." + | + |do + | local ret + | \(lua expr "vars.thunk.value") as lua block\ + | return ret + |end + ..else: + if (lua expr "#vars.condition_bits == 1 and vars.condition_bits[1].type ~= 'Word'"): + %condition =: (lua expr "vars.condition_bits[1]") + ..else: + %condition =: dict [..] + ["type",lua expr "vars['func-call'].type"] + ["src",lua expr "vars['func-call'].src"] + ["value", %condition_bits] + %result join=: ".." + | + |if compiler.utils.equivalent(branch_condition, \%condition as lua expr\) then + | local ret + | \(lua expr "vars.thunk.value") as lua block\ + | return ret + |end + %result + diff --git a/lib/core.nom b/lib/core.nom index f0cad0d..f00bd22 100644 --- a/lib/core.nom +++ b/lib/core.nom @@ -1,683 +1,6 @@ -# 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. Only '?' blocks are allowed." - %tokens =: lua expr "vars['func-call'].value" - %q =: lua expr "vars.tokens[1]" - if (((lua expr "vars.q.type") != "Word") or ((lua expr "vars.q.value") != "?")): - error "Invalid format for 'when' statement. Lines must begin with '?'" - %thunk =: lua expr "vars.tokens[#vars.tokens]" - if ((lua expr "vars.thunk.type") != "Thunk"): - error "Invalid format for 'when' statement. Lines must have a body." - %condition_bits =: [] - for %i in (2 up to (lua expr "#vars.tokens")): - lua block "table.insert(vars['condition_bits'], vars.tokens[vars.i])" - - if (lua expr "#vars.condition_bits == 0"): - %result concat=: ".." - | - |do - | local ret - | \(lua expr "vars.thunk.value") as lua block\ - | return ret - |end - ..else: - if (lua expr "#vars.condition_bits == 1 and vars.condition_bits[1].type ~= 'Word'"): - %condition =: lua expr "vars.condition_bits[1]" - ..else: - %condition =: dict [..] - ["type",lua expr "vars['func-call'].type"] - ["src",lua expr "vars['func-call'].src"] - ["value", %condition_bits] - %result concat=: ".." - | - |if \%condition as lua expr\ then - | local ret - | \(lua expr "vars.thunk.value") as lua block\ - | return ret - |end - - %result - -# Switch statement -macro block [when %branch-value %body] =: - %result =: ".."|local branch_value = \%branch-value as lua expr\ - 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. Only == blocks are allowed." - %tokens =: lua expr "vars['func-call'].value" - %eq =: lua expr "vars.tokens[1]" - if (((lua expr "vars.eq.type") != "Word") or ((lua expr "vars.eq.value") != "==")): - error "Invalid format for 'when' statement. Lines must begin with '=='" - %thunk =: lua expr "vars.tokens[#vars.tokens]" - if ((lua expr "vars.thunk.type") != "Thunk"): - error "Invalid format for 'when' statement. Lines must have a body." - %condition_bits =: [] - for %i in (2 up to (lua expr "#vars.tokens")): - lua block "table.insert(vars.condition_bits, vars.tokens[vars.i])" - if (lua expr "#vars.condition_bits == 0"): - %result concat=: ".." - | - |do - | local ret - | \(lua expr "vars.thunk.value") as lua block\ - | return ret - |end - ..else: - if (lua expr "#vars.condition_bits == 1 and vars.condition_bits[1].type ~= 'Word'"): - %condition =: (lua expr "vars.condition_bits[1]") - ..else: - %condition =: dict [..] - ["type",lua expr "vars['func-call'].type"] - ["src",lua expr "vars['func-call'].src"] - ["value", %condition_bits] - %result concat=: ".." - | - |if compiler.utils.equivalent(branch_condition, \%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: "" - +require "lib/metaprogramming.nom" +require "lib/utils.nom" +require "lib/operators.nom" +require "lib/control_flow.nom" +require "lib/collections.nom" +require "lib/permissions.nom" diff --git a/lib/metaprogramming.nom b/lib/metaprogramming.nom new file mode 100644 index 0000000..d2f8fb6 --- /dev/null +++ b/lib/metaprogramming.nom @@ -0,0 +1,105 @@ + +#.. + This File contains rules for making rules and macros and some helper functions to make + that easier. + +# Macros: + +# macro block [macro block %spec = %user_macro] =: .. +lua block ".." + |compiler:defmacro("macro block %spec = %user_macro", (function(compiler, vars, kind) + # Do a minimal amount of pre-processing (get the spec and the source) + | local spec = compiler:get_invocations_from_definition(vars.spec, vars) + | spec = compiler.utils.repr(spec) + | local src = compiler.utils.repr(vars.user_macro.src) + | local user_macro = compiler:tree_to_lua(vars.user_macro) + # Then produce a block of code that creates the macro at runtime + | local lua = [[ + |compiler:defmacro(%s, (function(compiler, vars, kind) + | if kind == "Expression" then + | compiler:error("Macro "..%s.." was defined to be a block, but is being used as an expression") + | end + | local user_macro = %s + | return ("do\\n"..user_macro(compiler, vars).."\\nend"), true + |end), %s) + |]] + | lua = lua:format(spec, compiler.utils.repr(spec), user_macro, src) + | return lua, true + |end), "N/A") + +macro block [macro %spec = %user_macro] =: + ".."|compiler:defmacro( + | \lua expr "compiler:get_invocations_from_definition(vars.spec, vars)"\, + | \lua expr "compiler:tree_to_lua(vars.user_macro, 'Expression')"\, + | \lua expr "compiler.utils.repr(vars.user_macro.src)"\) + +macro [compiler] =: "compiler" +macro [compiler's %key] =: ".."|compiler[\%key as lua expr\] +macro [compiler %method %args] =: + lua block ".." + |local args = {} + |for i,arg in ipairs(vars.args.value) do + | args[i] = compiler:tree_to_lua(arg) + |end + |return "compiler:"..compiler:tree_to_value(vars.method, vars).."("..table.concat(args, ", ")..")" +macro [compiler utils %method %args] =: + lua block ".." + |local args = {} + |for i,arg in ipairs(vars.args.value) do + | args[i] = compiler:tree_to_lua(arg) + |end + |return "compiler.utils."..compiler:tree_to_value(vars.method, vars).."("..table.concat(args, ", ")..")" + +# Macro that lets you make new rules +#.. + This is a macro instead of a rule because it needs to pre-empt processing the list of + invocations and convert it into a list of strings (rather than call a function that + is currently in the middle of being defined). Being a macro also allows us to snatch + the source code and store that +macro block [rule %spec = %body] =: ".." + |compiler:def( + | \compiler utils "repr" [compiler "get_invocations_from_definition" [%spec, lua expr "vars"]]\, + | \compiler "tree_to_lua" [%body, lua expr "vars"]\, + | \compiler utils "repr" [lua expr "vars.body.src"]\) + +# Get the source code for a function +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)) + |else + | compiler:writeln("rule "..compiler.utils.repr(fn_info.invocations).." ="..(fn_info.src or ":\\n ")) + |end + + +# Macro helper functions +rule [%tree as value] =: + lua expr "compiler:tree_to_value(vars.tree, vars)" + +rule [%tree as lua block] =: + lua expr "compiler:tree_to_lua(vars.tree, 'Statement')" + +rule [%tree as lua expr] =: + lua expr "compiler:tree_to_lua(vars.tree, 'Expression')" + +# Compiler tools +rule [eval %code, run %code] =: compiler "run" [%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") + | return chunk1..chunk2.."\\n" + |else + | return vars.tree.src:match(":%s*(%S.*)").."\\n" + |end + +macro [source code %body] =: + compiler utils "repr" [compiler "call" ["source code from tree %", %body]] + +macro [parse tree %code] =: + compiler utils "repr" [compiler "stringify_tree" [lua expr "vars.code.value"]] + diff --git a/lib/moonscript.nom b/lib/moonscript.nom new file mode 100644 index 0000000..1b7bf2d --- /dev/null +++ b/lib/moonscript.nom @@ -0,0 +1,30 @@ +require "lib/metaprogramming.nom" + +# 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)" diff --git a/lib/operators.nom b/lib/operators.nom new file mode 100644 index 0000000..f6fea36 --- /dev/null +++ b/lib/operators.nom @@ -0,0 +1,125 @@ +require "lib/metaprogramming.nom" + +# Literals +macro [true, yes] =: "true" +macro [false, no] =: "false" +macro [nil, null] =: "nil" +macro block [nop, pass] =: "" + +# Ternary operator +macro [%if_expr if %condition else %else_expr] =: + pass # TODO: Fix compiler bug that doesn't parse right here + #.. Note: this uses a function instead of (condition and if_expr or else_expr) + because that breaks if %if_expr is falsey. + ".."|(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) + +# Variable assignment operator, and += type versions +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.."\\nMaybe you forgot a percent sign on the variable name?") + | 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 + | compiler:error("Assignment operation should not have more than one value on the right hand side.") + | end + | return callback(compiler:tree_to_lua(vars.var, "Expression"), + | compiler:tree_to_lua(vars.rhs.value.value[1].value, "Expression")), true + | 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 join= %rhs", helper(function(var,result) return var.." = "..var.." .. "..result end)) + |compiler:defmacro("%var mod= %rhs", helper(function(var,result) return var.." = "..var.." % "..result end)) + +# Binary Operators +lua block ".." + |local binops = {"+","-","*","/","<","<=",">",">=","^",{"===","=="},{"!==","~="},"and","or",{"mod","%"}} + |for _,op in ipairs(binops) do + | local nomsu_alias = op + | if type(op) == 'table' then + | nomsu_alias, op = unpack(op) + | end + | compiler:defmacro("%a "..nomsu_alias.." %b", (function(compiler, vars, kind) + | return "("..compiler:tree_to_lua(vars.a).." "..op.." "..compiler:tree_to_lua(vars.b)..")" + | end), [[".."|(\\%a as lua expr\\ ]]..op..[[ \\%b as lua expr\\)]]) + |end +# == and != do equivalence checking, rather than identity checking +macro [%a == %b] =: ".."|compiler.utils.equivalent(\%a as lua expr\, \%b as lua expr\) +macro [%a != %b] =: ".."|(not compiler.utils.equivalent(\%a as lua expr\, \%b as lua expr\)) + +# Commutative Operators defined for up to 10 operands +lua block ".." + |local comops = {"+","*","and","or"} + |for _,_op in ipairs(comops) do + | local op = _op + | local spec = "%1 "..op.." %2" + | for i=3,10 do + | spec = spec .. " %"..tostring(i) + | compiler:defmacro(spec, (function(compiler, vars, kind) + | local bits = {} + | for _,v in ipairs(vars) do + | table.insert(bits, compiler:tree_to_lua(v)) + | end + | return "("..table.concat(bits, " "..op.." ")..")" + | end)) + | end + |end + +# Chained compairsions (e.g. x < y <= z < w) are defined up to 10 operands +lua block ".." + |for _,chainers in ipairs{{"<","<="},{">",">="}} do + | local function recurse(chain) + # The 1-op versions are already more efficiently defined, and a 0-op version doesnt make sense + | if #chain >= 2 then + | local spec = "%1" + | for i,op in ipairs(chain) do + | spec = spec .. " "..op.." %"..tostring(i+1) + | end + # Chained comparisons need to be functions to avoid re-evaluating their arguments :\ + | compiler:def(spec, function(compiler, vars) + | for i,op in ipairs(chain) do + | local a, b, result = vars[i], vars[i+1] + | if op == "<" then result = a < b + | elseif op == "<=" then result = a <= b + | elseif op == ">" then result = a > b + | elseif op == ">=" then result = a >= b end + # Short circuit + | if not result then return false end + | end + | end) + | end + # 9 operators == 10 operands, so don't continue any further + | if #chain >= 9 then return end + | for _,c in ipairs(chainers) do + | table.insert(chain, c) + | recurse(chain) + | table.remove(chain) + | end + | end + | recurse({}) + |end + +# Unary operators +macro [- %a] =: ".."|-(\%a as lua expr\) +macro [not %a] =: ".."|not (\%a as lua expr\) diff --git a/lib/permissions.nom b/lib/permissions.nom new file mode 100644 index 0000000..4b6c428 --- /dev/null +++ b/lib/permissions.nom @@ -0,0 +1,42 @@ +require "lib/metaprogramming.nom" +require "lib/control_flow.nom" +require "lib/operators.nom" +require "lib/collections.nom" + +# Permission functions +rule [restrict %rules to within %elite-rules] =: + %rules =: compiler "get_invocations" [%rules] + %elite-rules =: compiler "get_invocations" [%elite-rules] + for all (flatten [%elite-rules, %rules]): + assert ((compiler's "defs") has %it) ".."|Undefined function: \%it\ + for all %rules: + assert (not (compiler "check_permission" [%it])) ".." + |You do not have permission to restrict permissions for function: \%it\ + ((compiler's "defs")'s %it)'s "whiteset" =: dict (..) + [%it, (yes)] for %it in %elite-rules + +rule [allow %elite-rules to use %rules] =: + %rules =: compiler "get_invocations" [%rules] + %elite-rules =: compiler "get_invocations" [%elite-rules] + for all (flatten [%elite-rules, %rules]): + assert ((compiler's "defs") has %it) ".."|Undefined function: \%it\ + for %fn in %rules: + assert (not (compiler "check_permission" [%fn])) ".." + |You do not have permission to grant permissions for function: \%fn\ + %whiteset =: ((compiler's "defs")'s %fn)'s "whiteset" + if (not %whiteset): on to the next %fn + for all %elite-rules: %whiteset's %it =: yes + +rule [forbid %pleb-rules to use %rules] =: + %rules =: compiler "get_invocations" [%rules] + %pleb-rules =: compiler "get_invocations" [%pleb-rules] + for all (flatten [%pleb-rules, %used]): + assert ((compiler's "defs") has %it) ".."|Undefined function: \%it\ + for all %rules: + assert (not (compiler "check_permission" [%it])) ".." + |You do not have permission to grant permissions for function: \%it\ + %whiteset =: ((compiler's "defs")'s %it)'s "whiteset" + assert %whiteset ".." + |Cannot individually restrict permissions for \%it\ because it is currently + |available to everyone. Perhaps you meant to use "restrict % to within %" instead? + for all %pleb-rules: %whiteset's %it =: nil diff --git a/lib/testing.nom b/lib/testing.nom new file mode 100644 index 0000000..48b311f --- /dev/null +++ b/lib/testing.nom @@ -0,0 +1,14 @@ +require "lib/metaprogramming.nom" + +# For unit testing +macro block [test %code yields %expected] =: + %generated =: lua expr "compiler.utils.repr(compiler:stringify_tree(vars.code.value))" + %expected =: %expected as lua expr + if (%generated != %expected): + say "Test failed!" + say "Expected:" + say %expected + say "But got:" + say %generated + error! + return "" diff --git a/lib/utils.nom b/lib/utils.nom new file mode 100644 index 0000000..57b8dfc --- /dev/null +++ b/lib/utils.nom @@ -0,0 +1,85 @@ +require "lib/metaprogramming.nom" + +# Error functions +rule [error!, panic!, fail!, abort!] =: + compiler "error"[] +rule [error %msg] =: + compiler "error"[%msg] +macro block [assert %condition] =: ".." + |if not (\%condition as lua expr\) then + | compiler:error() + |end +macro block [assert %condition %msg] =: ".." + |if not (\%condition as lua expr\) then + | compiler:error(\%msg as lua expr\) + |end + +macro block [show generated lua %block] =: ".." + |compiler:writeln(\lua expr "compiler.utils.repr(compiler:tree_to_lua(vars.block.value, 'Statement'))"\) + + +# String functions +rule [join %strs] =: + lua block ".." + |local str_bits = {} + |for i,bit in ipairs(vars.strs) do str_bits[i] = compiler.utils.repr_if_not_string(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_if_not_string(bit) end + |return table.concat(str_bits, vars.glue) + +macro [capitalize %str, %str capitalized] =: ".." + |(\%str as lua expr\):gsub("%l", string.upper, 1) + +macro [repr %obj] =: + ".."|compiler.utils.repr(\%obj as lua expr\) + +macro [%obj as string] =: + ".."|compiler.utils.repr_if_not_string(\%obj as lua expr\) + +macro [say %str] =: + ".."|compiler:writeln(compiler.utils.repr_if_not_string(\%str as lua expr\)) + +# Number ranges +macro [%start up to %stop] =: ".." + |compiler.utils.range(\%start as lua expr\, \%stop as lua expr\-1) +macro [%start thru %stop, %start through %stop] =: ".." + |compiler.utils.range(\%start as lua expr\, \%stop as lua expr\) +macro [%start down to %stop] =: ".." + |compiler.utils.range(\%start as lua expr\, \%stop as lua expr\+1,-1) +macro [%start down thru %stop, %start down through %stop] =: ".." + |compiler.utils.range(\%start as lua expr\, \%stop as lua expr\,-1) +macro [%start up to %stop via %step] =: ".." + |compiler.utils.range(\%start as lua expr\,\%stop as lua expr\-1,vars.step) +macro [%start thru %stop via %step, %start through %stop via %step] =: ".." + |compiler.utils.range(\%start as lua expr\,\%stop as lua expr\,vars.step) +macro [%start down to %stop via %step] =: ".." + |compiler.utils.range(\%start as lua expr\,\%stop as lua expr\+1,-vars.step) +macro [%start down thru %stop via %step, %start down through %stop via %step] =: ".." + |compiler.utils.range(\%start as lua expr\,\%stop as lua expr\,-vars.step) + +# Common utility functions +macro [random number, random, rand] =: "math.random()" +macro [random int %n, random integer %n, randint %n] =: ".."|math.random(\%n as lua expr\) +macro [random from %low to %high, random number from %low to %high, rand %low %high] =: ".." + |math.random(\%low as lua expr\, \%high as lua expr\) +rule [random choice from %elements, random choice %elements, random %elements] =: + lua expr ".."|vars.elements[math.random(#vars.elements)] +macro [sum of %items, sum %items] =: ".."|compiler.utils.sum(\%items as lua expr\) +macro [product of %items, product %items] =: ".."|compiler.utils.product(\%items as lua expr\) +macro [all of %items] =: ".."|compiler.utils.all(\%items as lua expr\) +macro [any of %items] =: ".."|compiler.utils.any(\%items as lua expr\) +# This is a rule, not a macro so we can use vars.items twice without running it twice. +rule [avg of %items, average of %items] =: + lua expr ".."|(compiler.utils.sum(vars.items)/#vars.items) +macro [min of %items, smallest of %items, lowest of %items] =: + ".."|compiler.utils.min(\%items as lua expr\) +macro [max of %items, biggest of %items, largest of %items, highest of %items] =: + ".."|compiler.utils.min(\%items as lua expr\) +macro [min of %items with respect to %keys] =: + ".."|compiler.utils.min(\%items as lua expr\, \%keys as lua expr\) +macro [max of %items with respect to %keys] =: + ".."|compiler.utils.max(\%items as lua expr\, \%keys as lua expr\) diff --git a/nomsu.lua b/nomsu.lua index c2b1004..108cbd3 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -38,8 +38,11 @@ do if self.debug then self:writeln("Calling " .. tostring(fn_name) .. " with args: " .. tostring(utils.repr(args))) end - local ret = fn(self, args) + local ok, ret = pcall(fn, self, args) table.remove(self.callstack) + if not ok then + error(ret) + end return ret end, check_permission = function(self, fn_name) @@ -81,7 +84,6 @@ do return self:tree_to_value(def, vars) end if def.type ~= "List" then - error("DEF IS: " .. tostring(utils.repr(def))) self:error("Trying to get invocations from " .. tostring(def.type) .. ", but expected List or String.") end local invocations = { } @@ -191,7 +193,7 @@ do local _exp_0 = type(obj) if "function" == _exp_0 then error("Function serialization is not yet implemented.") - return "assert(load(" .. utils.repr(string.dump(obj), true) .. "))" + return "assert(load(" .. utils.repr(string.dump(obj)) .. "))" elseif "table" == _exp_0 then if utils.is_list(obj) then return "{" .. tostring(table.concat((function() @@ -216,9 +218,9 @@ do end)(), ", ")) .. "}" end elseif "number" == _exp_0 then - return utils.repr(obj, true) + return utils.repr(obj) elseif "string" == _exp_0 then - return utils.repr(obj, true) + return utils.repr(obj) else return error("Serialization not implemented for: " .. tostring(type(obj))) end @@ -441,14 +443,22 @@ do local _list_0 = tree.value.body.value for _index_0 = 1, #_list_0 do local statement = _list_0[_index_0] - local code = to_lua(statement, "Statement") + local ok, code = pcall(to_lua, statement, "Statement") + if not ok then + self:writeln("Error occurred in statement:\n" .. tostring(statement.src)) + error(code) + end local lua_code = "\n return (function(compiler, vars)\n" .. tostring(code) .. "\nend)" local lua_thunk, err = load(lua_code) if not lua_thunk then error("Failed to compile generated code:\n" .. tostring(code) .. "\n\n" .. tostring(err) .. "\n\nProduced by statement:\n" .. tostring(utils.repr(statement))) end local value = lua_thunk() - return_value = value(self, vars) + ok, return_value = pcall(value, self, vars) + if not ok then + self:writeln("Error occurred in statement:\n" .. tostring(statement.src)) + error(return_value) + end add(code) end add([[ return ret @@ -497,7 +507,7 @@ do end args = _accum_0 end - table.insert(args, 1, utils.repr(name, true)) + table.insert(args, 1, utils.repr(name)) add(self.__class:comma_separated_items("compiler:call(", args, ")")) end elseif "String" == _exp_0 then @@ -513,7 +523,7 @@ do local unescaped = tree.value:gsub("\\(.)", (function(c) return escapes[c] or c end)) - add(utils.repr(unescaped, true)) + add(utils.repr(unescaped)) elseif "Longstring" == _exp_0 then local concat_parts = { } local string_buffer = "" @@ -527,15 +537,15 @@ do string_buffer = string_buffer .. bit:gsub("\\\\", "\\") else if string_buffer ~= "" then - table.insert(concat_parts, utils.repr(string_buffer, true)) + table.insert(concat_parts, utils.repr(string_buffer)) string_buffer = "" end - table.insert(concat_parts, "compiler.utils.repr(" .. tostring(to_lua(bit)) .. ")") + table.insert(concat_parts, "compiler.utils.repr_if_not_string(" .. tostring(to_lua(bit)) .. ")") end end end if string_buffer ~= "" then - table.insert(concat_parts, utils.repr(string_buffer, true)) + table.insert(concat_parts, utils.repr(string_buffer)) end if #concat_parts == 0 then add("''") @@ -565,7 +575,7 @@ do end)(), "}")) end elseif "Var" == _exp_0 then - add("vars[" .. tostring(utils.repr(tree.value, true)) .. "]") + add("vars[" .. tostring(utils.repr(tree.value)) .. "]") else self:error("Unknown/unimplemented thingy: " .. tostring(tree.type)) end @@ -588,6 +598,16 @@ do end return table.concat(name_bits, " ") end, + var_to_lua_identifier = function(self, var) + if var.type ~= "Var" then + self:error("Tried to convert something that wasn't a Var into a lua identifier: it was not a Var, it was: " .. label.type) + end + local identifier = "var_" + for i = 1, #var.value do + identifier = identifier .. ("%x"):format(string.byte(var.value, i)) + end + return identifier + end, run_macro = function(self, tree, kind) if kind == nil then kind = "Expression" @@ -687,9 +707,9 @@ do end end elseif "String" == _exp_0 then - coroutine.yield(ind(utils.repr(tree.value, true))) + coroutine.yield(ind(utils.repr(tree.value))) elseif "Longstring" == _exp_0 then - coroutine.yield(ind(utils.repr(tree.value, true))) + coroutine.yield(ind(utils.repr(tree.value))) elseif "Number" == _exp_0 then coroutine.yield(ind(tree.value)) elseif "List" == _exp_0 then @@ -791,7 +811,7 @@ do end local inner_vars = setmetatable({ }, { __index = function(_, key) - return "vars[" .. tostring(utils.repr(key, true)) .. "]" + return "vars[" .. tostring(utils.repr(key)) .. "]" end }) return "do\n" .. self:tree_to_value(vars.lua_code, inner_vars) .. "\nend", true @@ -800,7 +820,7 @@ do local lua_code = vars.lua_code.value local inner_vars = setmetatable({ }, { __index = function(_, key) - return "vars[" .. tostring(utils.repr(key, true)) .. "]" + return "vars[" .. tostring(utils.repr(key)) .. "]" end }) return self:tree_to_value(vars.lua_code, inner_vars) @@ -920,7 +940,7 @@ elseif arg then return c:run(buff) end) if ok and ret ~= nil then - print("= " .. utils.repr(ret, true)) + print("= " .. utils.repr(ret)) end end end diff --git a/nomsu.moon b/nomsu.moon index 7f623cf..549684f 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -7,8 +7,9 @@ utils = require 'utils' -- improve indentation of generated lua code -- provide way to run precompiled nomsu -> lua code -- better scoping? --- first-class functions +-- first-class rules -- better error reporting +-- add line numbers of function calls -- versions of rules with auto-supplied arguments -- type checking? @@ -44,8 +45,10 @@ class NomsuCompiler args = {name, select(i,...) for i,name in ipairs(arg_names[fn_name])} if @debug @writeln "Calling #{fn_name} with args: #{utils.repr(args)}" - ret = fn(self, args) + ok,ret = pcall(fn, self, args) table.remove @callstack + if not ok + error(ret) return ret check_permission: (fn_name)=> @@ -71,7 +74,6 @@ class NomsuCompiler return @tree_to_value(def, vars) if def.type != "List" - error "DEF IS: #{utils.repr(def)}" @error "Trying to get invocations from #{def.type}, but expected List or String." invocations = {} @@ -134,16 +136,16 @@ class NomsuCompiler switch type(obj) when "function" error("Function serialization is not yet implemented.") - "assert(load("..utils.repr(string.dump(obj), true).."))" + "assert(load("..utils.repr(string.dump(obj)).."))" when "table" if utils.is_list obj "{#{table.concat([@serialize(i) for i in *obj], ", ")}}" else "{#{table.concat(["[#{@serialize(k)}]= #{@serialize(v)}" for k,v in pairs(obj)], ", ")}}" when "number" - utils.repr(obj, true) + utils.repr(obj) when "string" - utils.repr(obj, true) + utils.repr(obj) else error "Serialization not implemented for: #{type(obj)}" @@ -329,7 +331,10 @@ class NomsuCompiler local ret]] vars = {} for statement in *tree.value.body.value - code = to_lua(statement, "Statement") + ok,code = pcall(to_lua, statement, "Statement") + if not ok + @writeln "Error occurred in statement:\n#{statement.src}" + error(code) -- Run the fuckers as we go lua_code = " return (function(compiler, vars)\n#{code}\nend)" @@ -337,7 +342,10 @@ class NomsuCompiler if not lua_thunk error("Failed to compile generated code:\n#{code}\n\n#{err}\n\nProduced by statement:\n#{utils.repr(statement)}") value = lua_thunk! - return_value = value(self, vars) + ok,return_value = pcall(value, self, vars) + if not ok + @writeln "Error occurred in statement:\n#{statement.src}" + error(return_value) add code add [[ return ret @@ -375,13 +383,13 @@ class NomsuCompiler add @run_macro(tree, "Expression") else args = [to_lua(a) for a in *tree.value when a.type != "Word"] - table.insert args, 1, utils.repr(name, true) + table.insert args, 1, utils.repr(name) add @@comma_separated_items("compiler:call(", args, ")") when "String" escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r" unescaped = tree.value\gsub("\\(.)", ((c)-> escapes[c] or c)) - add utils.repr(unescaped, true) + add utils.repr(unescaped) when "Longstring" concat_parts = {} @@ -393,12 +401,12 @@ class NomsuCompiler string_buffer ..= bit\gsub("\\\\","\\") else if string_buffer ~= "" - table.insert concat_parts, utils.repr(string_buffer, true) + table.insert concat_parts, utils.repr(string_buffer) string_buffer = "" - table.insert concat_parts, "compiler.utils.repr(#{to_lua(bit)})" + table.insert concat_parts, "compiler.utils.repr_if_not_string(#{to_lua(bit)})" if string_buffer ~= "" - table.insert concat_parts, utils.repr(string_buffer, true) + table.insert concat_parts, utils.repr(string_buffer) if #concat_parts == 0 add "''" @@ -419,7 +427,7 @@ class NomsuCompiler add @@comma_separated_items("{", [to_lua(item) for item in *tree.value], "}") when "Var" - add "vars[#{utils.repr(tree.value,true)}]" + add "vars[#{utils.repr(tree.value)}]" else @error("Unknown/unimplemented thingy: #{tree.type}") @@ -450,6 +458,14 @@ class NomsuCompiler for token in *tree.value table.insert name_bits, if token.type == "Word" then token.value else "%" table.concat(name_bits, " ") + + var_to_lua_identifier: (var)=> + if var.type != "Var" + @error("Tried to convert something that wasn't a Var into a lua identifier: it was not a Var, it was: "..label.type) + identifier = "var_" + for i=1,#var.value + identifier ..= ("%x")\format(string.byte(var.value, i)) + return identifier run_macro: (tree, kind="Expression")=> name = @fn_name_from_tree(tree) @@ -502,11 +518,11 @@ class NomsuCompiler when "String" -- TODO: Better implement - coroutine.yield(ind(utils.repr(tree.value, true))) + coroutine.yield(ind(utils.repr(tree.value))) when "Longstring" -- TODO: Better implement - coroutine.yield(ind(utils.repr(tree.value, true))) + coroutine.yield(ind(utils.repr(tree.value))) when "Number" coroutine.yield(ind(tree.value)) @@ -588,12 +604,12 @@ class NomsuCompiler @defmacro [[lua block %lua_code]], (vars, kind)=> if kind == "Expression" then error("Expected to be in statement.") - inner_vars = setmetatable({}, {__index:(_,key)-> "vars[#{utils.repr(key,true)}]"}) + inner_vars = setmetatable({}, {__index:(_,key)-> "vars[#{utils.repr(key)}]"}) 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 - inner_vars = setmetatable({}, {__index:(_,key)-> "vars[#{utils.repr(key,true)}]"}) + inner_vars = setmetatable({}, {__index:(_,key)-> "vars[#{utils.repr(key)}]"}) return @tree_to_value(vars.lua_code, inner_vars) @def "require %filename", (vars)=> @@ -655,6 +671,6 @@ elseif arg break ok, ret = pcall(-> c\run(buff)) if ok and ret != nil - print "= "..utils.repr(ret, true) + print "= "..utils.repr(ret) return NomsuCompiler diff --git a/utils.lua b/utils.lua index b50a88f..ddd2c3c 100644 --- a/utils.lua +++ b/utils.lua @@ -10,10 +10,7 @@ utils = { end return true end, - repr = function(x, add_quotes) - if add_quotes == nil then - add_quotes = false - end + repr = function(x) local _exp_0 = type(x) if 'table' == _exp_0 then local mt = getmetatable(x) @@ -25,7 +22,7 @@ utils = { local _len_0 = 1 for _index_0 = 1, #x do local i = x[_index_0] - _accum_0[_len_0] = utils.repr(i, true) + _accum_0[_len_0] = utils.repr(i) _len_0 = _len_0 + 1 end return _accum_0 @@ -35,18 +32,16 @@ utils = { local _accum_0 = { } local _len_0 = 1 for k, v in pairs(x) do - _accum_0[_len_0] = "[" .. tostring(utils.repr(k, true)) .. "]= " .. tostring(utils.repr(v, true)) + _accum_0[_len_0] = "[" .. tostring(utils.repr(k)) .. "]= " .. tostring(utils.repr(v)) _len_0 = _len_0 + 1 end return _accum_0 end)(), ", ")) .. "}" end elseif 'string' == _exp_0 then - if not add_quotes then - return x - elseif not x:find([["]]) and not x:find("\n") then + if not x:find([["]]) and not x:find("\n") and not x:find("\\") then return "\"" .. x .. "\"" - elseif not x:find([[']]) and not x:find("\n") then + elseif not x:find([[']]) and not x:find("\n") and not x:find("\\") then return "\'" .. x .. "\'" else for i = 0, math.huge do @@ -64,6 +59,13 @@ utils = { return tostring(x) end end, + repr_if_not_string = function(x) + if type(x) == 'string' then + return x + else + return utils.repr(x) + end + end, split = function(str, sep) if sep == nil then sep = "%s" diff --git a/utils.moon b/utils.moon index b46bf44..3b3573c 100644 --- a/utils.moon +++ b/utils.moon @@ -7,22 +7,20 @@ utils = { i += 1 return true - repr: (x, add_quotes=false)-> + repr: (x)-> switch type(x) when 'table' mt = getmetatable(x) if mt and mt.__tostring mt.__tostring(x) elseif utils.is_list x - "{#{table.concat([utils.repr(i, true) for i in *x], ", ")}}" + "{#{table.concat([utils.repr(i) for i in *x], ", ")}}" else - "{#{table.concat(["[#{utils.repr(k, true)}]= #{utils.repr(v, true)}" for k,v in pairs x], ", ")}}" + "{#{table.concat(["[#{utils.repr(k)}]= #{utils.repr(v)}" for k,v in pairs x], ", ")}}" when 'string' - if not add_quotes - x - elseif not x\find[["]] and not x\find"\n" + if not x\find[["]] and not x\find"\n" and not x\find"\\" "\""..x.."\"" - elseif not x\find[[']] and not x\find"\n" + elseif not x\find[[']] and not x\find"\n" and not x\find"\\" "\'"..x.."\'" else for i=0,math.huge @@ -36,6 +34,10 @@ utils = { else tostring(x) + repr_if_not_string: (x)-> + if type(x) == 'string' then x + else utils.repr(x) + split: (str, sep="%s")-> [chunk for chunk in str\gmatch("[^#{sep}]+")] -- cgit v1.2.3