aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcore.moon258
-rwxr-xr-xnomic.moon193
-rw-r--r--utils.moon20
3 files changed, 229 insertions, 242 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
-
- when "Longstring"
- result = [line for line in str.value\gmatch("[ \t]*|([^\n]*)")]
- return table.concat(result, "\n")
- else
- return str.value
-
- 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
+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"
+ -- 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
+ 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 "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 = %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\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, 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\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 ..= INDENT
+ elseif code == "<DEDENT>"
+ 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)]