aboutsummaryrefslogtreecommitdiff
path: root/nomic2.moon
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2017-08-18 17:06:47 -0700
committerBruce Hill <bitbucket@bruce-hill.com>2017-08-18 17:06:47 -0700
commit73051b34d9ea46710d1e36be4b867a50ccc01eac (patch)
tree6b1859914a9d9782cf1c6d235cca3722ee3b99f2 /nomic2.moon
parent5fb8fabaf735f9a2ee97f7d07b85293ff8e9c987 (diff)
Reworked to use lua codegen.
Diffstat (limited to 'nomic2.moon')
-rw-r--r--nomic2.moon285
1 files changed, 285 insertions, 0 deletions
diff --git a/nomic2.moon b/nomic2.moon
new file mode 100644
index 0000000..2a03080
--- /dev/null
+++ b/nomic2.moon
@@ -0,0 +1,285 @@
+re = require 're'
+lpeg = require 'lpeg'
+moon = require 'moon'
+type = moon.type
+
+is_list = (t)->
+ i = 0
+ for _ in pairs(t)
+ i += 1
+ if t[i] == nil then return false
+ return true
+
+repr = (x)->
+ if type(x) == 'table'
+ if is_list x
+ "[#{table.concat([repr(i) for i in *x], ", ")}]"
+ else
+ "{#{table.concat(["#{k}: #{v}" for k,v in pairs x], ", ")}}"
+ else
+ tostring(x)
+
+currently_parsing = nil
+macros = nil
+indentation = 0
+indents = ->
+ (" ")\rep(indentation)
+indent = ->
+ export indentation
+ indentation += 1
+dedent = ->
+ export indentation
+ indentation -= 1
+indent_block = (block)->
+ block = block\gsub("\n", "\n"..indents!)
+ return indents!..block
+add_line = (lines, new_line)->
+ table.insert lines, (indents!..new_line)
+
+compactify_invocation = (raw_invocation)->
+ name_bits = {}
+ arg_names = {}
+ for chunk in raw_invocation\gmatch("%S+")
+ if chunk\sub(1,1) == "$"
+ table.insert name_bits, "$"
+ table.insert arg_names, chunk\sub(2,-1)
+ else
+ table.insert name_bits, chunk
+ invocation = table.concat name_bits, " "
+ return invocation, arg_names
+
+Number = (s)-> s
+
+String = (s)-> '"'..s..'"'
+
+List = (t)-> "{" .. table.concat(t, ", ") .. "}"
+
+Var = (s)-> "locals[\"#{s}\"]"
+
+Word = (s)-> setmetatable({type:'word', text:s}, {__tostring:=> error("Cannot convert word \"#{@text}\" to string")})
+
+Conditional = (condition, if_block, else_block)->
+ ret = {}
+ add_line ret, "(function()"
+ indent!
+ add_line ret, "local ret"
+ table.insert ret, indent_block("local condition = #{condition}")
+
+ add_line ret, "if condition then"
+ indent!
+ table.insert ret, indent_block("ret = (#{if_block})(game, locals)")
+ if else_block
+ dedent!
+ add_line ret, "else"
+ indent!
+ table.insert ret, indent_block("ret = (#{else_block})(game, locals)")
+ dedent!
+ add_line ret, "end"
+ add_line ret, "return ret"
+ dedent!
+ add_line ret, "end)()"
+ code = table.concat(ret, "\n")
+ return code
+
+FunctionCall = (tokens)->
+ words = [(if t.type == 'word' then t.text else "$") for t in *tokens]
+ args = [t for t in *tokens when t.type != 'word']
+ rule_name = table.concat(words, " ")
+ if rule_name == "$"
+ error("Empty rule: #{repr(tokens)}")
+
+ if macros[rule_name]
+ return macros[rule_name](unpack(args))
+
+ if #args == 0
+ return indent_block("game:call(\"#{rule_name}\")")
+
+ ret = {}
+ add_line ret, "game:call(\"#{rule_name}\","
+ indent!
+ arg_strs = [indent_block(arg) for arg in *args]
+ dedent!
+ table.insert ret, table.concat(arg_strs, ",\n")
+ add_line ret, ")"
+
+ code = table.concat(ret, "\n")
+ return code
+
+Thunk = (lines)->
+ ret = {}
+ add_line ret, "function(game, locals)"
+ indent!
+ for i,line in ipairs lines
+ if line\match "locals%[\".*\"%] = .*"
+ table.insert ret, indent_block(line)
+ elseif i == #lines
+ table.insert ret, indent_block("return "..line..";")
+ else
+ table.insert ret, indent_block(line..";")
+ dedent!
+ add_line ret, "end"
+ return table.concat(ret, "\n")
+
+lingo = [[
+ actions <- {| {:startPos: {}:} (%ws*) (%nl %ws*)* (action ((%nl %ws*)+ action)*)? (%nl %ws*)* {:endPos: {}:} |} -> Thunk
+ action <- (&conditional conditional) / ({| token (%ws+ token)* %ws* |} -> FunctionCall)
+ conditional <- ("if" !%wordchars %ws* expression %ws* thunk (%ws* "else" %ws* thunk %ws*)?) -> Conditional
+ token <- expression / (!"$" {%wordchars+} -> Word)
+ expression <- number / string / list / variable / thunk / subexpression
+ number <- ('-'? [0-9]+ ("." [0-9]+)?) -> Number
+ string <- ('"' {(("\\" .) / [^"])*} '"') -> String
+ list <- ({| '[' %ws* (%nl %ws*)* (expression (',' %ws* (%nl %ws*)* expression)*)? %ws* (%nl %ws*)* ']' |}) -> List
+ variable <- ("$" {%wordchars+}) -> Var
+ subexpression <- ("(" %ws* action ")")
+ thunk <- ("{" actions "}")
+ keywords <- "if" / "else" / "let"
+]]
+defs = {
+ :Var
+ :Word
+ :String
+ :Number
+ :List
+ :FunctionCall
+ :Thunk
+ :Conditional
+ ws: lpeg.S(" \t")
+ wordchars: lpeg.P(1)-lpeg.S(' \t\n,{}[]()"')
+}
+lingo = re.compile lingo, defs
+
+cross_compile = (nomic_code)->
+ export currently_parsing
+ old_parsing = currently_parsing
+ currently_parsing = nomic_code
+ lua_code = lingo\match nomic_code
+ currently_parsing = old_parsing
+ return lua_code
+
+defaulttable = (table,key)->
+ new = {}
+ table[key] = new
+ return new
+
+class Game
+ new:(@parent)=>
+ @rules = setmetatable({}, {__index:parent and parent.rules or nil})
+ @macros = setmetatable({}, {__index:parent and parent.macros or nil})
+ @arg_names = setmetatable({}, {__index:parent and parent.arg_names or nil})
+ @relations = setmetatable({}, {__index:parent and parent.relations or defaulttable})
+ @invocations = setmetatable({}, {__index:parent and parent.invocations or nil})
+ @authorized = setmetatable({}, {__index:parent and parent.authorized or nil})
+ @callstack = {}
+ @debug = false
+ @you = "Anonymous"
+
+ def: (invocations, fn)=>
+ if not fn then fn = false
+ invocations = if type(invocations) == 'table' then invocations else {invocations}
+ if fn then @invocations[fn] = {}
+ else @invocations[fn] = false
+ for raw_invocation in *invocations
+ invocation, arg_names = compactify_invocation raw_invocation
+ if invocation == "$"
+ error("Anonymous function: #{raw_invocation}")
+ @rules[invocation] = fn
+ if fn
+ table.insert @invocations[fn], invocation
+ @arg_names[invocation] = arg_names
+ else
+ @arg_names[invocation] = false
+
+ macro: (invocation, fn)=>
+ invocation, _ = compactify_invocation invocation
+ @macros[invocation] = fn
+
+ undefine: (invocations)=>
+ @\def invocations, false
+
+ all_aliases: (invocations)=>
+ if type(invocations) != 'table' then invocations = {invocations}
+ all_aliases = {}
+ for i in *invocations
+ all_aliases[i] = true
+ if not @invocations[@rules[i]]
+ error "Could not find aliases of [[#{i}]]"
+ for alias in *@invocations[@rules[i]]
+ all_aliases[alias] = true
+ return [a for a in pairs(all_aliases)]
+
+ canonicalize: (invocations)=>
+ if type(invocations) == 'string'
+ return @invocations[@rules[invocations]][1]
+ canonicals = {}
+ for i in *invocations
+ if @rules[i] == nil
+ error "Attempt to canonicalize invalid invocation: #{i}"
+ canonicals[@invocations[@rules[i]][1]] = true
+ return [c for c in pairs canonicals]
+
+ set_whitelist: (actions, whitelist)=>
+ if is_list whitelist then whitelist = {w,true for w in *whitelist}
+ for action in *@all_aliases(actions)
+ @authorized[action] = whitelist
+
+ check_authorization:(action)=>
+ authority = @authorized[action]
+ return true if authority == nil
+ for call in *@callstack
+ if authority[call] then return true
+ return false
+
+ run: (str, user)=>
+ user or= "anon"
+
+ if @debug
+ print("SOURCE NOMIC CODE:\n#{str}")
+ export macros
+ old_macros = macros
+ macros = (self.macros)
+ lua_code = cross_compile str
+ macros = old_macros
+ if @debug
+ print("\nGENERATED LUA CODE:\n#{lua_code}")
+
+ lua_thunk, err = loadstring("return "..lua_code)
+ if not lua_thunk
+ print("Parsing: "..lua_code)
+ error(err)
+ lua_fn = lua_thunk!
+
+ ret = lua_fn @, {}
+ return ret
+
+ call: (invocation, ...)=>
+ if not @rules[invocation]
+ error "Could not find rule: '#{invocation}'"
+ if not @\check_authorization invocation
+ print "Not authorized to #{invocation} from callstack: #{repr(@callstack)}"
+ return
+ table.insert @callstack, invocation
+ arg_names = @arg_names[invocation]
+ args = {...}
+ ret = (@rules[invocation])(@, {arg_names[i],arg for i, arg in ipairs(args)})
+ table.remove @callstack
+ return ret
+
+ run_debug:(...)=>
+ @debug = true
+ print("Debugging:")
+ @run ...
+ @debug = false
+
+ repl:=>
+ while true
+ io.write(">> ")
+ buf = ""
+ while buf\sub(-2,-1) != "\n\n"
+ buf ..= io.read("*line").."\n"
+ if buf == "exit\n\n" or buf == "quit\n\n"
+ break
+ @\run buf
+
+ repr: repr
+
+return Game