probably working after refactor?

This commit is contained in:
Bruce Hill 2017-09-11 13:05:25 -07:00
parent d218dcbd42
commit 5fef795cda
3 changed files with 223 additions and 236 deletions

248
core.moon
View File

@ -36,28 +36,28 @@ class PermissionNomic extends Nomic
g = PermissionNomic() g = PermissionNomic()
g\defmacro [[lua %lua_code]], (vars,helpers,ftype)=> g\defmacro [[lua %lua_code]], (vars, kind)=>
with helpers lua_code = vars.lua_code.value
lua_code = vars.lua_code.value as_lua_code = (str)->
as_lua_code = (str)-> switch str.type
switch str.type when "String"
when "String" escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r"
escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r" unescaped = str.value\gsub("\\(.)", ((c)-> escapes[c] or c))
unescaped = str.value\gsub("\\(.)", ((c)-> escapes[c] or c)) return unescaped
return unescaped
when "Longstring" when "Longstring"
result = [line for line in str.value\gmatch("[ \t]*|([^\n]*)")] -- TODO: handle comments?
return table.concat(result, "\n") result = [line for line in str.value\gmatch("[ \t]*|([^\n]*)")]
else return table.concat(result, "\n")
return str.value else
return @tree_to_lua(str)
switch lua_code.type switch lua_code.type
when "List" when "List"
-- TODO: handle subexpressions -- TODO: handle subexpressions
.lua table.concat[as_lua_code(i.value) for i in *lua_code.value] return table.concat([as_lua_code(i.value) for i in *lua_code.value]), true
else .lua(as_lua_code(lua_code)) else
return nil return as_lua_code(lua_code), true
g\def {"restrict %fn to %whitelist"}, (vars)=> g\def {"restrict %fn to %whitelist"}, (vars)=>
fns = if type(vars.fn) == 'string' then {vars.fn} else vars.fn 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)=> g\def [[quote %str]], (vars)=>
return utils.repr(vars.str, true) return utils.repr(vars.str, true)
g\defmacro "return %retval", (vars,helpers,ftype)=> g\defmacro "return %retval", (vars, kind)=>
with helpers if kind == "Expression"
switch ftype error("Cannot use a return statement as an expression")
when "Expression" return "do return "..((@tree_to_lua(vars.retval))\match("%s*(.*)")).." end", true
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 "let %varname = %value", (vars, helpers, ftype)=> g\defmacro "let %varname = %value", (vars, kind)=>
with helpers if kind == "Expression"
if ftype == "Expression" then error("Cannot set a variable in an expression.") error("Cannot set a variable in an expression.")
.lua "vars[#{.ded(.transform(vars.varname))}] = #{.ded(.transform(vars.value))}" return "vars[#{@tree_to_lua(vars.varname)}] = #{@tree_to_lua(vars.value)}"
return nil
singleton = (aliases, value)-> singleton = (aliases, value)->
g\defmacro aliases, (vars,helpers,ftype)=> g\defmacro aliases, ((vars)=> value)
if ftype == "Expression" then helpers.lua(value)
else helpers.lua("ret = #{value}")
infix = (ops)-> infix = (ops)->
for op in *ops for op in *ops
alias = op alias = op
if type(op) == 'table' if type(op) == 'table'
{alias,op} = op {alias,op} = op
g\defmacro "%x #{alias} %y", (vars,helpers,ftype)=> g\defmacro "%x #{alias} %y", (vars)=>
value = "(#{helpers.var('x')} #{op} #{helpers.var('y')})" return "(#{@tree_to_lua(vars.x)} #{op} #{@tree_to_lua(vars.y)})"
if ftype == "Expression" then helpers.lua(value)
else helpers.lua("ret = #{value}")
unary = (ops)-> unary = (ops)->
for op in *ops for op in *ops
g\defmacro "#{op} %x", (vars,helpers,ftype)=> g\defmacro "#{op} %x", (vars)=>
helpers.lua("#{op}(#{helpers.var('x')})") return "#{op}(#{@tree_to_lua(vars.x)})"
singleton {"true","yes"}, "true" singleton {"true","yes"}, "true"
singleton {"false","no"}, "false" 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)=> g\def "rule %spec %body", (vars)=>
self\def vars.spec, vars.body self\def vars.spec, vars.body
print "Defined rule: #{utils.repr(vars.spec)}"
-- TODO: write help -- TODO: write help
@ -216,127 +205,84 @@ g\def {[[# %list]], [[length of %list]], [[size of %list]]}, (args)=>
return return
return #(.list) return #(.list)
g\defmacro "if %condition %if_body else %else_body", (vars,helpers,ftype)=> g\defmacro "if %condition %if_body else %else_body", (vars, kind)=>
with helpers if kind == "Expression"
switch ftype return ([[(function(game, vars)
when "Expression" if (%s) then
.lua "((#{.ded(.transform(vars.condition))}) and" %s
.indented -> else
.lua "("..(.ded(.transform(vars.if_body)))..")" %s
.lua "or ("..(.ded(.transform(vars.if_body))).."))(game, vars)" end
when "Statement" end)(game, vars)]])\format(@tree_to_lua(vars.condition),
.lua("if (#{.ded(.transform(vars.condition))}) then") @tree_to_lua(vars.if_body.value.value),
.indented -> @tree_to_lua(vars.else_body.value.value))
if_body = vars.if_body else
while if_body.type != "Block" return ([[
if_body = if_body.value if (%s) then
if if_body == nil then error("Failed to find body.") %s
for statement in *if_body.value else
.lua(.ded(.transform(statement))) %s
.lua("else") end
.indented -> ]])\format(@tree_to_lua(vars.condition),
else_body = vars.else_body @tree_to_lua(vars.if_body.value.value),
while else_body.type != "Block" @tree_to_lua(vars.else_body.value.value)), true
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)=> g\defmacro "for %varname in %iterable %body", (vars, kind)=>
with helpers if kind == "Expression"
switch ftype return "
when "Expression" (function(game, vars)
.lua "(function(game, vars)" local comprehension, vars = {}, setmetatable({}, {__index=vars})
.indented -> for i, value in ipairs(#{@tree_to_lua(vars.iterable)}) do
.lua "local comprehension, vars = {}, setmetatable({}, {__index=vars})" local ret
.lua "for i, value in ipairs(#{.ded(.transform(vars.iterable))}) do" vars[#{@tree_to_lua(vars.varname)}] = value
.indented -> #{@tree_to_lua(vars.body.value)}
.lua "local comp_value" table.insert(comprehension, ret)
.lua "vars[#{.ded(.transform(vars.varname))}] = value" end
body = vars.body return comprehension
while body.type != "Block" end)(game, vars)"
body = body.value else
if body == nil then error("Failed to find body.") return "
for statement in *body.value do
-- TODO: Clean up this ugly bit local comprehension, vars = {}, setmetatable({}, {__index=vars})
.lua("comp_value = "..(.ded(.transform(statement.value, {type:"Expression"})))) for i, value in ipairs(#{@tree_to_lua(vars.iterable)}) do
.lua "table.insert(comprehension, comp_value)" vars[#{@tree_to_lua(vars.varname)}] = value
.lua "end" #{@tree_to_lua(vars.body.value)}
.lua "return comprehension" end
.lua "end)(game,vars)" end", true
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)=> g\simplemacro "for %varname = %start to %stop %body", [[for %varname in (lua ["utils.range(",%start,",",%stop,")"]) %body]]
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 "if %condition %body", [[ g\simplemacro "if %condition %body", [[
if %condition %body if %condition %body
..else: pass ..else: nil
]] ]]
g\simplemacro "unless %condition %body", [[ g\simplemacro "unless %condition %body", [[
if (not %condition) %body if (not %condition) %body
..else: pass ..else: nil
]] ]]
g\def [[do %action]], (vars)=> return vars.action(self,vars) 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 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 return g

View File

@ -5,9 +5,13 @@ utils = require 'utils'
moon = require 'moon' moon = require 'moon'
-- TODO: -- TODO:
-- Comments
-- string interpolation -- string interpolation
-- comprehensions?
-- dicts?
-- better scoping?
-- first-class functions
INDENT = " "
lpeg.setmaxstack 10000 -- whoa lpeg.setmaxstack 10000 -- whoa
{:P,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg {:P,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg
@ -116,7 +120,6 @@ class Game
return invocations, arg_names return invocations, arg_names
defmacro: (spec, fn)=> defmacro: (spec, fn)=>
assert fn, "No function supplied"
invocations,arg_names = self\get_invocations spec invocations,arg_names = self\get_invocations spec
fn_info = {:fn, :arg_names, :invocations, is_macro:true} fn_info = {:fn, :arg_names, :invocations, is_macro:true}
for invocation in *invocations for invocation in *invocations
@ -131,7 +134,7 @@ class Game
string <- '"' (("\" .) / [^"])* '"' string <- '"' (("\" .) / [^"])* '"'
longstring <- ('".."' %ws? %indent {(%new_line "|" [^%nl]*)+} %dedent (%new_line '..')?) longstring <- ('".."' %ws? %indent {(%new_line "|" [^%nl]*)+} %dedent (%new_line '..')?)
]=] ]=]
fn = (vars, helpers, ftype)=> fn = (vars, kind)=>
replacer = (varname)-> replacer = (varname)->
ret = vars[varname].src ret = vars[varname].src
return ret return ret
@ -139,9 +142,7 @@ class Game
code = replacement_grammar\match(replacement) code = replacement_grammar\match(replacement)
tree = self\parse(code) tree = self\parse(code)
-- Ugh, this is magic code. -- Ugh, this is magic code.
result = helpers.transform(tree.value.body.value[1].value.value.value) return @tree_to_lua(tree.value.body.value[1].value.value.value, kind), true
helpers.lua(result)
return
self\defmacro spec, fn self\defmacro spec, fn
@ -226,83 +227,60 @@ class Game
self\print_tree(tree) self\print_tree(tree)
assert tree, "Failed to parse: #{str}" assert tree, "Failed to parse: #{str}"
return tree 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 = {} tree_to_lua: (tree, kind="Expression")=>
lua = (line, skip_indent=false)-> assert tree, "No tree provided."
unless skip_indent indent = ""
line = ind(ded(line)) buffer = {}
table.insert ret_lines, line
return line to_lua = (t,kind)->
ret = self\tree_to_lua(t,kind)
comma_separated_items = (open, items, close)-> return ret
buffer = open
so_far = indent_level*2 add = (code)-> table.insert(buffer, code)
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
switch tree.type switch tree.type
when "File" when "File"
lua "return (function(game, vars)" add [[
indented -> return (function(game, vars)
lua "local ret" local ret]]
lua transform(tree.value.body) add to_lua(tree.value.body)
lua "return ret" add [[
lua "end)" return ret
end)
]]
when "Block" when "Block"
for chunk in *tree.value for chunk in *tree.value
lua transform(chunk) add to_lua(chunk)
when "Thunk" when "Thunk"
if not tree.value assert tree.value.type == "Block", "Non-block value in Thunk"
error("Thunk without value: #{utils.repr(tree)}") add [[
lua "(function(game,vars)" (function(game, vars)
indented -> local ret]]
lua "local ret" add to_lua(tree.value)
assert tree.value.type == "Block", "Non-block value in Thunk" add [[
lua transform(tree.value) return ret
lua "return ret" end)
lua "end)" ]]
when "Statement" when "Statement"
-- This case here is to prevent "ret =" from getting prepended when the macro might not want it
if tree.value.type == "FunctionCall" if tree.value.type == "FunctionCall"
name_bits = {} name_bits = {}
for token in *tree.value.value for token in *tree.value.value
table.insert name_bits, if token.type == "Word" then token.value else "%" table.insert name_bits, if token.type == "Word" then token.value else "%"
name = table.concat(name_bits, " ") name = table.concat(name_bits, " ")
if @defs[name] and @defs[name].is_macro 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 add @run_macro(tree.value, "Statement")
lua transform(tree.value) else
ret = table.concat ret_lines, "\n" add "ret = "..(to_lua(tree.value)\match("%s*(.*)"))
return ret else
lua "ret = #{ded(transform(tree.value))}" add "ret = "..(to_lua(tree.value)\match("%s*(.*)"))
when "Expression" when "Expression"
lua transform(tree.value) add to_lua(tree.value)
when "FunctionCall" when "FunctionCall"
name_bits = {} name_bits = {}
@ -310,50 +288,92 @@ class Game
table.insert name_bits, if token.type == "Word" then token.value else "%" table.insert name_bits, if token.type == "Word" then token.value else "%"
name = table.concat(name_bits, " ") name = table.concat(name_bits, " ")
if @defs[name] and @defs[name].is_macro if @defs[name] and @defs[name].is_macro
{:fn, :arg_names} = @defs[name] add @run_macro(tree, "Expression")
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
else 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) table.insert args, 1, utils.repr(name, true)
comma_separated_items("game:call(", args, ")") add @@comma_separated_items("game:call(", args, ")")
when "String" when "String"
escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r" escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r"
unescaped = tree.value\gsub("\\(.)", ((c)-> escapes[c] or c)) unescaped = tree.value\gsub("\\(.)", ((c)-> escapes[c] or c))
lua utils.repr(unescaped, true) add utils.repr(unescaped, true)
when "Longstring" when "Longstring"
-- TODO: handle comments here?
result = [line for line in tree.value\gmatch("[ \t]*|([^\n]*)")] 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" when "Number"
lua tree.value add tree.value
when "List" when "List"
if #tree.value == 0 if #tree.value == 0
lua "{}" add "{}"
elseif #tree.value == 1 elseif #tree.value == 1
lua "{#{transform(tree.value)}}" add "{#{to_lua(tree.value)}}"
else 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" when "Var"
lua "vars[#{utils.repr(tree.value,true)}]" add "vars[#{utils.repr(tree.value,true)}]"
else else
error("Unknown/unimplemented thingy: #{tree.type}") 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 return ret
_yield_tree: (tree, indent_level=0)=> _yield_tree: (tree, indent_level=0)=>
ind = (s) -> (" ")\rep(indent_level)..s ind = (s) -> INDENT\rep(indent_level)..s
switch tree.type switch tree.type
when "File" when "File"
coroutine.yield(ind"File:") coroutine.yield(ind"File:")
@ -427,7 +447,8 @@ class Game
compile: (src)=> compile: (src)=>
tree = self\parse(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 return code
test: (src, expected)=> test: (src, expected)=>

View File

@ -31,6 +31,26 @@ utils = {
split: (str, sep="%s")-> split: (str, sep="%s")->
[chunk for chunk in str\gmatch("[^#{sep}]+")] [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)] keys: (t)-> [k for k in pairs(t)]
values: (t)-> [v for _,v in pairs(t)] values: (t)-> [v for _,v in pairs(t)]