diff --git a/lib/metaprogramming.nom b/lib/metaprogramming.nom index 9dcc9df..e60a235 100644 --- a/lib/metaprogramming.nom +++ b/lib/metaprogramming.nom @@ -5,32 +5,74 @@ # Nil lua code ".." |nomsu:defmacro("nil", function(nomsu, vars) return "nil", nil end) -..with value 0 # Macros: lua code ".." - |nomsu:def("parse %shorthand as %longhand", function(nomsu, vars) - | local alias = nomsu:get_alias(vars.shorthand) + |local function parse_as(nomsu, vars) + | if vars.shorthand.type ~= "Block" then + | nomsu:error("Expected shorthand to be Block, but got "..vars.shorthand.type) + | end + | if vars.longhand.type ~= "Block" then + | nomsu:error("Expected longhand to be Block, but got "..vars.longhand.type) + | end | local template = vars.longhand - | nomsu:defmacro(alias, function(nomsu, vars) - | return nomsu:tree_to_lua(nomsu:replaced_vars(template, vars)) - | end) - |end) -..with value (nil) + | local function parsing_as(nomsu, vars) + | local expanded = nomsu:replaced_vars(template, vars) + | local expr,statement = nomsu:tree_to_lua(expanded) + | return expr, statement + | end + | for _,call in ipairs(vars.shorthand.value) do + | nomsu:defmacro(call, parsing_as) + | end + |end + |nomsu:def("parse nomsu %shorthand as nomsu %longhand", parse_as) +parse nomsu \(parse %shorthand as %longhand) as nomsu \(parse nomsu \%shorthand as nomsu \%longhand) +parse (foo %x) as (baz %x) -parse \(lua code %code) as\: lua code %code with value (nil) -parse \(lua block %block) as\: +lua code ".." + |nomsu:defmacro("lua expr %code", function(nomsu, vars) + | return nomsu:tree_to_value(vars.code, vars), nil + |end) + + +# Rule that lets you make new rules +lua code ".." + |nomsu:defmacro("rule %rule_def = %body", function(nomsu, vars) + | if vars.rule_def.type ~= "Block" then + | nomsu:error("Wrong type for rule definition, expected Block, but got "..vars.rule_def.type) + | end + | local thunk = nomsu:tree_to_lua({type="Thunk", value=vars.body, src=vars.body.src}) + | local fn_name = "fn_"..nomsu:var_to_lua_identifier(nomsu:get_stub(vars.rule_def.value[1])) + | local lua = ([[ + |do + | local %s = %s + | for _,alias in ipairs(%s) do + | nomsu:def(alias, %s) + | end + |end]]):format(fn_name, thunk, nomsu:repr(vars.rule_def.value), fn_name) + | return nil, lua + |end) +parse (rule %rule_name) as: lua expr "nomsu.defs[(nomsu:get_stub(\(%rule_name).value[1]))]" + + +# Macro helper functions +rule (%tree as value) =: + lua expr "nomsu:tree_to_value(vars.tree, vars)" + +rule (%tree as lua) =: + lua expr "nomsu:tree_to_lua(vars.tree)" + + +parse (lua block %block) as: lua code ".." |do | \(%block) |end - ..with value (nil) -parse \(lua value %value) as\: lua code (nil) with value %value -parse \(nomsu) as\: lua value "nomsu" -parse \(nomsu's %key) as\: - lua value "nomsu[\(%key as lua)]" -parse \(nomsu %method %args) as\: +parse (nomsu) as: lua expr "nomsu" +parse (nomsu's %key) as: + lua expr "nomsu[\(%key as lua)]" +parse (nomsu %method %args) as: lua block ".." |local args = {"nomsu"} |for _,arg in ipairs(vars.args.value) do @@ -39,15 +81,7 @@ parse \(nomsu %method %args) as\: |local method_name = nomsu:repr(nomsu:tree_to_value(vars.method, vars)) ..with value ".." |("nomsu[%s](%s)"):format(method_name, table.concat(args, ", ")) -parse \(repr %) as\: nomsu "repr" [%] - -# Rule that lets you make new rules -lua block ".." - |nomsu:def("rule helper %rule_def = %body", function(nomsu, vars) - | nomsu:def(nomsu:get_alias(vars.rule_def), vars.body) - |end) -parse \(rule %rule_def = %body) as\: rule helper \(%rule_def) = \(%body) -parse \(rule %rule_name) as\: lua value "nomsu.defs[nomsu:get_alias(\(%rule_name))]" +parse (repr %) as: nomsu "repr" [%] # Get the source code for a function rule (help %rule) =: @@ -56,17 +90,10 @@ rule (help %rule) =: |if not fn_def then | nomsu:writeln("Rule not found: "..nomsu:repr(vars.rule)) |else - | nomsu:writeln("rule "..nomsu:repr(nomsu.utils.keys(fn_def.aliases)) + | nomsu:writeln("rule "..nomsu:repr(nomsu.utils.keys(fn_def.invocation)) | .." ="..(fn_def.src or ":\\n ")) |end -# Macro helper functions -rule (%tree as value) =: - lua expr "nomsu:tree_to_value(vars.tree, vars)" - -rule (%tree as lua) =: - lua expr "nomsu:tree_to_lua(vars.tree)" - # Compiler tools rule (eval %code; run %code) =: nomsu "run" [%code] rule (source code from tree %tree) =: @@ -80,9 +107,7 @@ rule (source code from tree %tree) =: | return vars.tree.src:match(":%s*(%S.*)").."\\n" |end -macro (source code %body) =: - nomsu "repr" [nomsu "call" ["source code from tree %", %body]] +parse (source code %body) as: source code from tree \%body -macro (parse tree %code) =: - nomsu "repr" [nomsu "stringify_tree" [lua expr "vars.code.value"]] +parse (parse tree %code) as: repr (nomsu "tree_to_str" [\%code]) diff --git a/nomsu.moon b/nomsu.moon index a3a0f30..497bda1 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -16,7 +16,7 @@ lpeg = require 'lpeg' utils = require 'utils' repr = utils.repr {:insert, :remove, :concat} = table -pcall = (fn,...)-> true, fn(...) +--pcall = (fn,...)-> true, fn(...) -- TODO: -- use actual variables instead of a vars table @@ -74,9 +74,9 @@ nomsu = [=[ (dedent / (({.+} ("" -> "Error while parsing block")) => error)) |} }) -> Block - inline_nomsu <- ({ ("\" inline_block ) }) -> Nomsu - eol_nomsu <- ({ ("\" eol_block ) }) -> Nomsu - indented_nomsu <- ({ ("\" {indented_block} ) }) -> Nomsu + inline_nomsu <- ({ ("\" inline_expression) }) -> Nomsu + eol_nomsu <- ({ ("\" noeol_expression) }) -> Nomsu + indented_nomsu <- ({ ("\" expression) }) -> Nomsu inline_expression <- number / variable / inline_string / inline_list / inline_block / inline_nomsu noeol_expression <- indented_string / indented_block / indented_nomsu / indented_list / inline_expression @@ -187,42 +187,40 @@ class NomsuCompiler @write("\n") def: (invocation, thunk, src)=> - if type(invocation) != 'string' then @error "Invocation should be string, not: #{repr invocation}" - if @debug then @writeln "Defining rule: #{repr invocation}" - stub = invocation\gsub("'"," '")\gsub("%%%S+","%%")\gsub("%s+"," ") - args = [arg for arg in invocation\gmatch("%%(%S[^%s']*)")] - for i=1,#args-1 do for j=i+1,#args - if args[i] == args[j] then @error "Duplicate argument in function def: #{args[i]}" - with @defs[invocation] = {:thunk, :invocation, :args, :src, is_macro:false} do nil + stub, arg_names = @get_stub invocation + assert stub, "NO STUB FOUND: #{repr invocation}" + if @debug then @writeln "Defining rule: #{repr stub} with args #{repr arg_names}" + for i=1,#arg_names-1 do for j=i+1,#arg_names + if arg_names[i] == arg_names[j] then @error "Duplicate argument in function #{stub}: '#{arg_names[i]}'" + with @defs[stub] = {:thunk, :invocation, :arg_names, :src, is_macro:false} do nil defmacro: (invocation, thunk, src)=> with @def(invocation, thunk, src) do .is_macro = true - call: (alias,...)=> - def = @defs[alias] + call: (stub,...)=> + def = @defs[stub] if def == nil - @error "Attempt to call undefined function: #{alias}" + @error "Attempt to call undefined function: #{stub}" -- This is a little bit hacky, but having this check is handy for catching mistakes -- I use a hash sign in "#macro" so it's guaranteed to not be a valid function name if def.is_macro and @callstack[#@callstack] != "#macro" - @error "Attempt to call macro at runtime: #{alias}\nThis can be caused by using a macro in a function that is defined before the macro." + @error "Attempt to call macro at runtime: #{stub}\nThis can be caused by using a macro in a function that is defined before the macro." unless @check_permission(def) - @error "You do not have the authority to call: #{alias}" - {:thunk, :args} = def - args = {name, select(i,...) for i,name in ipairs(args)} + @error "You do not have the authority to call: #{stub}" + {:thunk, :arg_names} = def + args = {name, select(i,...) for i,name in ipairs(arg_names)} if @debug - @writeln "Calling #{repr alias} with args: #{repr(args)}" - insert @callstack, alias + @writeln "Calling #{repr stub} with args: #{repr(args)}" + insert @callstack, stub -- TODO: optimize, but still allow multiple return values? rets = {thunk(self,args)} remove @callstack return unpack(rets) run_macro: (tree, kind="Expression")=> - local args, alias - alias,args = @get_alias tree + stub,args = @get_stub tree insert @callstack, "#macro" - expr, statement = @call(alias, unpack(args)) + expr, statement = @call(stub, unpack(args)) remove @callstack return expr, statement @@ -257,12 +255,15 @@ class NomsuCompiler run: (src, filename)=> tree = @parse(src, filename) assert tree, "Tree failed to compile: #{src}" - assert tree.type == "File" + assert tree.type == "File", "Attempt to run non-file: #{tree.type}" buffer = {} vars = {} return_value = nil for statement in *tree.value + if @debug + @writeln "RUNNING TREE:" + @print_tree statement ok,expr,statements = pcall(@tree_to_lua, self, statement) if not ok @writeln "Error occurred in statement:\n#{statement.src}" @@ -282,7 +283,7 @@ class NomsuCompiler if expr then return_value = ret if not ok @writeln "Error occurred in statement:\n#{statement.src}" - @error(return_value) + @error(repr return_value) insert buffer, "#{statements or ''}\n#{expr and "ret = #{expr}" or ''}" lua_code = ([[ @@ -305,6 +306,7 @@ class NomsuCompiler -- Return , assert tree, "No tree provided." if not tree.type + @writeln debug.traceback() @error "Invalid tree: #{repr(tree)}" switch tree.type when "File" @@ -313,27 +315,32 @@ class NomsuCompiler when "Nomsu" return repr(tree.value), nil - when "Block" + when "Thunk" -- This is not created by the parser, it's just a helper lua_bits = {} - for arg in *tree.value + for arg in *tree.value.value expr,statement = @tree_to_lua arg - -- Optimization for common case - if expr and not statement and #tree.value == 1 - return expr, nil if statement then insert lua_bits, statement if expr then insert lua_bits, "ret = #{expr}" return ([[ - function(nomsu, vars) + (function(nomsu, vars) local ret %s return ret - end]])\format(concat lua_bits, "\n") + end)]])\format(concat lua_bits, "\n") + + when "Block" + if #tree.value == 1 + expr,statement = @tree_to_lua tree.value[1] + if not statement + return expr, nil + thunk_lua = @tree_to_lua {type:"Thunk", value:tree, src:tree.src} + return ("%s(nomsu, vars)")\format(thunk_lua), nil when "FunctionCall" - alias = @get_alias(tree) - if @defs[alias] and @defs[alias].is_macro + stub = @get_stub(tree) + if @defs[stub] and @defs[stub].is_macro return @run_macro(tree, "Expression") - args = {repr(alias)} + args = {repr(stub)} for arg in *tree.value if arg.type == 'Word' then continue expr,statement = @tree_to_lua arg @@ -380,16 +387,32 @@ class NomsuCompiler else @error("Unknown/unimplemented thingy: #{tree.type}") - print_tree: (tree, ind="")=> - if type(tree) ~= 'table' or not tree.type - @writeln "#{ind}#{repr tree}" + walk_tree: (tree, depth=0)=> + coroutine.yield(tree, depth) + if type(tree) != 'table' or not tree.type return - @writeln "#{ind}#{tree.type}:" switch tree.type - when "List", "File", "Block", "FunctionCall", "String" + when "List", "File", "Nomsu", "Block", "FunctionCall", "String" for v in *tree.value - @print_tree(v, ind.." ") - else @print_tree(tree.value, ind.." ") + @walk_tree(v, depth+1) + else @walk_tree(tree.value, depth+1) + return nil + + print_tree: (tree)=> + for node,depth in coroutine.wrap(-> @walk_tree tree) + if type(node) != 'table' or not node.type + @writeln((" ")\rep(depth)..repr(node)) + else + @writeln("#{(" ")\rep(depth)}#{node.type}:") + + tree_to_str: (tree)=> + bits = {} + for node,depth in coroutine.wrap(-> @walk_tree tree) + if type(node) != 'table' or not node.type + insert bits, ((" ")\rep(depth)..repr(node)) + else + insert bits, ("#{(" ")\rep(depth)}#{node.type}:") + return concat(bits, "\n") @unescape_string: (str)=> str\gsub("\\(.)", ((c)-> STRING_ESCAPES[c] or c)) @@ -414,8 +437,8 @@ class NomsuCompiler when "Var" if vars[tree.value] tree = vars[tree.value] - when "File", "Thunk", "Statement", "Block", "List", "FunctionCall", "String" - new_value = @replaced_vars tree.value + when "File", "Nomsu", "Thunk", "Block", "List", "FunctionCall", "String" + new_value = @replaced_vars tree.value, vars if new_value != tree.value tree = {k,v for k,v in pairs(tree)} tree.value = new_value @@ -423,52 +446,39 @@ class NomsuCompiler new_values = {} any_different = false for k,v in pairs tree - new_values[k] = @replaced_vars v + new_values[k] = @replaced_vars v, vars any_different or= (new_values[k] != tree[k]) if any_different tree = new_values return tree - get_alias: (x)=> - if not x then @error "Nothing to get alias from" - -- Returns a single alias ("say %"), and list of args ({msg}) from a single rule def + get_stub: (x)=> + if not x + @error "Nothing to get stub from" + -- Returns a single stub ("say %"), and list of args ({msg}) from a single rule def -- (e.g. "say %msg") or function call (e.g. FunctionCall({Word("say"), Var("msg"))) if type(x) == 'string' - alias = x\gsub("'"," '")\gsub("%%%S+","%%")\gsub("%s+"," ") - args = [arg for arg in x\gmatch("%%(%S[^%s']*)")] - return alias, args + stub = x\gsub("'"," '")\gsub("%%%S+","%%")\gsub("%s+"," ") + args = [arg for arg in x\gmatch("%%([^%s']*)")] + return stub, args switch x.type - when "String" then return @get_alias(x.value) - when "Statement" then return @get_alias(x.value) + when "String" then return @get_stub(x.value) when "FunctionCall" - alias, args = {}, {}, {} + stub, args = {}, {}, {} for token in *x.value switch token.type when "Word" - insert alias, token.value + insert stub, token.value when "Var" - insert alias, "%" + insert stub, "%" insert args, token.value else - insert alias, "%" + insert stub, "%" insert args, token - return concat(alias," "), args - - get_aliases:(x)=> - if not x then @error "Nothing to get aliases from" - if type(x) == 'string' - alias, args = @get_alias(x) - return {[alias]: args} - switch x.type - when "String" then return @get_aliases({x.value}) - when "Statement" then return @get_aliases({x.value}) - when "FunctionCall" then return @get_aliases({x}) - when "List" then x = x.value - when "Block" then x = x.value - with {} - for y in *x - alias,args = @get_alias(y) - [alias] = args + return concat(stub," "), args + when "Block" + @writeln debug.traceback! + @error "Please pass in a single line from a block, not the whole thing:\n#{@tree_to_str x}" var_to_lua_identifier: (var)=> -- Converts arbitrary nomsu vars to valid lua identifiers by replacing illegal @@ -480,7 +490,8 @@ class NomsuCompiler error: (...)=> @writeln "ERROR!" - @writeln(...) + if select(1, ...) + @writeln(...) @writeln("Callstack:") for i=#@callstack,1,-1 @writeln " #{@callstack[i]}" @@ -490,32 +501,39 @@ class NomsuCompiler initialize_core: => -- Sets up some core functionality - @defmacro "lua code %statements with value %value", (vars)=> + lua_code = (vars)=> inner_vars = setmetatable({}, {__index:(_,key)-> "vars[#{repr(key)}]"}) - statements = @tree_to_value(vars.statements, inner_vars) - value = @tree_to_value(vars.value, inner_vars) - return value, statements + lua = @tree_to_value(vars.code, inner_vars) + return nil, lua + @defmacro "lua code %code", lua_code + + lua_value = (vars)=> + inner_vars = setmetatable({}, {__index:(_,key)-> "vars[#{repr(key)}]"}) + lua = @tree_to_value(vars.code, inner_vars) + return lua, nil + @defmacro "lua value %code", lua_value - @def "require %filename", (vars)=> + _require = (vars)=> if not @loaded_files[vars.filename] file = io.open(vars.filename) if not file @error "File does not exist: #{vars.filename}" @loaded_files[vars.filename] = (@run(file\read('*a'), vars.filename)) or true return @loaded_files[vars.filename] + @def "require %filename", _require - @def "run file %filename", (vars)=> + run_file = (vars)=> file = io.open(vars.filename) if not file @error "File does not exist: #{vars.filename}" return @run(file\read('*a'), vars.filename) + @def "run file %filename", run_file if arg and arg[1] --ProFi = require 'ProFi' --ProFi\start() c = NomsuCompiler() - c.debug = true input = io.open(arg[1])\read("*a") -- If run via "./nomsu.moon file.nom -", then silence output and print generated -- source code instead. diff --git a/utils.moon b/utils.moon index 471b28e..87cd5a3 100644 --- a/utils.moon +++ b/utils.moon @@ -1,6 +1,7 @@ local utils utils = { is_list: (t)-> + if type(t) != 'table' then return false i = 1 for _ in pairs(t) if t[i] == nil then return false