From 5fef795cda854647e1ab2048299e71565621116a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Mon, 11 Sep 2017 13:05:25 -0700 Subject: [PATCH] probably working after refactor? --- core.moon | 248 +++++++++++++++++++++-------------------------------- nomic.moon | 191 +++++++++++++++++++++++------------------ utils.moon | 20 +++++ 3 files changed, 223 insertions(+), 236 deletions(-) diff --git a/core.moon b/core.moon index 1420898..a32a62a 100755 --- a/core.moon +++ b/core.moon @@ -36,28 +36,28 @@ class PermissionNomic extends Nomic g = PermissionNomic() -g\defmacro [[lua %lua_code]], (vars,helpers,ftype)=> - with helpers - lua_code = vars.lua_code.value - as_lua_code = (str)-> - switch str.type - when "String" - escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r" - unescaped = str.value\gsub("\\(.)", ((c)-> escapes[c] or c)) - return unescaped +g\defmacro [[lua %lua_code]], (vars, kind)=> + lua_code = vars.lua_code.value + as_lua_code = (str)-> + switch str.type + when "String" + escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r" + unescaped = str.value\gsub("\\(.)", ((c)-> escapes[c] or c)) + return unescaped - when "Longstring" - result = [line for line in str.value\gmatch("[ \t]*|([^\n]*)")] - return table.concat(result, "\n") - else - return str.value + when "Longstring" + -- TODO: handle comments? + result = [line for line in str.value\gmatch("[ \t]*|([^\n]*)")] + return table.concat(result, "\n") + else + return @tree_to_lua(str) - switch lua_code.type - when "List" - -- TODO: handle subexpressions - .lua table.concat[as_lua_code(i.value) for i in *lua_code.value] - else .lua(as_lua_code(lua_code)) - return nil + switch lua_code.type + when "List" + -- TODO: handle subexpressions + return table.concat([as_lua_code(i.value) for i in *lua_code.value]), true + else + return as_lua_code(lua_code), true g\def {"restrict %fn to %whitelist"}, (vars)=> fns = if type(vars.fn) == 'string' then {vars.fn} else vars.fn @@ -119,41 +119,31 @@ g\def [[concat %strs]], (vars)=> g\def [[quote %str]], (vars)=> return utils.repr(vars.str, true) -g\defmacro "return %retval", (vars,helpers,ftype)=> - with helpers - switch ftype - when "Expression" - error("Cannot use a return statement as an expression") - when "Statement" - .lua "do return "..(.ded(.transform(vars.retval))).." end" - else - error"Unknown: #{ftype}" - return nil +g\defmacro "return %retval", (vars, kind)=> + if kind == "Expression" + error("Cannot use a return statement as an expression") + return "do return "..((@tree_to_lua(vars.retval))\match("%s*(.*)")).." end", true -g\defmacro "let %varname = %value", (vars, helpers, ftype)=> - with helpers - if ftype == "Expression" then error("Cannot set a variable in an expression.") - .lua "vars[#{.ded(.transform(vars.varname))}] = #{.ded(.transform(vars.value))}" - return nil +g\defmacro "let %varname = %value", (vars, kind)=> + if kind == "Expression" + error("Cannot set a variable in an expression.") + return "vars[#{@tree_to_lua(vars.varname)}] = #{@tree_to_lua(vars.value)}" singleton = (aliases, value)-> - g\defmacro aliases, (vars,helpers,ftype)=> - if ftype == "Expression" then helpers.lua(value) - else helpers.lua("ret = #{value}") + g\defmacro aliases, ((vars)=> value) infix = (ops)-> for op in *ops alias = op if type(op) == 'table' {alias,op} = op - g\defmacro "%x #{alias} %y", (vars,helpers,ftype)=> - value = "(#{helpers.var('x')} #{op} #{helpers.var('y')})" - if ftype == "Expression" then helpers.lua(value) - else helpers.lua("ret = #{value}") + g\defmacro "%x #{alias} %y", (vars)=> + return "(#{@tree_to_lua(vars.x)} #{op} #{@tree_to_lua(vars.y)})" + unary = (ops)-> for op in *ops - g\defmacro "#{op} %x", (vars,helpers,ftype)=> - helpers.lua("#{op}(#{helpers.var('x')})") + g\defmacro "#{op} %x", (vars)=> + return "#{op}(#{@tree_to_lua(vars.x)})" singleton {"true","yes"}, "true" singleton {"false","no"}, "false" @@ -164,7 +154,6 @@ g\def [[%x == %y]], (args)=> utils.equivalent(args.x, args.y) g\def "rule %spec %body", (vars)=> self\def vars.spec, vars.body - print "Defined rule: #{utils.repr(vars.spec)}" -- TODO: write help @@ -216,127 +205,84 @@ g\def {[[# %list]], [[length of %list]], [[size of %list]]}, (args)=> return return #(.list) -g\defmacro "if %condition %if_body else %else_body", (vars,helpers,ftype)=> - with helpers - switch ftype - when "Expression" - .lua "((#{.ded(.transform(vars.condition))}) and" - .indented -> - .lua "("..(.ded(.transform(vars.if_body)))..")" - .lua "or ("..(.ded(.transform(vars.if_body))).."))(game, vars)" - when "Statement" - .lua("if (#{.ded(.transform(vars.condition))}) then") - .indented -> - if_body = vars.if_body - while if_body.type != "Block" - if_body = if_body.value - if if_body == nil then error("Failed to find body.") - for statement in *if_body.value - .lua(.ded(.transform(statement))) - .lua("else") - .indented -> - else_body = vars.else_body - while else_body.type != "Block" - else_body = else_body.value - if else_body == nil then error("Failed to find body.") - for statement in *else_body.value - .lua(.ded(.transform(statement))) - .lua("end") - return nil +g\defmacro "if %condition %if_body else %else_body", (vars, kind)=> + if kind == "Expression" + return ([[(function(game, vars) + if (%s) then + %s + else + %s + end + end)(game, vars)]])\format(@tree_to_lua(vars.condition), + @tree_to_lua(vars.if_body.value.value), + @tree_to_lua(vars.else_body.value.value)) + else + return ([[ + if (%s) then + %s + else + %s + end + ]])\format(@tree_to_lua(vars.condition), + @tree_to_lua(vars.if_body.value.value), + @tree_to_lua(vars.else_body.value.value)), true -g\defmacro "for %varname in %iterable %body", (vars,helpers,ftype)=> - with helpers - switch ftype - when "Expression" - .lua "(function(game, vars)" - .indented -> - .lua "local comprehension, vars = {}, setmetatable({}, {__index=vars})" - .lua "for i, value in ipairs(#{.ded(.transform(vars.iterable))}) do" - .indented -> - .lua "local comp_value" - .lua "vars[#{.ded(.transform(vars.varname))}] = value" - body = vars.body - while body.type != "Block" - body = body.value - if body == nil then error("Failed to find body.") - for statement in *body.value - -- TODO: Clean up this ugly bit - .lua("comp_value = "..(.ded(.transform(statement.value, {type:"Expression"})))) - .lua "table.insert(comprehension, comp_value)" - .lua "end" - .lua "return comprehension" - .lua "end)(game,vars)" - when "Statement" - .lua "do" - .indented -> - .lua "local vars = setmetatable({}, {__index=vars})" - .lua "for i, value in ipairs(#{.ded(.transform(vars.iterable))}) do" - .indented -> - .lua "vars[#{.ded(.transform(vars.varname))}] = value" - body = vars.body - while body.type != "Block" - body = body.value - if body == nil then error("Failed to find body.") - for statement in *body.value - .lua(.ded(.transform(statement))) - .lua "end" - .lua "end" - return nil +g\defmacro "for %varname in %iterable %body", (vars, kind)=> + if kind == "Expression" + return " +(function(game, vars) + local comprehension, vars = {}, setmetatable({}, {__index=vars}) + for i, value in ipairs(#{@tree_to_lua(vars.iterable)}) do + local ret + vars[#{@tree_to_lua(vars.varname)}] = value + #{@tree_to_lua(vars.body.value)} + table.insert(comprehension, ret) + end + return comprehension +end)(game, vars)" + else + return " +do + local comprehension, vars = {}, setmetatable({}, {__index=vars}) + for i, value in ipairs(#{@tree_to_lua(vars.iterable)}) do + vars[#{@tree_to_lua(vars.varname)}] = value + #{@tree_to_lua(vars.body.value)} + end +end", true -g\defmacro "for %varname = %start to %stop %body", (vars,helpers,ftype)=> - with helpers - switch ftype - when "Expression" - .lua "(function(game, vars)" - .indented -> - .lua "local comprehension, vars = {}, setmetatable({}, {__index=vars})" - .lua "for value=(#{.ded(.transform(vars.start))}),(#{.ded(.transform(vars.stop))}) do" - .indented -> - .lua "local comp_value" - .lua "vars[#{.ded(.transform(vars.varname))}] = value" - body = vars.body - while body.type != "Block" - body = body.value - if body == nil then error("Failed to find body.") - for statement in *body.value - -- TODO: Clean up this ugly bit - .lua("comp_value = "..(.ded(.transform(statement.value, {type:"Expression"})))) - .lua "table.insert(comprehension, comp_value)" - .lua "end" - .lua "return comprehension" - .lua "end)(game,vars)" - when "Statement" - .lua "do" - .indented -> - .lua "local vars = setmetatable({}, {__index=vars})" - .lua "for value=(#{.ded(.transform(vars.start))}),(#{.ded(.transform(vars.stop))}) do" - .indented -> - .lua "vars[#{.ded(.transform(vars.varname))}] = value" - body = vars.body - while body.type != "Block" - body = body.value - if body == nil then error("Failed to find body.") - for statement in *body.value - .lua(.ded(.transform(statement))) - .lua "end" - .lua "end" - return nil +g\simplemacro "for %varname = %start to %stop %body", [[for %varname in (lua ["utils.range(",%start,",",%stop,")"]) %body]] g\simplemacro "if %condition %body", [[ if %condition %body -..else: pass +..else: nil ]] g\simplemacro "unless %condition %body", [[ if (not %condition) %body -..else: pass +..else: nil ]] g\def [[do %action]], (vars)=> return vars.action(self,vars) -g\defmacro [[macro %spec %body]], (vars,helpers,ftype)=> +g\defmacro [[macro %spec %body]], (vars, kind)=> + if kind == "Expression" then error("Cannot use a macro definition in an expression.") self\simplemacro vars.spec.value.value, vars.body.src + return "", true +g\defmacro [[test %code yields %tree]], (vars, kind)=> + if kind == "Expression" then error("Tests must be statements.") + got = self\stringify_tree(vars.code.value) + got = got\match("Thunk:\n (.*)")\gsub("\n ","\n") + got = utils.repr(got,true) + expected = @tree_to_lua(vars.tree) + return " +do + local got = #{got} + local expected = #{expected} + if got ~= expected then + error('Test failed. Expected:\n'..expected..'\n\nButGot:\n'..got) + end +end", true return g diff --git a/nomic.moon b/nomic.moon index f37e262..ebac1ff 100755 --- a/nomic.moon +++ b/nomic.moon @@ -5,9 +5,13 @@ utils = require 'utils' moon = require 'moon' -- TODO: --- Comments -- string interpolation +-- comprehensions? +-- dicts? +-- better scoping? +-- first-class functions +INDENT = " " lpeg.setmaxstack 10000 -- whoa {:P,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg @@ -116,7 +120,6 @@ class Game return invocations, arg_names defmacro: (spec, fn)=> - assert fn, "No function supplied" invocations,arg_names = self\get_invocations spec fn_info = {:fn, :arg_names, :invocations, is_macro:true} for invocation in *invocations @@ -131,7 +134,7 @@ class Game string <- '"' (("\" .) / [^"])* '"' longstring <- ('".."' %ws? %indent {(%new_line "|" [^%nl]*)+} %dedent (%new_line '..')?) ]=] - fn = (vars, helpers, ftype)=> + fn = (vars, kind)=> replacer = (varname)-> ret = vars[varname].src return ret @@ -139,9 +142,7 @@ class Game code = replacement_grammar\match(replacement) tree = self\parse(code) -- Ugh, this is magic code. - result = helpers.transform(tree.value.body.value[1].value.value.value) - helpers.lua(result) - return + return @tree_to_lua(tree.value.body.value[1].value.value.value, kind), true self\defmacro spec, fn @@ -226,83 +227,60 @@ class Game self\print_tree(tree) assert tree, "Failed to parse: #{str}" return tree - - transform: (tree, indent_level=0, parent=nil, src=nil)=> - if src == nil then src = tree.src - indented = (fn)-> - export indent_level - indent_level += 1 - fn! - indent_level -= 1 - transform = (t,parent)-> self\transform(t, indent_level, parent or tree, src) - ind = (line) -> (" ")\rep(indent_level)..line - ded = (lines)-> - if not lines.match then error("WTF: #{utils.repr(lines)}") - lines\match"^%s*(.*)" - ret_lines = {} - lua = (line, skip_indent=false)-> - unless skip_indent - line = ind(ded(line)) - table.insert ret_lines, line - return line - - comma_separated_items = (open, items, close)-> - buffer = open - so_far = indent_level*2 - indented -> - export buffer,so_far - for i,item in ipairs(items) - if i < #items then item ..= ", " - if so_far + #item >= 80 and #buffer > 0 - lua buffer - so_far -= #buffer - buffer = item - else - so_far += #item - buffer ..= item - buffer ..= close - lua buffer + tree_to_lua: (tree, kind="Expression")=> + assert tree, "No tree provided." + indent = "" + buffer = {} + + to_lua = (t,kind)-> + ret = self\tree_to_lua(t,kind) + return ret + + add = (code)-> table.insert(buffer, code) switch tree.type when "File" - lua "return (function(game, vars)" - indented -> - lua "local ret" - lua transform(tree.value.body) - lua "return ret" - lua "end)" + add [[ + return (function(game, vars) + local ret]] + add to_lua(tree.value.body) + add [[ + return ret + end) + ]] when "Block" for chunk in *tree.value - lua transform(chunk) + add to_lua(chunk) when "Thunk" - if not tree.value - error("Thunk without value: #{utils.repr(tree)}") - lua "(function(game,vars)" - indented -> - lua "local ret" - assert tree.value.type == "Block", "Non-block value in Thunk" - lua transform(tree.value) - lua "return ret" - lua "end)" + assert tree.value.type == "Block", "Non-block value in Thunk" + add [[ + (function(game, vars) + local ret]] + add to_lua(tree.value) + add [[ + return ret + end) + ]] when "Statement" + -- This case here is to prevent "ret =" from getting prepended when the macro might not want it if tree.value.type == "FunctionCall" name_bits = {} for token in *tree.value.value table.insert name_bits, if token.type == "Word" then token.value else "%" name = table.concat(name_bits, " ") if @defs[name] and @defs[name].is_macro - -- This case here is to prevent "ret =" from getting prepended when the macro might not want it - lua transform(tree.value) - ret = table.concat ret_lines, "\n" - return ret - lua "ret = #{ded(transform(tree.value))}" + add @run_macro(tree.value, "Statement") + else + add "ret = "..(to_lua(tree.value)\match("%s*(.*)")) + else + add "ret = "..(to_lua(tree.value)\match("%s*(.*)")) when "Expression" - lua transform(tree.value) + add to_lua(tree.value) when "FunctionCall" name_bits = {} @@ -310,50 +288,92 @@ class Game table.insert name_bits, if token.type == "Word" then token.value else "%" name = table.concat(name_bits, " ") if @defs[name] and @defs[name].is_macro - {:fn, :arg_names} = @defs[name] - helpers = {:indented, :transform, :ind, :ded, :lua, :comma_separated_items} - args = [a for a in *tree.value when a.type != "Word"] - args = {name,args[i] for i,name in ipairs(arg_names)} - helpers.var = (varname)-> - ded(transform(args[varname])) - m = fn(self, args, helpers, parent.type) - if m != nil then return m + add @run_macro(tree, "Expression") else - args = [ded(transform(a)) for a in *tree.value when a.type != "Word"] + args = [to_lua(a) for a in *tree.value when a.type != "Word"] table.insert args, 1, utils.repr(name, true) - comma_separated_items("game:call(", args, ")") + add @@comma_separated_items("game: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)) - lua utils.repr(unescaped, true) + add utils.repr(unescaped, true) when "Longstring" + -- TODO: handle comments here? result = [line for line in tree.value\gmatch("[ \t]*|([^\n]*)")] - lua utils.repr(table.concat(result, "\n"), true) + add utils.repr(table.concat(result, "\n"), true) when "Number" - lua tree.value + add tree.value when "List" if #tree.value == 0 - lua "{}" + add "{}" elseif #tree.value == 1 - lua "{#{transform(tree.value)}}" + add "{#{to_lua(tree.value)}}" else - comma_separated_items("{", [ded(transform(item)) for item in *tree.value], "}") + add @@comma_separated_items("{", [to_lua(item) for item in *tree.value], "}") when "Var" - lua "vars[#{utils.repr(tree.value,true)}]" + add "vars[#{utils.repr(tree.value,true)}]" else error("Unknown/unimplemented thingy: #{tree.type}") - - ret = table.concat ret_lines, "\n" + + -- TODO: make indentation clean + -- Patch the buffer together + [=[ + for code in *buffer + if code == "" + indent ..= INDENT + elseif code == "" + indent = indent\gsub(INDENT, "", 1) + else + first_indent,middle,last_indent = code\match("(%s*)(.*\n(%s*)[^\n]*)") + if not first_indent + error("No indent found on: [[#{code}]]") + table.insert(buffer, indent..(middle\gsub("\n"..first_indent, "\n"..indent))) + indent = last_indent + ]=] + + return table.concat(buffer, "\n") + + @comma_separated_items: (open, items, close)=> + utils.accumulate "\n", -> + buffer = open + so_far = 0 + for i,item in ipairs(items) + if i < #items then item ..= ", " + if so_far + #item >= 80 and #buffer > 0 + coroutine.yield buffer + so_far -= #buffer + buffer = item + else + so_far += #item + buffer ..= item + buffer ..= close + coroutine.yield buffer + + run_macro: (tree, kind="Expression")=> + name_bits = {} + for token in *tree.value + table.insert name_bits, if token.type == "Word" then token.value else "%" + name = table.concat(name_bits, " ") + unless @defs[name] and @defs[name].is_macro + error("Macro not found: #{name}") + {:fn, :arg_names} = @defs[name] + args = [a for a in *tree.value when a.type != "Word"] + args = {name,args[i] for i,name in ipairs(arg_names)} + ret, manual_mode = fn(self, args, kind) + if not ret + error("No return value for macro: #{name}") + if kind == "Statement" and not manual_mode + ret = "ret = "..ret return ret _yield_tree: (tree, indent_level=0)=> - ind = (s) -> (" ")\rep(indent_level)..s + ind = (s) -> INDENT\rep(indent_level)..s switch tree.type when "File" coroutine.yield(ind"File:") @@ -427,7 +447,8 @@ class Game compile: (src)=> tree = self\parse(src) - code = self\transform(tree,0) + assert tree, "Tree failed to compile: #{src}" + code = self\tree_to_lua(tree) return code test: (src, expected)=> diff --git a/utils.moon b/utils.moon index ee97903..33e1312 100644 --- a/utils.moon +++ b/utils.moon @@ -31,6 +31,26 @@ utils = { split: (str, sep="%s")-> [chunk for chunk in str\gmatch("[^#{sep}]+")] + + accumulate: (glue, co)-> + if co == nil then glue, co = "", glue + bits = {} + for bit in coroutine.wrap(co) + table.insert(bits, bit) + return table.concat(bits, glue) + + range: (start,stop,step)-> + if stop == nil + start,stop,step = 1,start,1 + elseif step == nil + step = 1 + return setmetatable({:start,:stop,:step}, { + __ipairs: => + iter = (i)=> + if i < (@stop-@start)/@step + return i+1, @start+i*@step + return iter, @, 0 + }) keys: (t)-> [k for k in pairs(t)] values: (t)-> [v for _,v in pairs(t)]