aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/metaprogramming.nom97
-rwxr-xr-xnomsu.moon182
-rw-r--r--utils.moon1
3 files changed, 162 insertions, 118 deletions
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)
+ | 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)
+
+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)
-..with value (nil)
+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 code %code) as\: lua code %code with value (nil)
-parse \(lua block %block) as\:
+
+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 <unknown source code>"))
|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 <lua code for value>, <additional lua code>
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)}]"})
+ 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)}]"})
- 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 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