aboutsummaryrefslogtreecommitdiff
path: root/nomic.moon
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2017-08-18 17:08:15 -0700
committerBruce Hill <bitbucket@bruce-hill.com>2017-08-18 17:08:15 -0700
commitb2d49dde55522429f805590c40f96a95bfc67106 (patch)
treede07c02be2f4ecb256f84c9bbfe7f4216cb404f7 /nomic.moon
parent73051b34d9ea46710d1e36be4b867a50ccc01eac (diff)
Got rid of old versions.
Diffstat (limited to 'nomic.moon')
-rw-r--r--nomic.moon372
1 files changed, 237 insertions, 135 deletions
diff --git a/nomic.moon b/nomic.moon
index cca67e0..2a03080 100644
--- a/nomic.moon
+++ b/nomic.moon
@@ -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