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\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

View File

@ -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)=>

View File

@ -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)]