aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2017-09-12 20:00:19 -0700
committerBruce Hill <bitbucket@bruce-hill.com>2017-09-12 20:00:19 -0700
commitaa3401ab215cd2d3e60608da385477f154c58175 (patch)
treeb798ccc1857a91ac905acb6358a02131b6d7eac1
parentc0efa6c7d12e3f9749f6e2dafe507799aa47fad6 (diff)
Major cleanup and consolidation.
-rwxr-xr-xcore.moon290
-rw-r--r--core.nom250
-rwxr-xr-xnomic.moon165
-rw-r--r--tutorial.nom211
4 files changed, 606 insertions, 310 deletions
diff --git a/core.moon b/core.moon
deleted file mode 100755
index bb4ba50..0000000
--- a/core.moon
+++ /dev/null
@@ -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
diff --git a/core.nom b/core.nom
new file mode 100644
index 0000000..45b4116
--- /dev/null
+++ b/core.nom
@@ -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
diff --git a/nomic.moon b/nomic.moon
index e9a232c..73b48c2 100755
--- a/nomic.moon
+++ b/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
diff --git a/tutorial.nom b/tutorial.nom
new file mode 100644
index 0000000..1b25f69
--- /dev/null
+++ b/tutorial.nom
@@ -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"
+