Major cleanup and consolidation.
This commit is contained in:
parent
c0efa6c7d1
commit
aa3401ab21
290
core.moon
290
core.moon
@ -1,290 +0,0 @@
|
||||
#!/usr/bin/env moon
|
||||
Nomic = require 'nomic'
|
||||
utils = require 'utils'
|
||||
|
||||
|
||||
class PermissionNomic extends Nomic
|
||||
new: (...)=>
|
||||
super(...)
|
||||
@callstack = {}
|
||||
|
||||
call: (fn_name,...)=>
|
||||
fn_info = @defs[fn_name]
|
||||
if fn_info == nil
|
||||
error "Attempt to call undefined function: #{fn_name}"
|
||||
unless self\check_permission(fn_name)
|
||||
error "You do not have the authority to call: #{fn_name}"
|
||||
table.insert @callstack, fn_name
|
||||
{:fn, :arg_names} = fn_info
|
||||
args = {name, select(i,...) for i,name in ipairs(arg_names)}
|
||||
if @debug
|
||||
print "Calling #{fn_name} with args: #{utils.repr(args)}"
|
||||
ret = fn(self, args)
|
||||
table.remove @callstack
|
||||
return ret
|
||||
|
||||
check_permission: (fn_name)=>
|
||||
fn_info = @defs[fn_name]
|
||||
if fn_info == nil
|
||||
error "Undefined function: #{fn_name}"
|
||||
if fn_info.whitelist == nil then return true
|
||||
for caller in *@callstack
|
||||
if fn_info.whitelist[caller]
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
g = PermissionNomic()
|
||||
|
||||
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
|
||||
whitelist = if type(vars.whitelist) == 'string' then {vars.whitelist} else vars.whitelist
|
||||
whitelist = {w,true for w in *whitelist}
|
||||
for fn in *fns
|
||||
fn_info = @defs[fn]
|
||||
if fn_info == nil
|
||||
print "Undefined function: #{fn}"
|
||||
continue
|
||||
unless self\check_permission(fn)
|
||||
print "You do not have permission to restrict function: #{fn}"
|
||||
continue
|
||||
@defs[fn].whitelist = whitelist
|
||||
|
||||
g\def {"allow %whitelist to %fn"}, (vars)=>
|
||||
fns = if type(vars.fn) == 'string' then {vars.fn} else vars.fn
|
||||
whitelist = if type(vars.whitelist) == 'string' then {vars.whitelist} else vars.whitelist
|
||||
for fn in *fns
|
||||
fn_info = @defs[fn]
|
||||
if fn_info == nil
|
||||
print "Undefined function: #{fn}"
|
||||
continue
|
||||
if fn_info.whitelist == nil
|
||||
print "Function is already allowed by everyone: #{fn}"
|
||||
continue
|
||||
unless self\check_permission(fn)
|
||||
print "You do not have permission to grant permissions for function: #{fn}"
|
||||
continue
|
||||
for w in *whitelist do fn_info.whitelist[w] = true
|
||||
|
||||
g\def {"forbid %blacklist to %fn"}, (vars)=>
|
||||
fns = if type(vars.fn) == 'string' then {vars.fn} else vars.fn
|
||||
blacklist = if type(vars.blacklist) == 'string' then {vars.blacklist} else vars.blacklist
|
||||
for fn in *fns
|
||||
fn_info = @defs[fn]
|
||||
if fn_info == nil
|
||||
print "Undefined function: #{fn}"
|
||||
continue
|
||||
if fn_info.whitelist == nil
|
||||
print "Cannot remove items from a whitelist when there is no whitelist on function: #{fn}"
|
||||
continue
|
||||
unless self\check_permission(fn)
|
||||
print "You do not have permission to restrict function: #{fn}"
|
||||
continue
|
||||
for b in *blacklist do fn_info.whitelist[b] = nil
|
||||
|
||||
|
||||
g\def {"say %x", "print %x"}, (vars)=>
|
||||
print(utils.repr(vars.x))
|
||||
|
||||
g\def [[printf %str]], (args)=>
|
||||
for s in *args.str do io.write(utils.repr(s))
|
||||
io.write("\n")
|
||||
|
||||
g\def [[concat %strs]], (vars)=>
|
||||
return table.concat([utils.repr(s) for s in *vars.strs], "")
|
||||
|
||||
g\def [[quote %str]], (vars)=>
|
||||
return utils.repr(vars.str, true)
|
||||
|
||||
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, kind)=>
|
||||
if kind == "Expression"
|
||||
error("Cannot set a variable in an expression.")
|
||||
return "vars[#{@tree_to_lua(vars.varname)}] = #{@tree_to_lua(vars.value)}", true
|
||||
|
||||
singleton = (aliases, 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)=>
|
||||
return "(#{@tree_to_lua(vars.x)} #{op} #{@tree_to_lua(vars.y)})"
|
||||
|
||||
unary = (ops)->
|
||||
for op in *ops
|
||||
g\defmacro "#{op} %x", (vars)=>
|
||||
return "#{op}(#{@tree_to_lua(vars.x)})"
|
||||
|
||||
singleton {"true","yes"}, "true"
|
||||
singleton {"false","no"}, "false"
|
||||
singleton {"nil","null","nop","pass"}, "nil"
|
||||
infix{"+","-","*","/","==",{"!=","~="},"<","<=",">",">=","^","and","or",{"mod","%"}}
|
||||
unary{"-","#","not"}
|
||||
g\def [[%x == %y]], (args)=> utils.equivalent(args.x, args.y)
|
||||
|
||||
g\def "rule %spec %body", (vars)=>
|
||||
self\def vars.spec, vars.body
|
||||
|
||||
-- TODO: write help
|
||||
|
||||
|
||||
g\def [[random]], -> math.random()
|
||||
|
||||
g\def [[sum %items]], (args)=> utils.sum(args.items)
|
||||
g\def [[all %items]], (args)=> utils.all(args.items)
|
||||
g\def [[any %items]], (args)=> utils.any(args.items)
|
||||
g\def {[[average %items]], [[avg %items]]}, (args)=> utils.sum(items)/#items
|
||||
g\def {[[min %items]], [[smallest %items]], [[lowest %items]], [[fewest %items]]}, (args)=>
|
||||
utils.min(args.items)
|
||||
|
||||
g\def {[[max %items]], [[largest %items]], [[highest %items]], [[most %items]]}, (args)=>
|
||||
utils.max(args.items)
|
||||
|
||||
g\def {[[argmin %items]]}, (args)=>
|
||||
utils.min(args.items, ((i)->i[2]))
|
||||
g\def {[[argmax %items]]}, (args)=>
|
||||
utils.max(args.items, ((i)->i[2]))
|
||||
|
||||
g\def {[[min %items with respect to %keys]]}, (args)=>
|
||||
utils.min(args.items, args.keys)
|
||||
g\def {[[max %items with respect to %keys]]}, (args)=>
|
||||
utils.max(args.items, args.keys)
|
||||
|
||||
g\def {[[%index st in %list]], [[%index nd in %list]], [[%index rd in %list]], [[%index th in %list]]}, (args)=>
|
||||
with args
|
||||
if type(.list) != 'table'
|
||||
print "Not a list: #{.list}"
|
||||
return
|
||||
.list[.index]
|
||||
|
||||
g\def {[[index of %item in %list]]}, (args)=>
|
||||
with args
|
||||
if type(.list) != 'table'
|
||||
print "Not a list: #{.list}"
|
||||
return
|
||||
utils.key_for(args.list, args.item)
|
||||
|
||||
g\run [=[
|
||||
rule ["%item is in %list", "%list contains %item"]: (index of %item in %list) != (nil)
|
||||
]=]
|
||||
|
||||
g\def {[[# %list]], [[length of %list]], [[size of %list]]}, (args)=>
|
||||
with args
|
||||
if type(.list) != 'table'
|
||||
print "Not a list: #{.list}"
|
||||
return
|
||||
return #(.list)
|
||||
|
||||
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, old_loopval = {}, vars[#{@tree_to_lua(vars.varname)}]
|
||||
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.value)}
|
||||
table.insert(comprehension, ret)
|
||||
end
|
||||
vars[#{@tree_to_lua(vars.varname)}] = old_loopval
|
||||
return comprehension
|
||||
end)(game, vars)"
|
||||
else
|
||||
return "
|
||||
do
|
||||
local comprehension, old_loopval = {}, vars[#{@tree_to_lua(vars.varname)}]
|
||||
for i, value in ipairs(#{@tree_to_lua(vars.iterable)}) do
|
||||
vars[#{@tree_to_lua(vars.varname)}] = value
|
||||
#{@tree_to_lua(vars.body.value.value)}
|
||||
end
|
||||
vars[#{@tree_to_lua(vars.varname)}] = old_loopval
|
||||
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: nil
|
||||
]]
|
||||
|
||||
g\simplemacro "unless %condition %body", [[
|
||||
if (not %condition) %body
|
||||
..else: nil
|
||||
]]
|
||||
|
||||
g\def [[do %action]], (vars)=> return vars.action(self,vars)
|
||||
|
||||
|
||||
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\\nBut got:\\n'..got)
|
||||
end
|
||||
end", true
|
||||
|
||||
return g
|
250
core.nom
Normal file
250
core.nom
Normal file
@ -0,0 +1,250 @@
|
||||
(# Global import #)
|
||||
lua block ".."
|
||||
|utils = require('utils')
|
||||
|
||||
lua block ".."
|
||||
|compiler:def("rule %spec %body", function(compiler, vars)
|
||||
| return compiler:def(vars.spec, vars.body)
|
||||
|end)
|
||||
|
||||
rule "macro %spec %body":
|
||||
lua block ".."
|
||||
|compiler:defmacro(vars.spec, vars.body)
|
||||
|
||||
rule "macro block %spec %body":
|
||||
lua block ".."
|
||||
|do
|
||||
| local spec, body = vars.spec, vars.body
|
||||
| local wrapper = function(compiler, vars, kind)
|
||||
| if kind == "Expression" then
|
||||
| error("Macro: "..spec.." was defined to be a block, but is being used as an expression.")
|
||||
| end
|
||||
| return body(compiler, vars, kind), true
|
||||
| end
|
||||
| compiler:defmacro(spec, wrapper)
|
||||
| return nil
|
||||
|end
|
||||
|
||||
rule ["eval %code", "run %code"]:
|
||||
lua expr "compiler:run(vars.code)"
|
||||
|
||||
macro "source code %code":
|
||||
lua expr "utils.repr(vars.code.src, true)"
|
||||
|
||||
rule "run file %filename":
|
||||
lua block [..]
|
||||
"do"
|
||||
"\n local file = io.open(vars.filename)"
|
||||
"\n return compiler:run(file:read('*a'))"
|
||||
"\nend"
|
||||
|
||||
rule "%tree as lua block":
|
||||
lua block [..]
|
||||
"do return compiler:tree_to_lua(", %tree, ", 'Statement'), true end"
|
||||
|
||||
rule "%tree as lua expr":
|
||||
lua expr [..]
|
||||
"compiler:tree_to_lua(", %tree, ", 'Expression')"
|
||||
|
||||
rule "concat %strs":
|
||||
lua block [..]
|
||||
"local str_bits = {}"
|
||||
"\nfor i,bit in ipairs(vars.strs) do str_bits[i] = utils.repr(bit) end"
|
||||
"\ndo return table.concat(str_bits) end"
|
||||
|
||||
rule "concat %strs with glue %glue":
|
||||
lua block [..]
|
||||
"local str_bits = {}"
|
||||
"\nfor i,bit in ipairs(vars.strs) do str_bits[i] = utils.repr(bit) end"
|
||||
"\ndo return table.concat(str_bits, vars.glue) end"
|
||||
|
||||
macro block "return %return-value":
|
||||
concat ["do return ",%return-value as lua expr," end"]
|
||||
|
||||
macro block "let %varname = %value":
|
||||
concat ["vars[",%varname as lua expr,"] = ",%value as lua expr]
|
||||
|
||||
macro ["true","yes"]: "true"
|
||||
macro ["false","no"]: "false"
|
||||
macro ["nil","null"]: "nil"
|
||||
macro block ["nop", "pass"]: ""
|
||||
macro "%a + %b": concat ["(",%a as lua expr," + ",%b as lua expr,")"]
|
||||
macro "%a + %b + %c": concat ["(",%a as lua expr," + ",%b as lua expr," + ",%c as lua expr,")"]
|
||||
macro "%a + %b + %c + %d": concat ["(",%a as lua expr," + ",%b as lua expr," + ",%c as lua expr," + ",%d as lua expr,")"]
|
||||
macro "%a - %b": concat ["(",%a as lua expr," - ",%b as lua expr,")"]
|
||||
macro "%a * %b": concat ["(",%a as lua expr," * ",%b as lua expr,")"]
|
||||
macro "%a * %b * %c": concat ["(",%a as lua expr," * ",%b as lua expr," * ",%c as lua expr,")"]
|
||||
macro "%a * %b * %c * %d": concat ["(",%a as lua expr," * ",%b as lua expr," * ",%c as lua expr," * ",%d as lua expr,")"]
|
||||
macro "%a / %b": concat ["(",%a as lua expr," / ",%b as lua expr,")"]
|
||||
macro "%a === %b": concat ["(",%a as lua expr," == ",%b as lua expr,")"]
|
||||
macro "%a !== %b": concat ["(",%a as lua expr," ~= ",%b as lua expr,")"]
|
||||
macro "%a < %b": concat ["(",%a as lua expr," < ",%b as lua expr,")"]
|
||||
macro "%a < %b < %c": concat ["((",%a as lua expr," < ",%b as lua expr,") and (",%b as lua expr," < ",%c as lua expr,"))"]
|
||||
macro "%a <= %b < %c": concat ["((",%a as lua expr," <= ",%b as lua expr,") and (",%b as lua expr," < ",%c as lua expr,"))"]
|
||||
macro "%a <= %b <= %c": concat ["((",%a as lua expr," <= ",%b as lua expr,") and (",%b as lua expr," <= ",%c as lua expr,"))"]
|
||||
macro "%a < %b <= %c": concat ["((",%a as lua expr," < ",%b as lua expr,") and (",%b as lua expr," <= ",%c as lua expr,"))"]
|
||||
macro "%a > %b > %c": concat ["((",%a as lua expr," > ",%b as lua expr,") and (",%b as lua expr," > ",%c as lua expr,"))"]
|
||||
macro "%a >= %b > %c": concat ["((",%a as lua expr," >= ",%b as lua expr,") and (",%b as lua expr," > ",%c as lua expr,"))"]
|
||||
macro "%a >= %b >= %c": concat ["((",%a as lua expr," >= ",%b as lua expr,") and (",%b as lua expr," >= ",%c as lua expr,"))"]
|
||||
macro "%a > %b >= %c": concat ["((",%a as lua expr," > ",%b as lua expr,") and (",%b as lua expr," >= ",%c as lua expr,"))"]
|
||||
macro "%a <= %b": concat ["(",%a as lua expr," <= ",%b as lua expr,")"]
|
||||
macro "%a > %b": concat ["(",%a as lua expr," > ",%b as lua expr,")"]
|
||||
macro "%a >= %b": concat ["(",%a as lua expr," >= ",%b as lua expr,")"]
|
||||
macro "%a ^ %b": concat ["(",%a as lua expr," ^ ",%b as lua expr,")"]
|
||||
macro "%a and %b": concat ["(",%a as lua expr," and ",%b as lua expr,")"]
|
||||
macro "%a and %b and %c":
|
||||
concat ["(",%a as lua expr," and ",%b as lua expr," and ",%c as lua expr,")"]
|
||||
macro "%a and %b and %c and %d":
|
||||
concat ["(",%a as lua expr," and ",%b as lua expr," and ",%c as lua expr," and ",%d as lua expr,")"]
|
||||
macro "%a or %b": concat ["(",%a as lua expr," or ",%b as lua expr,")"]
|
||||
macro "%a or %b or %c":
|
||||
concat ["(",%a as lua expr," or ",%b as lua expr," or ",%c as lua expr,")"]
|
||||
macro "%a or %b or %c or %d":
|
||||
concat ["(",%a as lua expr," or ",%b as lua expr," or ",%c as lua expr," or ",%d as lua expr,")"]
|
||||
macro "%a mod %b": concat ["(",%a as lua expr," mod ",%b as lua expr,")"]
|
||||
macro "- %a": concat ["-(",%a as lua expr,")"]
|
||||
macro "not %a": concat ["not (",%a as lua expr,")"]
|
||||
macro "# %a": concat ["#(",%a as lua expr,")"]
|
||||
|
||||
(# This does equivalence checking instead of identity checking. #)
|
||||
rule "%a == %b":
|
||||
lua expr "utils.equivalent(vars.a, vars.b)"
|
||||
macro "%a != %b": concat ["not (",%a as lua expr," == ",%b as lua expr,")"]
|
||||
|
||||
rule "say %str":
|
||||
lua block ["print(", %str, ")"]
|
||||
|
||||
rule "printf %str":
|
||||
lua block ".."
|
||||
|for _,s in ipairs(vars.str) do
|
||||
| io.write(utils.repr(s))
|
||||
|end
|
||||
|io.write("\n")
|
||||
|
||||
rule "do %action":
|
||||
lua expr "vars.action(compiler, setmetatable({}, {__index=vars}))"
|
||||
|
||||
macro block "if %condition %if_body":
|
||||
concat [..]
|
||||
"if ",%condition as lua expr," then"
|
||||
"\n ",lua expr "vars.if_body.value.value" as lua block
|
||||
"\nend"
|
||||
|
||||
macro block "if %condition %if_body else %else_body":
|
||||
concat [..]
|
||||
"if ",%condition as lua expr," then"
|
||||
"\n ",(lua expr "vars.if_body.value.value") as lua block
|
||||
"\nelse"
|
||||
"\n ",(lua expr "vars.else_body.value.value") as lua block
|
||||
"\nend"
|
||||
|
||||
macro "%if_expr if %condition else %else_expr":
|
||||
concat [..]
|
||||
"(function(compiler, vars)"
|
||||
"\n if ",%condition as lua expr," then"
|
||||
"\n return ",%if_expr as lua expr
|
||||
"\n else"
|
||||
"\n return ",%else_expr as lua expr
|
||||
"\n end"
|
||||
"\nend)(compiler, vars)"
|
||||
|
||||
macro block "for %varname in %iterable %body":
|
||||
let "varname" = (%varname as lua expr)
|
||||
concat [..]
|
||||
"do"
|
||||
"\n local old_loopval = vars[", %varname, "]"
|
||||
"\n for i,value in ipairs(", %iterable as lua expr, ") do"
|
||||
"\n vars[", %varname, "] = value"
|
||||
"\n ",(lua expr "vars.body.value.value") as lua block
|
||||
"\n end"
|
||||
"\n vars[", %varname, "] = old_loopval"
|
||||
"\nend"
|
||||
|
||||
rule "%start up to %stop":
|
||||
lua expr "utils.range(vars.start,vars.stop-1)"
|
||||
|
||||
rule ["%start thru %stop", "%start through %stop"]:
|
||||
lua expr "utils.range(vars.start,vars.stop)"
|
||||
|
||||
rule "%start down to %stop":
|
||||
lua expr "utils.range(vars.start,vars.stop+1,-1)"
|
||||
|
||||
rule ["%start down thru %stop", "%start down through %stop"]:
|
||||
lua expr "utils.range(vars.start,vars.stop,-1)"
|
||||
|
||||
|
||||
rule ["random number"]: lua expr "math.random()"
|
||||
rule ["sum of %items"]: lua expr "utils.sum(vars.items)"
|
||||
rule ["product of %items"]: lua expr "utils.product(vars.items)"
|
||||
rule ["all of %items"]: lua expr "utils.all(vars.items)"
|
||||
rule ["any of %items"]: lua expr "utils.any(vars.items)"
|
||||
rule ["avg of %items", "average of %items"]: lua expr "(utils.sum(vars.items)/#vars.items)"
|
||||
rule ["min of %items", "smallest of %items", "lowest of %items"]:
|
||||
lua expr "utils.min(vars.items)"
|
||||
rule ["max of %items", "biggest of %items", "largest of %items", "highest of %items"]:
|
||||
lua expr "utils.min(vars.items)"
|
||||
|
||||
rule ["min of %items with respect to %keys"]:
|
||||
lua expr "utils.min(vars.items, vars.keys)"
|
||||
rule ["max of %items with respect to %keys"]:
|
||||
lua expr "utils.max(vars.items, vars.keys)"
|
||||
|
||||
macro ["%index st in %list", "%index nd in %list", "%index rd in %list", "%index th in %list"]:
|
||||
concat [(%list as lua expr), "[", (%index as lua expr), "]"]
|
||||
|
||||
macro ["%item is in %list", "%list contains %item"]:
|
||||
concat ["(",%list as lua expr,"[",%index as lua expr,"] ~= nil)"]
|
||||
|
||||
macro ["length of %list", "size of %list"]:
|
||||
concat ["#(",%list as lua expr,")"]
|
||||
|
||||
rule "restrict %fn to within %whitelist":
|
||||
lua block ".."
|
||||
|local fns = (type(vars.fn) == 'string') and {vars.fn} or vars.fn
|
||||
|local whitelist = (type(vars.whitelist) == 'string') and {vars.whitelist} or vars.whitelist
|
||||
|local whiteset = {}
|
||||
|for _,w in ipairs(whitelist) do whiteset[w] = true end
|
||||
|for _,fn in ipairs(fns) do
|
||||
| local fn_info = compiler.defs[fn]
|
||||
| if fn_info == nil then
|
||||
| print("Undefined function: "..tostring(fn))
|
||||
| elseif not compiler:check_permission(fn) then
|
||||
| print("You do not have permission to restrict function: "..tostring(fn))
|
||||
| else
|
||||
| compiler.defs[fn].whiteset = whiteset
|
||||
| end
|
||||
|end
|
||||
|
||||
rule "allow %whitelist to use %fn":
|
||||
lua block ".."
|
||||
|local fns = (type(vars.fn) == 'string') and {vars.fn} or vars.fn
|
||||
|local whitelist = (type(vars.whitelist) == 'string') and {vars.whitelist} or vars.whitelist
|
||||
|for _,fn in ipairs(fns) do
|
||||
| local fn_info = compiler.defs[fn]
|
||||
| if fn_info == nil then
|
||||
| print("Undefined function: "..tostring(fn))
|
||||
| elseif fn_info.whiteset == nil then
|
||||
| print("Function is already allowed by everyone: "..tostring(fn))
|
||||
| elseif not compiler:check_permission(fn) then
|
||||
| print("You do not have permission to grant permissions for function: "..tostring(fn))
|
||||
| else
|
||||
| for _,w in ipairs(whitelist) do fn_info.whiteset[w] = true end
|
||||
| end
|
||||
|end
|
||||
|
||||
rule "forbid %blacklist to use %fn":
|
||||
lua block ".."
|
||||
|local fns = (type(vars.fn) == 'string') and {vars.fn} or vars.fn
|
||||
|local blacklist = (type(vars.blacklist) == 'string') and {vars.blacklist} or vars.blacklist
|
||||
|for _,fn in ipairs(fns) do
|
||||
| local fn_info = compiler.defs[fn]
|
||||
| if fn_info == nil then
|
||||
| print("Undefined function: "..tostring(fn))
|
||||
| elseif fn_info.whiteset == nil then
|
||||
| print("Cannot remove items from a whitelist when there is no whitelist on function: "..tostring(fn))
|
||||
| elseif not compiler:check_permission(fn) then
|
||||
| print("You do not have permission to restrict function: "..tostring(fn))
|
||||
| else
|
||||
| for _,b in ipairs(blacklist) do fn_info.whiteset[b] = nil end
|
||||
| end
|
||||
|end
|
165
nomic.moon
165
nomic.moon
@ -2,7 +2,6 @@
|
||||
re = require 're'
|
||||
lpeg = require 'lpeg'
|
||||
utils = require 'utils'
|
||||
moon = require 'moon'
|
||||
|
||||
-- TODO:
|
||||
-- string interpolation
|
||||
@ -84,10 +83,12 @@ make_parser = (lingo, extra_definitions)->
|
||||
})
|
||||
return re.compile lingo, defs
|
||||
|
||||
class Game
|
||||
class Compiler
|
||||
new:(parent)=>
|
||||
@defs = setmetatable({}, {__index:parent and parent.defs})
|
||||
@callstack = {}
|
||||
@debug = false
|
||||
@initialize_core!
|
||||
|
||||
call: (fn_name,...)=>
|
||||
fn_info = @defs[fn_name]
|
||||
@ -95,13 +96,30 @@ class Game
|
||||
error "Attempt to call undefined function: #{fn_name}"
|
||||
if fn_info.is_macro
|
||||
error "Attempt to call macro at runtime: #{fn_name}"
|
||||
unless @check_permission(fn_name)
|
||||
error "You do not have the authority to call: #{fn_name}"
|
||||
table.insert @callstack, fn_name
|
||||
{:fn, :arg_names} = fn_info
|
||||
args = {name, select(i,...) for i,name in ipairs(arg_names)}
|
||||
if @debug
|
||||
print "Calling #{fn_name} with args: #{utils.repr(args)}"
|
||||
return fn(self, args)
|
||||
ret = fn(self, args)
|
||||
table.remove @callstack
|
||||
return ret
|
||||
|
||||
check_permission: (fn_name)=>
|
||||
fn_info = @defs[fn_name]
|
||||
if fn_info == nil
|
||||
error "Undefined function: #{fn_name}"
|
||||
if fn_info.whiteset == nil then return true
|
||||
for caller in *@callstack
|
||||
if fn_info.whiteset[caller]
|
||||
return true
|
||||
return false
|
||||
|
||||
def: (spec, fn)=>
|
||||
if @debug
|
||||
print "Defining rule: #{spec}"
|
||||
invocations,arg_names = @get_invocations spec
|
||||
fn_info = {:fn, :arg_names, :invocations, is_macro:false}
|
||||
for invocation in *invocations
|
||||
@ -133,6 +151,7 @@ class Game
|
||||
code = @compile(text)
|
||||
if @debug
|
||||
print "\nGENERATED LUA CODE:\n#{code}"
|
||||
[==[
|
||||
lua_thunk, err = loadstring(code)
|
||||
if not lua_thunk
|
||||
error("Failed to compile generated code:\n#{code}\n\n#{err}")
|
||||
@ -140,14 +159,9 @@ class Game
|
||||
if @debug
|
||||
print("Running...")
|
||||
return action(self, {})
|
||||
]==]
|
||||
code
|
||||
|
||||
run_debug:(text)=>
|
||||
old_debug = @debug
|
||||
@debug = true
|
||||
ret = @run(text)
|
||||
@debug = old_debug
|
||||
return ret
|
||||
|
||||
parse: (str)=>
|
||||
if @debug
|
||||
print("PARSING:\n#{str}")
|
||||
@ -210,8 +224,8 @@ class Game
|
||||
return tree
|
||||
|
||||
tree_to_value: (tree)=>
|
||||
code = "return (function(game, vars)\nreturn #{@tree_to_lua(tree)}\nend)"
|
||||
lua_thunk, err = loadstring(code)
|
||||
code = "return (function(compiler, vars)\nreturn #{@tree_to_lua(tree)}\nend)"
|
||||
lua_thunk, err = load(code)
|
||||
if not lua_thunk
|
||||
error("Failed to compile generated code:\n#{code}\n\n#{err}")
|
||||
return (lua_thunk!)(self, {})
|
||||
@ -229,22 +243,30 @@ class Game
|
||||
|
||||
switch tree.type
|
||||
when "File"
|
||||
add [[return (function(game, vars)
|
||||
add [[return (function(compiler, vars)
|
||||
local ret]]
|
||||
add to_lua(tree.value.body)
|
||||
vars = {}
|
||||
for statement in *tree.value.body.value
|
||||
code = to_lua(statement)
|
||||
-- Run the fuckers as we go
|
||||
lua_thunk, err = loadstring("return (function(compiler, vars)\n#{code}\nend)")
|
||||
if not lua_thunk
|
||||
error("Failed to compile generated code:\n#{code}\n\n#{err}")
|
||||
lua_thunk!(self, vars)
|
||||
add code
|
||||
add [[
|
||||
return ret
|
||||
end)
|
||||
]]
|
||||
|
||||
when "Block"
|
||||
for chunk in *tree.value
|
||||
add to_lua(chunk)
|
||||
for statement in *tree.value
|
||||
add to_lua(statement)
|
||||
|
||||
when "Thunk"
|
||||
assert tree.value.type == "Block", "Non-block value in Thunk"
|
||||
add [[
|
||||
(function(game, vars)
|
||||
(function(compiler, vars)
|
||||
local ret]]
|
||||
add to_lua(tree.value)
|
||||
add [[
|
||||
@ -273,7 +295,7 @@ class Game
|
||||
else
|
||||
args = [to_lua(a) for a in *tree.value when a.type != "Word"]
|
||||
table.insert args, 1, utils.repr(name, true)
|
||||
add @@comma_separated_items("game:call(", args, ")")
|
||||
add @@comma_separated_items("compiler:call(", args, ")")
|
||||
|
||||
when "String"
|
||||
escapes = n:"\n", t:"\t", b:"\b", a:"\a", v:"\v", f:"\f", r:"\r"
|
||||
@ -333,10 +355,14 @@ class Game
|
||||
name = @fn_name_from_tree(tree)
|
||||
unless @defs[name] and @defs[name].is_macro
|
||||
error("Macro not found: #{name}")
|
||||
unless @check_permission(name)
|
||||
error "You do not have the authority to call: #{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)}
|
||||
table.insert @callstack, name
|
||||
ret, manual_mode = fn(self, args, kind)
|
||||
table.remove @callstack
|
||||
if not ret
|
||||
error("No return value for macro: #{name}")
|
||||
if kind == "Statement" and not manual_mode
|
||||
@ -413,10 +439,15 @@ class Game
|
||||
table.insert(result, line)
|
||||
return table.concat result, "\n"
|
||||
|
||||
compile: (src)=>
|
||||
compile: (src, output_file=nil)=>
|
||||
if @debug
|
||||
print "COMPILING:\n#{src}"
|
||||
tree = @parse(src)
|
||||
assert tree, "Tree failed to compile: #{src}"
|
||||
code = @tree_to_lua(tree)
|
||||
if output_file
|
||||
output = io.open(output_file, "w")
|
||||
output\write(code)
|
||||
return code
|
||||
|
||||
test: (src, expected)=>
|
||||
@ -437,4 +468,98 @@ class Game
|
||||
error"TEST FAILED!\nSource:\n#{test_src}\nExpected:\n#{expected}\n\nGot:\n#{got}"
|
||||
|
||||
|
||||
return Game
|
||||
initialize_core: =>
|
||||
-- Sets up some core functionality
|
||||
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)
|
||||
|
||||
@defmacro [[lua block %lua_code]], (vars, kind)=>
|
||||
if kind == "Expression" then error("Expected to be in statement.")
|
||||
lua_code = vars.lua_code.value
|
||||
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
|
||||
|
||||
@defmacro [[lua expr %lua_code]], (vars, kind)=>
|
||||
lua_code = vars.lua_code.value
|
||||
switch lua_code.type
|
||||
when "List"
|
||||
-- TODO: handle subexpressions
|
||||
return table.concat([as_lua_code(@, i.value) for i in *lua_code.value])
|
||||
else
|
||||
return as_lua_code(@, lua_code)
|
||||
|
||||
@def "rule %spec %body", (vars)=>
|
||||
@def vars.spec, vars.body
|
||||
|
||||
@defmacro [[macro %spec %body]], (vars, kind)=>
|
||||
if kind == "Expression"
|
||||
error("Macro definitions cannot be used as expressions.")
|
||||
@defmacro @tree_to_value(vars.spec), @tree_to_value(vars.body)
|
||||
return "", true
|
||||
|
||||
@defmacro [[macro block %spec %body]], (vars, kind)=>
|
||||
if kind == "Expression"
|
||||
error("Macro definitions cannot be used as expressions.")
|
||||
invocation = @tree_to_value(vars.spec)
|
||||
fn = @tree_to_value(vars.body)
|
||||
@defmacro invocation, ((vars,kind)=>
|
||||
if kind == "Expression"
|
||||
error("Macro: #{invocation} was defined to be a block, not an expression.")
|
||||
return fn(@,vars,kind), true)
|
||||
return "", true
|
||||
|
||||
@def "run file %filename", (vars)=>
|
||||
file = io.open(vars.filename)
|
||||
return @run(file\read('*a'))
|
||||
|
||||
|
||||
-- Run on the command line via "./nomic.moon input_file.nom" to execute
|
||||
-- and "./nomic.moon input_file.nom output_file.lua" to compile (use "-" to compile to stdout)
|
||||
if arg[1]
|
||||
c = Compiler()
|
||||
input = io.open(arg[1])\read("*a")
|
||||
-- Kinda hacky, if run via "./nomic.moon file.nom -", then silence print and io.write
|
||||
-- during execution and re-enable them to print out the generated source code
|
||||
_print = print
|
||||
_io_write = io.write
|
||||
if arg[2] == "-"
|
||||
export print
|
||||
nop = ->
|
||||
print, io.write = nop, nop
|
||||
code = c\run(input)
|
||||
if arg[2]
|
||||
output = if arg[2] == "-"
|
||||
export print
|
||||
print, io.write = _print, _io_write
|
||||
io.output()
|
||||
else io.open(arg[2], 'w')
|
||||
|
||||
output\write [[
|
||||
local load = function()
|
||||
]]
|
||||
output\write(code)
|
||||
output\write [[
|
||||
|
||||
end
|
||||
local utils = require('utils')
|
||||
local Compiler = require('nomic')
|
||||
local c = Compiler(require('core'))
|
||||
load()(c, {})
|
||||
]]
|
||||
|
||||
return Compiler
|
||||
|
211
tutorial.nom
Normal file
211
tutorial.nom
Normal file
@ -0,0 +1,211 @@
|
||||
(# Comments use (# ... #), and can be nested #)
|
||||
|
||||
(# Import files like so: #)
|
||||
run file "core.nom"
|
||||
|
||||
(# Numbers: #)
|
||||
23
|
||||
4.5
|
||||
(# Since this language cross-compiles to lua, integers and floating point numbers are
|
||||
both represented using the same primitive. #)
|
||||
|
||||
(# Strings: #)
|
||||
"asdf"
|
||||
".."
|
||||
|This is a multi-line string
|
||||
|that starts with ".." and includes each indented line that starts with a "|"
|
||||
|until the indentation ends
|
||||
|
||||
(# Lists: #)
|
||||
[1,2,3]
|
||||
[..]
|
||||
"like multi-line strings, lists have an indented form", "that can use commas too"
|
||||
"or just newlines to separate items"
|
||||
5
|
||||
6,7,8
|
||||
|
||||
(# Function calls: #)
|
||||
say "Hello world!"
|
||||
|
||||
(# Function definition: #)
|
||||
rule "say both %first and also %second":
|
||||
(# Variables use the "%" sign: #)
|
||||
say %first
|
||||
say %second
|
||||
|
||||
(# Function calls can have parts of the function's name spread throughout.
|
||||
Everything that's not a literal value is treated as part of the function's name #)
|
||||
say both "Hello" and also "again!"
|
||||
|
||||
(# Functions can even have their name at the end: #)
|
||||
rule "%what-she-said is what she said":
|
||||
say %what-she-said
|
||||
say "-- she said"
|
||||
"Howdy pardner" is what she said
|
||||
|
||||
(# The language only reserves []{}().,:;% as special characters, so functions and variables
|
||||
can have really funky names! #)
|
||||
rule ">> %foo-bar###^ --> %@@& _~-^-~_~-^ %1 !":
|
||||
say %foo-bar###^
|
||||
say %@@&
|
||||
say %1
|
||||
>> "wow" --> "so flexible!" _~-^-~_~-^ "even numbers can be variables!" !
|
||||
(# Though literals can't be used in function names #)
|
||||
|
||||
(# Math and logic operations are just treated the same as function calls in the syntax #)
|
||||
say (2 + 3)
|
||||
(# So it's easy to define your own operators #)
|
||||
rule "%a ++ %b": 2 * (%a + %b)
|
||||
say (2 ++ 3)
|
||||
|
||||
|
||||
(# Code blocks start with ":" and either continue until the end of the line
|
||||
or are indented blocks #)
|
||||
|
||||
(# One liner: #)
|
||||
: say "hi"
|
||||
|
||||
(# Block version: #)
|
||||
:
|
||||
say "one"
|
||||
say "two"
|
||||
|
||||
(# So the function definitions above are actually just passing a regular string, like
|
||||
"say both %first and also %second", and a code block to a function called "rule % %"
|
||||
that takes two arguments. #)
|
||||
|
||||
(# Line continuations work by either ending a line with ".." and continuing with an indented block: #)
|
||||
say..
|
||||
both "Tom" and
|
||||
also
|
||||
"Sawyer"
|
||||
|
||||
(# Or by starting the next line with ".." #)
|
||||
say both "Bruce"
|
||||
..and also "Lee"
|
||||
|
||||
(# This can be mixed and matched: #)
|
||||
say both..
|
||||
"Rick"
|
||||
..and also..
|
||||
"Moranis"
|
||||
|
||||
(# And combined with the block forms of literals: #)
|
||||
say both ".."
|
||||
|Four score and seven years ago our fathers brought forth, upon this continent,
|
||||
|a new nation, conceived in liberty, and dedicated to the proposition that
|
||||
|"all men are created equal"
|
||||
..and also..
|
||||
"-- Abraham Lincoln"
|
||||
|
||||
rule "my favorite number": return 23
|
||||
(# Subexpressions are wrapped in parentheses: #)
|
||||
(# printf takes a list of bits that are converted to strings and concatenated together, and printed #)
|
||||
printf ["My favorite number is ", my favorite number]
|
||||
(# There's a multi-line indented block form for subexpressions too: #)
|
||||
printf [..]
|
||||
"My favorite number is still ", (..)
|
||||
my favorite
|
||||
number
|
||||
|
||||
(# There's a few macros in the language for things like conditional branches and logic/math
|
||||
operations, but they can be thought of as basically the same as functions.
|
||||
There are no keywords in the language! #)
|
||||
if (1 < 10):
|
||||
say "One is less than ten"
|
||||
..else:
|
||||
say "One is not less than ten"
|
||||
|
||||
(# Breakdown of the above: #)
|
||||
(# Function call (actually a macro) to "if % % else %" #)
|
||||
(# First argument is a subexpression that is a function call (also a macro) to "% < %"
|
||||
that performs a comparison on its arguments, 1 and 10 #)
|
||||
(# Second argument is a block of code that includes a function call to "say %", the "if" body #)
|
||||
(# Third argument is a block of code that includes a different function call to "say %", the "else" body #)
|
||||
|
||||
(# Line continuations can be used for "elseif" #)
|
||||
if (1 > 10):
|
||||
say "First condition"
|
||||
..else: if (1 > 5):
|
||||
say "Second condition"
|
||||
..else:
|
||||
say "Last condition"
|
||||
|
||||
(# ^that's the same as: #)
|
||||
if (1 > 10):
|
||||
say "First condition"
|
||||
..else:
|
||||
if (1 > 5):
|
||||
say "Second condition"
|
||||
..else:
|
||||
say "Last condition"
|
||||
|
||||
(# Variables are modified with a macro, "let % = %" #)
|
||||
let "numbers" = [5,6,7]
|
||||
|
||||
(# Looping: #)
|
||||
printf ["Looping over: ",%numbers,"!"]
|
||||
for "number" in %numbers:
|
||||
say (%number + 100)
|
||||
|
||||
rule "sing %starting-bottles bottles of beer":
|
||||
for "n" in (%starting-bottles down through 0):
|
||||
printf [..]
|
||||
(%n if (%n > 0) else "No more")
|
||||
(" bottle" if (%n == 1) else " bottles")
|
||||
" of beer on the wall."
|
||||
("" if (%n == 0) else " Take one down, pass it around...")
|
||||
|
||||
sing 9 bottles of beer
|
||||
|
||||
|
||||
(# Note that because math and logic operations are just macros, they require a lot
|
||||
of parentheses to disambiguate. There's no PEMDAS. #)
|
||||
say (5 + (4 * (- (1 + (6 + 2)))))
|
||||
(# For convenience, +,*,"and", and "or" have been hand defined to work with up to 4 operands: #)
|
||||
1 + 2 + 3 + 4
|
||||
1 * 2 * 3 * 4
|
||||
1 and 2 and 3 and 4
|
||||
1 or 2 or 3 or 4
|
||||
(# Longer lists can use "sum of %", "product of %", "all of %", and "any of %", respectively, or lots of parentheses. #)
|
||||
sum of [1,2,3,4,5,6,7]
|
||||
product of [1,2,3,4,5,6,7]
|
||||
all of [1,1,1,1,0,1,1]
|
||||
any of [0,0,0,0,1,0,0]
|
||||
(# And 3-operand chained inequality comparisons have been defined: #)
|
||||
1 < 2 <= 3
|
||||
|
||||
|
||||
(# Macros: #)
|
||||
(# The "lua block %" and "lua expr %" macros can be used to write raw lua code: #)
|
||||
rule "say time":
|
||||
lua block ".."
|
||||
|io.write("The OS time is: ")
|
||||
|io.write(tostring(os.time()).."\n")
|
||||
say time
|
||||
printf ["Math expression: ", lua expr "(1 + 2*3 + 3*4)^2"]
|
||||
(# In the lua environment, "vars" can be used to get local variables/function args, and "game"
|
||||
can be used to access the compiler, function defs, and other things #)
|
||||
rule "square root of %n":
|
||||
return (lua expr "math.sqrt(vars.n)")
|
||||
printf ["the square root of 2 is ", square root of 2]
|
||||
|
||||
(# Macros can be defined as functions that take unprocessed syntax trees and return lua code #)
|
||||
(# "macro block %" is for defining macros that produce blocks of code, not values #)
|
||||
macro block "unless %condition %body":
|
||||
concat [..]
|
||||
(# "% as lua expr" and "% as lua block" are two useful helper functions here. #)
|
||||
"if not (", %condition as lua expr, ") then"
|
||||
(# Extract the inner part of the code block's body and insert it: #)
|
||||
"\n ", (lua expr "vars.body.value.value") as lua block
|
||||
"\nend"
|
||||
|
||||
unless (1 > 10):
|
||||
say "Macros work!"
|
||||
say "It looks like a keyword, but there's no magic here!"
|
||||
|
||||
(# and "macro %" is for defining macros that produce an expression #)
|
||||
macro "%value as a boolean":
|
||||
concat ["(not not (", %value as lua expr, "))"]
|
||||
macro "yep": "true"
|
||||
|
Loading…
Reference in New Issue
Block a user