diff options
| author | Bruce Hill <bitbucket@bruce-hill.com> | 2017-08-18 17:08:15 -0700 |
|---|---|---|
| committer | Bruce Hill <bitbucket@bruce-hill.com> | 2017-08-18 17:08:15 -0700 |
| commit | b2d49dde55522429f805590c40f96a95bfc67106 (patch) | |
| tree | de07c02be2f4ecb256f84c9bbfe7f4216cb404f7 /nomic.moon | |
| parent | 73051b34d9ea46710d1e36be4b867a50ccc01eac (diff) | |
Got rid of old versions.
Diffstat (limited to 'nomic.moon')
| -rw-r--r-- | nomic.moon | 372 |
1 files changed, 237 insertions, 135 deletions
@@ -3,173 +3,273 @@ 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) -as_value = (x, game, locals)-> - assert (game and locals), "Shit's fucked" - if type(x) == 'number' or type(x) == 'string' or type(x) == 'table' - return x - ret = x\as_value game, locals - return ret - -class Var - new:(@name)=> - as_value:(game, locals)=> - if locals[@name] == nil - error("Attempt to access undefined variable: #{@name}") - locals[@name] - __tostring:=> "Var(#{@text})" - -class Word - new:(@text)=> - __tostring:=> "Word(#{@text})" - -class Action - new:(tokens)=> - words = [(if type(t) == Word then t.text else "$") for t in *tokens] - @name = table.concat(words, ";") - @args = [t for t in *tokens when type(t) != Word] - - __tostring:=> "Action(#{@name})" - - as_value:(game, locals)=> - assert((game and locals), "f'd up") - ret = @\run game, locals - return ret +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 - run:(game, locals)=> - assert((game and locals), "f'd up") - rule = game.rules[@name] - unless rule - error("Tried to run rule, but couldn't find: #{@name}") - arg_names = rule.arg_names - new_locals = {} - for i, arg in ipairs(@args) - new_locals[arg_names[i]] = as_value(arg, game, locals) - - ret = rule.fn(game, new_locals) - return ret +Number = (s)-> s -class Thunk - new:(@actions, @body_str)=> - if @actions.startPos - assert currently_parsing, "Not currently parsing!" - unless @body_str - @body_str = currently_parsing\sub(@actions.startPos, @actions.endPos-1) +String = (s)-> '"'..s..'"' - as_value:=>@ +List = (t)-> "{" .. table.concat(t, ", ") .. "}" - run:(game, locals)=> - assert((game and locals), "f'd up") - ret = nil - for a in *@actions - ret = a\run game,locals - return ret +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}") - __tostring:=> - --"Thunk(#{table.concat([tostring(a) for a in *@actions], ", ") .. tostring(@actions.returnValue or "") })" - "{#{@body_str}}" + 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 <- {| token (%ws+ token)* %ws* |} -> Action + 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]+)?) -> tonumber - string <- ('"' {(("\\" .) / [^"])*} '"') -> tostring - list <- {| '[' (expression (',' (%ws*) expression)*)? ']' |} + 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: (...)->Var(...) - Word: (...)->Word(...) - Action: (...)->Action(...) - Thunk: (...)->Thunk(...) - tostring: tostring - tonumber: tonumber + :Var + :Word + :String + :Number + :List + :FunctionCall + :Thunk + :Conditional ws: lpeg.S(" \t") - wordchars: lpeg.P(1)-lpeg.S(' \t\n{}[]()"') + wordchars: lpeg.P(1)-lpeg.S(' \t\n,{}[]()"') } lingo = re.compile lingo, defs -class Rule - new:(invocations, action, docstring)=> - if type(action) == Thunk - @body_str = docstring or tostring(action) - @fn = (game,locals)-> action\run(game, locals) - elseif type(action) == 'function' - @body_str = docstring or "<lua function>" - @fn = action - else - error("Invalid action type: #{type(action)}") +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 - eq = (x,y)-> - if #x != #y then return false - for i=1,#x - if x[i] != y[i] then return false - return true +defaulttable = (table,key)-> + new = {} + table[key] = new + return new - @raw_invocations = invocations - @invocations = {} +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 - 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 - if @arg_names - assert(eq(arg_names, @arg_names), "Attempt to use an alias with different variables") + 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 = arg_names - invocation = table.concat name_bits, ";" - table.insert @invocations, invocation - - __tostring:=> - "Rule: #{table.concat(@raw_invocations, " | ")} :=\n #{@body_str\gsub("\n","\n ")}" - + @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)] -class Game - new:(parent)=> - @rules = setmetatable({}, {__index:parent and parent.rules or nil}) - @relations = setmetatable({}, {__index:parent and parent.relations or nil}) + 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] - def: (invocation, action, docstring)=> - invocations = if type(invocation) == 'table' then invocation else {invocation} - rule = Rule(invocations, action, docstring) - for invocation in *rule.invocations - @rules[invocation] = rule + 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 - proxy: (rule)=> - for i in *rule.invocations - @rules[i] = rule + 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)=> - export currently_parsing - old_parsing = currently_parsing - currently_parsing = str - ok,thunk = pcall lingo.match, lingo, str - currently_parsing = old_parsing - if not ok - error(thunk) - - unless thunk - error("failed to parse nomic:\n#{str}") - - prev_you = @you - @you = user or "anon" - ok,ret = pcall thunk.run, thunk, @, {} - @you = prev_you - if not ok - error(ret) - if ret != nil - print("= #{ret}") + 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(">> ") @@ -180,4 +280,6 @@ class Game break @\run buf + repr: repr + return Game |
