aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2017-08-22 01:02:41 -0700
committerBruce Hill <bitbucket@bruce-hill.com>2017-08-22 01:02:41 -0700
commit0e916161b1b47758e0bb15051028d8c92c6446e7 (patch)
tree4d60014362c1101efe99224416e40ca43f5de655
parent4713d7db0dcb713b4a36e63e5a5322829fca7c97 (diff)
Updated core with new syntax.
-rwxr-xr-xcore.moon196
-rwxr-xr-xgame2.moon3
-rw-r--r--nomic.moon666
-rw-r--r--nomic_whitespace.moon437
-rw-r--r--utils.moon1
5 files changed, 490 insertions, 813 deletions
diff --git a/core.moon b/core.moon
index 1ea22dc..8d76915 100755
--- a/core.moon
+++ b/core.moon
@@ -1,165 +1,107 @@
#!/usr/bin/env moon
nomic = require 'nomic'
utils = require 'utils'
-game = nomic()
+g = nomic()
-game\def {[[print $str]], [[say $str]]}, (args)=> print(utils.repr(args.str))
-game\def {[[printf $str]]}, (args)=>
- for s in *args.str do io.write(utils.repr(s))
- io.write("\n")
-
-game\def [[return $value]], (args)=> args.value
-game\macro [[let $var = $value]], (var, value)->
- "locals[#{var}] = #{value}"
+g\def {"say %x", "print %x"}, (vars)=>
+ print(utils.repr(vars.x))
-game\def [[$signature := $body]], (args)=>
- invocations = if type(args.signature) == 'table' then args.signature else {args.signature}
- for i in *invocations
- unless @check_authorization(i)
- print "You are not permitted to redefine this function"
- return
+g\def [[printf %str]], (args)=>
+ for s in *args.str do io.write(utils.repr(s))
+ io.write("\n")
- @\def args.signature, args.body
- print "Defined new rule: #{utils.repr(args.signature)}"
- --print debug.getinfo(args.body, "S").source
+g\defmacro "return %retval", (vars,helpers,ftype)=>
+ with helpers
+ switch ftype
+ when "Expression"
+ error("Cannot use a return statement as an expression")
+ when "Statement"
+ .lua "do return "..(.ded(.transform(vars.retval))).." end"
+ else
+ error"Unknown: #{ftype}"
return nil
-game\def [[compile $body]], (args)=>
- print debug.getinfo(args.body, "S").source
+g\defmacro "let %varname = %value", (vars, helpers, ftype)=>
+ with helpers
+ if ftype == "Expression" then error("Cannot set a variable in an expression.")
+ .lua "vars[#{.ded(.transform(vars.varname))} = #{.ded(.transform(vars.value))}"
return nil
-game\def [[help $invocation]], (args)=>
- print_rule = (invocation, rule)->
- print "\"#{invocation}\" :="
- info = debug.getinfo(rule, "S")
- i = 0
- for line in info.source\gmatch("[^\n]+")
- i+=1
- if i >= info.linedefined+1
- print(line)
- if i >= info.lastlinedefined-1
- return
- print " #{info.short_src}:#{info.linedefined}-#{info.lastlinedefined}"
-
- with args
- if rule = @rules[.invocation]
- print_rule .invocation, rule
- return nil
- words = [w for w in .invocation\gmatch("%S+")]
- match_count = (i)->
- iws = [w for w in i\gmatch("[^ $]+")]
- count = 0
- for w in *words
- for iw in *iws
- if w == iw then count += 1
- count += 1/#iws
- return count
- rules = {}
- invocations = {}
- for i,r in pairs(@rules)
- c = match_count(i)
- if c > (rules[r] or 0)
- rules[r] = c
- invocations[r] = i
- best = [r for r in pairs rules]
- utils.sort best, rules
- if rules[best[1]] > 0
- for r in *best
- if rules[r] < rules[best[1]]
- break
- print_rule invocations[r], r
- return nil
-
-game\macro "true", -> "true"
-game\macro "yes", -> "true"
-game\macro "false", -> "false"
-game\macro "no", -> "false"
-game\macro "nil", -> "nil"
-game\macro "None", -> "nil"
-game\macro "null", -> "nil"
-game\def [[nop]], ((args)=> nil), "... does nothing, returns nil ..."
-
-game\def [[$x == $y]], (args)=> utils.equivalent(args.x, args.y)
-game\run [=[
- ["$x != $y", "$x <> $y", "$x ~= $y"] := {return (not (x == y))}
-]=]
-game\def [[$x < $y]], (args)=> args.x < args.y
-game\def [[$x <= $y]], (args)=> args.x <= args.y
-game\def [[$x > $y]], (args)=> args.x > args.y
-game\def [[$x >= $y]], (args)=> args.x >= args.y
-
-game\macro [[$x + $y]], (x,y)-> "(#{x} + #{y})"
-game\macro [[$x - $y]], (x,y)-> "(#{x} - #{y})"
-game\macro [[$x * $y]], (x,y)-> "(#{x} * #{y})"
-game\macro [[$x / $y]], (x,y)-> "(#{x} / #{y})"
-game\macro [[$x ^ $y]], (x,y)-> "(#{x} ^ #{y})"
-
-game\macro [[$x < $y]], (x,y)-> "(#{x} < #{y})"
-game\macro [[$x <= $y]], (x,y)-> "(#{x} <= #{y})"
-game\macro [[$x > $y]], (x,y)-> "(#{x} > #{y})"
-game\macro [[$x >= $y]], (x,y)-> "(#{x} >= #{y})"
-
-game\macro [[not $x]], (x)-> "(not #{x})"
-game\macro [[$x and $y]], (x,y)-> "(#{x} and #{y})"
-game\macro [[$x or $y]], (x,y)-> "(#{x} or #{y})"
-
-
-[==[
-game\def [[if $condition $body else $else_body]], (args)=>
- with args
- if .condition
- return .body(@, args)
- else return .else_body(@, args)
-
-game\run [=[
- ["if $condition $body", "when $condition $body"] := {if $condition $body else {}}
- ["unless $condition $body"] := {if (not $condition) $body else {}}
- ["unless $condition $body else $else_body"] := {if (not $condition) $body else $else_body}
-]=]
-]==]
-
-game\def [[random]], -> math.random()
-
-game\def [[sum $items]], (args)=> utils.sum(args.items)
-game\def [[all $items]], (args)=> utils.all(args.items)
-game\def [[any $items]], (args)=> utils.any(args.items)
-game\def {[[average $items]], [[avg $items]]}, (args)=> utils.sum(items)/#items
-game\def {[[min $items]], [[smallest $items]], [[lowest $items]], [[fewest $items]]}, (args)=>
+g\defmacro {"true", "yes"}, (vars,helpers,ftype)=> helpers.lua("true")
+g\defmacro {"false", "no"}, (vars,helpers,ftype)=> helpers.lua("false")
+g\defmacro "nil", (vars,helpers,ftype)=> helpers.lua("nil")
+infix = (ops)->
+ for op in *ops
+ alias = op
+ if type(op) == 'table'
+ {alias,op} = op
+ g\defmacro "%x #{op} %y", (vars,helpers,ftype)=>
+ if ftype == "Statement"
+ helpers.lua("ret = (#{helpers.var('x')} #{op} #{helpers.var('y')})")
+ elseif ftype == "Expression"
+ helpers.lua("(#{helpers.var('x')} #{op} #{helpers.var('y')})")
+ else error("Unknown: #{ftype}")
+unary = (ops)->
+ for op in *ops
+ g\defmacro "#{op} %x", (vars,helpers,ftype)=>
+ if ftype == "Statement"
+ helpers.lua("ret = #{op}(#{helpers.var('x')})")
+ elseif ftype == "Expression"
+ helpers.lua("#{op}(#{helpers.var('x')})")
+ else error("Unknown: #{ftype}")
+infix{"+","-","*","/","==",{"!=","~="},"<","<=",">",">=","^","and","or"}
+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
+ print "Defined rule: #{utils.repr(vars.spec)}"
+
+-- 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)
-game\def {[[max $items]], [[largest $items]], [[highest $items]], [[most $items]]}, (args)=>
+g\def {[[max %items]], [[largest %items]], [[highest %items]], [[most %items]]}, (args)=>
utils.max(args.items)
-game\def {[[argmin $items]]}, (args)=>
+g\def {[[argmin %items]]}, (args)=>
utils.min(args.items, ((i)->i[2]))
-game\def {[[argmax $items]]}, (args)=>
+g\def {[[argmax %items]]}, (args)=>
utils.max(args.items, ((i)->i[2]))
-game\def {[[min $items with respect to $keys]]}, (args)=>
+g\def {[[min %items with respect to %keys]]}, (args)=>
utils.min(args.items, args.keys)
-game\def {[[max $items with respect to $keys]]}, (args)=>
+g\def {[[max %items with respect to %keys]]}, (args)=>
utils.max(args.items, args.keys)
-game\def {[[$index st in $list]], [[$index nd in $list]], [[$index rd in $list]], [[$index th in $list]]}, (args)=>
+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]
-game\def {[[index of $item in $list]]}, (args)=>
+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)
-game\run [=[
- ["$item is in $list", "$list contains $item"] := {(index of $item in $list) != (nil)}
+g\run [=[
+rule ["%item is in %list", "%list contains %item"]: (index of %item in %list) != (nil)
]=]
-game\def {[[# $list]], [[length of $list]], [[size of $list]]}, (args)=>
+g\def {[[# %list]], [[length of %list]], [[size of %list]]}, (args)=>
with args
if type(.list) != 'table'
print "Not a list: #{.list}"
diff --git a/game2.moon b/game2.moon
index a95070a..e487991 100755
--- a/game2.moon
+++ b/game2.moon
@@ -1,6 +1,6 @@
#!/usr/bin/env moon
utils = require 'utils'
-Game = require 'nomic_whitespace'
+Game = require 'nomic'
g = Game()
print("===========================================================================================")
@@ -21,7 +21,6 @@ g\defmacro "return %retval", (vars,helpers,ftype)=>
.lua "do return "..(.ded(.transform(vars.retval))).." end"
else
error"Unknown: #{ftype}"
-
return nil
g\defmacro "true", (vars,helpers,ftype)=> helpers.lua("true")
diff --git a/nomic.moon b/nomic.moon
index 12bfb08..a8eb4d5 100644
--- a/nomic.moon
+++ b/nomic.moon
@@ -2,268 +2,440 @@ re = require 're'
lpeg = require 'lpeg'
utils = require 'utils'
moon = require 'moon'
-type = moon.type
-
-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: #{utils.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 = (statements)->
- ret = {}
- add_line ret, "function(game, locals)"
- indent!
- for i,statement in ipairs statements
- -- TODO: clean up? This is a bit hacky. I should *know* if this is a var assignment.
- if statement\match "locals%[\".*\"%] = .*"
- table.insert ret, indent_block(statement)
- elseif i == #statements
- table.insert ret, indent_block("return "..statement..";")
- else
- table.insert ret, indent_block(statement..";")
- 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
+lpeg.setmaxstack 10000 -- whoa
-defaulttable = (table,key)->
- new = {}
- table[key] = new
- return new
+linebreak = lpeg.P("\r")^-1 * lpeg.P("\n")
-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
+get_line_indentation = (line)->
+ indent_amounts = {[" "]:1, ["\t"]:4}
+ with sum = 0
+ leading_space = line\gsub("([\t ]*).*", "%1")
+ for c in leading_space\gmatch "[\t ]"
+ sum += indent_amounts[c]
+
+pos_to_line = (str,pos)->
+ line_no = 1
+ for line in str\gmatch("[^%n]+")
+ if #line >= pos then return line, line_no
+ pos -= (#line + 1)
+ line_no += 1
+ error "Failed to find position #{pos} in str"
+
+pos_to_line_no = (str,pos)->
+ with line = 1
+ for _ in str\sub(1, pos)\gmatch("\n")
+ line += 1
+
+add_indent_tokens = (str)->
+ indent_stack = {0}
+ result = {}
+ -- TODO: Store mapping from new line numbers to old ones
+ defs =
+ linebreak: linebreak
+ process_line: (line)->
+ -- Remove blank lines
+ unless line\match"[^ \t\n]"
+ return
+ indent = get_line_indentation(line)
+ if indent > indent_stack[#indent_stack]
+ table.insert result, "{\n "
+ table.insert indent_stack, indent
+ elseif indent < indent_stack[#indent_stack]
+ dedents = 0
+ tokens = {}
+ while indent < indent_stack[#indent_stack]
+ table.remove indent_stack
+ table.insert tokens, "}"
+ table.insert tokens, " "
+ table.insert result, table.concat(tokens, "\n")
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 utils.is_list whitelist then whitelist = {w,true for w in *whitelist}
- for action in *@all_aliases(actions)
- @authorized[action] = whitelist
+ table.insert result, " "
+ -- Delete leading whitespace
+ --line = line\gsub("[ \t]*", "", 1)
+ -- Delete trailing whitespace and carriage returns
+ line = line\gsub("[ \t\r]*\n", "\n", 1)
+ table.insert result, line
+
+ indentflagger = [=[
+ file <- line*
+ line <- ((string / [^%linebreak])* %linebreak) -> process_line
+ string <- '"' (("\\" .) / [^"])* '"'
+ ]=]
+ indentflagger = re.compile indentflagger, defs
+ indentflagger\match(str)
+ while #indent_stack > 1
+ table.remove indent_stack
+ table.insert result, "}\n"
+ return (table.concat result)\sub(1,-2)
+
+lingo = [=[
+ file <- ({} {| {:body: (" " block) :} ({:errors: errors :})? |} {}) -> File
+ errors <- ({} {.+} {}) -> Errors
+ block <- ({} {| statement (%nodent statement)* |} {}) -> Block
+ statement <- ({} (functioncall / expression) {}) -> Statement
+ one_liner <- ({} {|
+ (({}
+ (({} {|
+ (expression (%word_boundary fn_bit)+) / (word (%word_boundary fn_bit)*)
+ |} {}) -> FunctionCall)
+ {}) -> Statement)
+ |} {}) -> Block
+
+ functioncall <- ({} {| (expression %word_boundary fn_bits) / (word (%word_boundary fn_bits)?) |} {}) -> FunctionCall
+ fn_bit <- (expression / word)
+ fn_bits <-
+ ((".." %ws? (%indent %nodent indented_fn_bits %dedent) (%nodent ".." %ws? fn_bits)?)
+ / (%nodent ".." fn_bit fn_bits)
+ / (fn_bit (%word_boundary fn_bits)?))
+ indented_fn_bits <-
+ fn_bit ((%nodent / %word_boundary) indented_fn_bits)?
- check_authorization:(action)=>
- authority = @authorized[action]
- return true if authority == nil
- for call in *@callstack
- if authority[call] then return true
- return false
+ thunk <-
+ ({} ":" %ws?
+ ((%indent %nodent block %dedent (%nodent "..")?)
+ / (one_liner (%ws? ((%nodent? "..")))?)) {}) -> Thunk
+
+ word <- ({} !number {%wordchar+} {}) -> Word
+ expression <- ({} (string / number / variable / list / thunk / subexpression) {}) -> Expression
- run: (str, user)=>
- user or= "anon"
+ string <- ({} '"' {(("\\" .) / [^"])*} '"' {}) -> String
+ number <- ({} {'-'? [0-9]+ ("." [0-9]+)?} {}) -> Number
+ variable <- ({} ("%" {%wordchar+}) {}) -> Var
+ subexpression <-
+ ("(" %ws? (functioncall / expression) %ws? ")")
+ / ("(..)" %ws? %indent %nodent (expression / (({} {| indented_fn_bits |} {}) -> FunctionCall)) %dedent (%nodent "..")?)
+
+ list <- ({} {|
+ ("[..]" %ws? %indent %nodent indented_list ","? %dedent (%nodent "..")?)
+ / ("[" %ws? (list_items ","?)? %ws?"]")
+ |} {}) -> List
+ list_items <- (expression (list_sep list_items)?)
+ list_sep <- %ws? "," %ws?
+ indented_list <-
+ expression (((list_sep %nodent?) / %nodent) indented_list)?
+]=]
+
+defs =
+ eol: #(linebreak) + (lpeg.P("")-lpeg.P(1))
+ ws: lpeg.S(" \t")^1
+ wordchar: lpeg.P(1)-lpeg.S(' \t\n\r%:;,.{}[]()"')
+ indent: linebreak * lpeg.P("{") * lpeg.S(" \t")^0
+ nodent: linebreak * lpeg.P(" ") * lpeg.S(" \t")^0
+ dedent: linebreak * lpeg.P("}") * lpeg.S(" \t")^0
+ word_boundary: lpeg.S(" \t")^1 + lpeg.B(lpeg.P("..")) + lpeg.B(lpeg.S("\";)]")) + #lpeg.S("\":([") + #lpeg.P("..")
+
+setmetatable(defs, {
+ __index: (t,key)->
+ --print("WORKING for #{key}")
+ fn = (start, value, stop, ...)->
+ token = {type: key, range:{start,stop}, value: value}
+ return token
+ t[key] = fn
+ return fn
+})
+lingo = re.compile lingo, defs
+
+
+class Game
+ new:=>
+ @defs = {}
+ @macros = {}
+ @debug = false
+
+ call: (fn_name,...)=>
+ if @defs[fn_name] == nil
+ error "Attempt to call undefined function: #{fn_name}"
+ {fn, arg_names} = @defs[fn_name]
if @debug
- print("SOURCE NOMIC CODE:\n#{str}")
- export macros
- old_macros = macros
- macros = (self.macros)
- lua_code = cross_compile str
- macros = old_macros
+ print("Calling #{fn_name}...")
+ args = {}
+ for i,name in ipairs(arg_names)
+ args[name] = select(i,...)
+ if @debug
+ print("arg #{utils.repr(name,true)} = #{select(i,...)}")
+ ret = fn(self, args)
if @debug
- print("\nGENERATED LUA CODE:\n#{lua_code}")
+ print "returned #{utils.repr(ret,true)}"
+ return ret
+
+ def: (spec, fn)=>
+ invocations,arg_names = self\get_invocations spec
+ for invocation in *invocations
+ @defs[invocation] = {fn, arg_names}
- lua_thunk, err = loadstring("return "..lua_code)
+ get_invocations:(text)=>
+ if type(text) == 'string' then text = {text}
+ invocations = {}
+ local arg_names
+ for _text in *text
+ name_bits = {}
+ _arg_names = {}
+ for chunk in _text\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, " "
+ table.insert(invocations, invocation)
+ if arg_names and not utils.equivalent(utils.set(arg_names), utils.set(_arg_names))
+ error("Conflicting argument names #{utils.repr(arg_names)} and #{utils.repr(_arg_names)} for #{utils.repr(text)}")
+ arg_names = _arg_names
+ return invocations, arg_names
+
+ defmacro: (spec, fn)=>
+ invocations,arg_names = self\get_invocations spec
+ for invocation in *invocations
+ @macros[invocation] = {fn, arg_names}
+
+ run: (text)=>
+ if @debug
+ print("RUNNING TEXT:\n")
+ print(text)
+ code = self\compile(text)
+ if @debug
+ print("\nGENERATED LUA CODE:")
+ print(code)
+ lua_thunk, err = loadstring(code)
if not lua_thunk
- print("Parsing: "..lua_code)
- error(err)
- lua_fn = lua_thunk!
-
- ret = lua_fn @, {}
+ error("Failed to compile generated code:\n#{code}")
+ action = lua_thunk!
+ if @debug
+ print("Running...")
+ return action(self, {})
+
+ run_debug:(text)=>
+ old_debug = @debug
+ @debug = true
+ ret = self\run(text)
+ @debug = old_debug
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: #{utils.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
+ parse: (str)=>
+ if @debug
+ print("PARSING:\n#{str}")
+ indentified = add_indent_tokens str
+ if @debug
+ print("\nINDENTIFIED:\n#{indentified}")
+ tree = lingo\match indentified
+ if @debug
+ print("\nPARSE TREE:")
+ self\print_tree(tree)
+ assert tree, "Failed to parse: #{str}"
+ return tree
+
+ transform: (tree, indent_level=0, parent=nil)=>
+ indented = (fn)->
+ export indent_level
+ indent_level += 1
+ fn!
+ indent_level -= 1
+ transform = (t,parent)-> self\transform(t, indent_level, parent or tree)
+ ind = (line) -> (" ")\rep(indent_level)..line
+ ded = (lines)->
+ if not lines.match then error("WTF: #{utils.repr(lines)}")
+ lines\match"^%s*(.*)"
+
+ ret_lines = {}
+ lua = (line, skip_indent=false)->
+ unless skip_indent
+ line = ind(ded(line))
+ table.insert ret_lines, line
+ return line
+
+ comma_separated_items = (open, items, close)->
+ buffer = open
+ so_far = indent_level*2
+ indented ->
+ export buffer,so_far
+ for i,item in ipairs(items)
+ if i < #items then item ..= ", "
+ if so_far + #item >= 80 and #buffer > 0
+ lua buffer
+ so_far -= #buffer
+ buffer = item
+ else
+ so_far += #item
+ buffer ..= item
+ buffer ..= close
+ lua buffer
+
+ switch tree.type
+ when "File"
+ if tree.value.errors and #tree.value.errors.value > 0
+ ret = transform(tree.value.errors)
+ return ret
+
+ lua "return (function(game, vars)"
+ indented ->
+ lua "local ret"
+ lua transform(tree.value.body)
+ lua "return ret"
+ lua "end)"
+
+ when "Errors"
+ -- TODO: Better error reporting via tree.range[1]
+ error("\nParse error on: #{tree.value}")
+
+ when "Block"
+ for chunk in *tree.value
+ lua transform(chunk)
+
+ when "Thunk"
+ if not tree.value
+ error("Thunk without value: #{utils.repr(tree)}")
+ lua "(function(game,vars)"
+ indented ->
+ lua "local ret"
+ assert tree.value.type == "Block", "Non-block value in Thunk"
+ lua transform(tree.value)
+ lua "return ret"
+ lua "end)"
+
+ when "Statement"
+ ret = transform(tree.value)
+ return ret
+
+ when "Expression"
+ ret = transform(tree.value)
+ if parent.type == "Statement"
+ ret = "ret = "..ded(ret)
+ return ret
+
+ when "FunctionCall"
+ name_bits = {}
+ for token in *tree.value
+ table.insert name_bits, if token.type == "Word" then token.value else "%"
+ name = table.concat(name_bits, " ")
+ if @macros[name]
+ {fn, arg_names} = @macros[name]
+ helpers = {:indented, :transform, :ind, :ded, :lua, :comma_separated_items}
+ args = [a for a in *tree.value when a.type != "Word"]
+ args = {name,args[i] for i,name in ipairs(arg_names)}
+ helpers.var = (varname)->
+ ded(transform(args[varname]))
+ m = fn(self, args, helpers, parent.type)
+ if m != nil then return m
+ else
+ if parent.type == "Statement"
+ lua "ret ="
+ args = [ded(transform(a)) for a in *tree.value when a.type != "Word"]
+ table.insert args, 1, utils.repr(name, true)
+ comma_separated_items("game:call(", args, ")")
+
+ when "String"
+ lua utils.repr(tree.value, true)
+
+ when "Number"
+ lua tree.value
+
+ when "List"
+ if #tree.value == 0
+ lua "{}"
+ elseif #tree.value == 1
+ lua "{#{transform(tree.value)}}"
+ else
+ comma_separated_items("{", [ded(transform(item)) for item in *tree.value], "}")
+
+ when "Var"
+ lua "vars[#{utils.repr(tree.value,true)}]"
+
+ else
+ error("Unknown/unimplemented thingy: #{tree.type}")
+
+ ret = table.concat ret_lines, "\n"
return ret
- run_debug:(...)=>
- @debug = true
- print("Debugging:")
- @run ...
- @debug = false
+ _yield_tree: (tree, indent_level=0)=>
+ ind = (s) -> (" ")\rep(indent_level)..s
+ switch tree.type
+ when "File"
+ coroutine.yield(ind"File:")
+ self\_yield_tree(tree.value.body, indent_level+1)
+
+ when "Errors"
+ coroutine.yield(ind"Error:\n#{tree.value}")
+
+ when "Block"
+ for chunk in *tree.value
+ self\_yield_tree(chunk, indent_level)
+
+ when "Thunk"
+ coroutine.yield(ind"Thunk:")
+ self\_yield_tree(tree.value, indent_level+1)
+
+ when "Statement"
+ self\_yield_tree(tree.value, indent_level)
+
+ when "Expression"
+ self\_yield_tree(tree.value, indent_level)
+
+ when "FunctionCall"
+ name_bits = {}
+ for token in *tree.value
+ table.insert name_bits, if token.type == "Word" then token.value else "%"
+ name = table.concat(name_bits, " ")
+ if #[a for a in *tree.value when a.type != "Word"] == 0
+ coroutine.yield(ind"Call [#{name}]!")
+ else
+ coroutine.yield(ind"Call [#{name}]:")
+ for a in *tree.value
+ if a.type != "Word"
+ self\_yield_tree(a, indent_level+1)
+
+ when "String"
+ coroutine.yield(ind(utils.repr(tree.value, true)))
+
+ when "Number"
+ coroutine.yield(ind(tree.value))
+
+ when "List"
+ if #tree.value == 0
+ coroutine.yield(ind("<Empty List>"))
+ else
+ coroutine.yield(ind"List:")
+ for item in *tree.value
+ self\_yield_tree(item, indent_level+1)
+
+ when "Var"
+ coroutine.yield ind"Var[#{utils.repr(tree.value)}]"
+
+ else
+ error("Unknown/unimplemented thingy: #{tree.type}")
+ return nil -- to prevent tail calls
+
+ print_tree:(tree)=>
+ for line in coroutine.wrap(-> self\_yield_tree(tree))
+ print(line)
+
+ stringify_tree:(tree)=>
+ result = {}
+ for line in coroutine.wrap(-> self\_yield_tree(tree))
+ table.insert(result, line)
+ return table.concat result, "\n"
+
+ compile: (src)=>
+ tree = self\parse(src)
+ code = self\transform(tree,0)
+ return code
+
+ test: (src, expected)=>
+ if expected == nil
+ start,stop = src\find"==="
+ if not start or not stop then
+ error("WHERE'S THE ===? in:\n#{src}")
+ src, expected = src\sub(1,start-1), src\sub(stop+1,-1)
+ expected = expected\match'[\n]*(.*[^\n])'
+ if not expected then error("WTF???")
+ tree = self\parse(src)
+ got = if tree.value.errors and #tree.value.errors.value > 0
+ self\stringify_tree(tree.value.errors)
+ else
+ self\stringify_tree(tree.value.body)
+ if got != expected
+ error"TEST FAILED!\nSource:\n#{src}\nExpected:\n#{expected}\n\nGot:\n#{got}"
- 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
return Game
diff --git a/nomic_whitespace.moon b/nomic_whitespace.moon
deleted file mode 100644
index 783c29c..0000000
--- a/nomic_whitespace.moon
+++ /dev/null
@@ -1,437 +0,0 @@
-re = require 're'
-lpeg = require 'lpeg'
-utils = require 'utils'
-moon = require 'moon'
-
-lpeg.setmaxstack 10000 -- whoa
-
-linebreak = lpeg.P("\r")^-1 * lpeg.P("\n")
-
-get_line_indentation = (line)->
- indent_amounts = {[" "]:1, ["\t"]:4}
- with sum = 0
- leading_space = line\gsub("([\t ]*).*", "%1")
- for c in leading_space\gmatch "[\t ]"
- sum += indent_amounts[c]
-
-pos_to_line = (str,pos)->
- line_no = 1
- for line in str\gmatch("[^%n]+")
- if #line >= pos then return line, line_no
- pos -= (#line + 1)
- line_no += 1
- error "Failed to find position #{pos} in str"
-
-pos_to_line_no = (str,pos)->
- with line = 1
- for _ in str\sub(1, pos)\gmatch("\n")
- line += 1
-
-add_indent_tokens = (str)->
- indent_stack = {0}
- result = {}
- -- TODO: Store mapping from new line numbers to old ones
- defs =
- linebreak: linebreak
- process_line: (line)->
- -- Remove blank lines
- unless line\match"[^ \t\n]"
- return
- indent = get_line_indentation(line)
- if indent > indent_stack[#indent_stack]
- table.insert result, "{\n "
- table.insert indent_stack, indent
- elseif indent < indent_stack[#indent_stack]
- dedents = 0
- tokens = {}
- while indent < indent_stack[#indent_stack]
- table.remove indent_stack
- table.insert tokens, "}"
- table.insert tokens, " "
- table.insert result, table.concat(tokens, "\n")
- else
- table.insert result, " "
- -- Delete leading whitespace
- --line = line\gsub("[ \t]*", "", 1)
- -- Delete trailing whitespace and carriage returns
- line = line\gsub("[ \t\r]*\n", "\n", 1)
- table.insert result, line
-
- indentflagger = [=[
- file <- line*
- line <- ((string / [^%linebreak])* %linebreak) -> process_line
- string <- '"' (("\\" .) / [^"])* '"'
- ]=]
- indentflagger = re.compile indentflagger, defs
- indentflagger\match(str)
- while #indent_stack > 1
- table.remove indent_stack
- table.insert result, "}\n"
- return (table.concat result)\sub(1,-2)
-
-lingo = [=[
- file <- ({} {| {:body: (" " block) :} ({:errors: errors :})? |} {}) -> File
- errors <- ({} {.+} {}) -> Errors
- block <- ({} {| statement (%nodent statement)* |} {}) -> Block
- statement <- ({} (functioncall / expression) {}) -> Statement
- one_liner <- ({} {|
- (({}
- (({} {|
- (expression (%word_boundary fn_bit)+) / (word (%word_boundary fn_bit)*)
- |} {}) -> FunctionCall)
- {}) -> Statement)
- |} {}) -> Block
-
- functioncall <- ({} {| (expression %word_boundary fn_bits) / (word (%word_boundary fn_bits)?) |} {}) -> FunctionCall
- fn_bit <- (expression / word)
- fn_bits <-
- ((".." %ws? (%indent %nodent indented_fn_bits %dedent) (%nodent ".." %ws? fn_bits)?)
- / (%nodent ".." fn_bit fn_bits)
- / (fn_bit (%word_boundary fn_bits)?))
- indented_fn_bits <-
- fn_bit ((%nodent / %word_boundary) indented_fn_bits)?
-
- thunk <-
- ({} ":" %ws?
- ((%indent %nodent block %dedent (%nodent "..")?)
- / (one_liner (%ws? ((%nodent? "..")))?)) {}) -> Thunk
-
- word <- ({} !number {%wordchar+} {}) -> Word
- expression <- ({} (string / number / variable / list / thunk / subexpression) {}) -> Expression
-
- string <- ({} '"' {(("\\" .) / [^"])*} '"' {}) -> String
- number <- ({} {'-'? [0-9]+ ("." [0-9]+)?} {}) -> Number
- variable <- ({} ("%" {%wordchar+}) {}) -> Var
-
- subexpression <-
- ("(" %ws? (functioncall / expression) %ws? ")")
- / ("(..)" %ws? %indent %nodent (expression / (({} {| indented_fn_bits |} {}) -> FunctionCall)) %dedent (%nodent "..")?)
-
- list <- ({} {|
- ("[..]" %ws? %indent %nodent indented_list ","? %dedent (%nodent "..")?)
- / ("[" %ws? (list_items ","?)? %ws?"]")
- |} {}) -> List
- list_items <- (expression (list_sep list_items)?)
- list_sep <- %ws? "," %ws?
- indented_list <-
- expression (((list_sep %nodent?) / %nodent) indented_list)?
-]=]
-
-defs =
- eol: #(linebreak) + (lpeg.P("")-lpeg.P(1))
- ws: lpeg.S(" \t")^1
- wordchar: lpeg.P(1)-lpeg.S(' \t\n\r%:;,.{}[]()"')
- indent: linebreak * lpeg.P("{") * lpeg.S(" \t")^0
- nodent: linebreak * lpeg.P(" ") * lpeg.S(" \t")^0
- dedent: linebreak * lpeg.P("}") * lpeg.S(" \t")^0
- word_boundary: lpeg.S(" \t")^1 + lpeg.B(lpeg.P("..")) + lpeg.B(lpeg.S("\";)]")) + #lpeg.S("\":([") + #lpeg.P("..")
-
-setmetatable(defs, {
- __index: (t,key)->
- --print("WORKING for #{key}")
- fn = (start, value, stop, ...)->
- token = {type: key, range:{start,stop}, value: value}
- return token
- t[key] = fn
- return fn
-})
-lingo = re.compile lingo, defs
-
-
-class Game
- new:=>
- @defs = {}
- @macros = {}
- @debug = false
-
- call: (fn_name,...)=>
- if @defs[fn_name] == nil
- error "Attempt to call undefined function: #{fn_name}"
- {fn, arg_names} = @defs[fn_name]
- if @debug
- print("Calling #{fn_name}...")
- args = {}
- for i,name in ipairs(arg_names)
- args[name] = select(i,...)
- if @debug
- print("arg #{utils.repr(name,true)} = #{select(i,...)}")
- ret = fn(self, args)
- if @debug
- print "returned #{utils.repr(ret,true)}"
- return ret
-
- def: (spec, fn)=>
- invocation,arg_names = self\get_invocation spec
- @defs[invocation] = {fn, arg_names}
-
- get_invocation:(text)=>
- name_bits = {}
- arg_names = {}
- for chunk in text\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
-
- defmacro: (spec, fn)=>
- invocation,arg_names = self\get_invocation spec
-
- if type(fn) == 'string'
- error("not implemented")
- str = fn
- fn = (vars,helper,ftype)=> nil
-
- @macros[invocation] = {fn, arg_names}
-
- run: (text)=>
- if @debug
- print("RUNNING TEXT:\n")
- print(text)
- code = self\compile(text)
- if @debug
- print("\nGENERATED LUA CODE:")
- print(code)
- lua_thunk, err = loadstring(code)
- if not lua_thunk
- error("Failed to compile generated code:\n#{code}")
- action = lua_thunk!
- if @debug
- print("Running...")
- return action(self, {})
-
- run_debug:(text)=>
- old_debug = @debug
- @debug = true
- ret = self\run(text)
- @debug = old_debug
- return ret
-
- parse: (str)=>
- if @debug
- print("PARSING:\n#{str}")
- indentified = add_indent_tokens str
- if @debug
- print("\nINDENTIFIED:\n#{indentified}")
- tree = lingo\match indentified
- if @debug
- print("\nPARSE TREE:")
- self\print_tree(tree)
- assert tree, "Failed to parse: #{str}"
- return tree
-
- transform: (tree, indent_level=0, parent=nil)=>
- indented = (fn)->
- export indent_level
- indent_level += 1
- fn!
- indent_level -= 1
- transform = (t,parent)-> self\transform(t, indent_level, parent or tree)
- ind = (line) -> (" ")\rep(indent_level)..line
- ded = (lines)->
- if not lines.match then error("WTF: #{utils.repr(lines)}")
- lines\match"^%s*(.*)"
-
- ret_lines = {}
- lua = (line, skip_indent=false)->
- unless skip_indent
- line = ind(ded(line))
- table.insert ret_lines, line
- return line
-
- comma_separated_items = (open, items, close)->
- buffer = open
- so_far = indent_level*2
- indented ->
- export buffer,so_far
- for i,item in ipairs(items)
- if i < #items then item ..= ", "
- if so_far + #item >= 80 and #buffer > 0
- lua buffer
- so_far -= #buffer
- buffer = item
- else
- so_far += #item
- buffer ..= item
- buffer ..= close
- lua buffer
-
- switch tree.type
- when "File"
- if tree.value.errors and #tree.value.errors.value > 0
- ret = transform(tree.value.errors)
- return ret
-
- lua "return (function(game, vars)"
- indented ->
- lua "local ret"
- lua transform(tree.value.body)
- lua "return ret"
- lua "end)"
-
- when "Errors"
- -- TODO: Better error reporting via tree.range[1]
- error("\nParse error on: #{tree.value}")
-
- when "Block"
- for chunk in *tree.value
- lua transform(chunk)
-
- when "Thunk"
- if not tree.value
- error("Thunk without value: #{utils.repr(tree)}")
- lua "(function(game,vars)"
- indented ->
- lua "local ret"
- assert tree.value.type == "Block", "Non-block value in Thunk"
- lua transform(tree.value)
- lua "return ret"
- lua "end)"
-
- when "Statement"
- ret = transform(tree.value)
- return ret
-
- when "Expression"
- ret = transform(tree.value)
- if parent.type == "Statement"
- ret = "ret = "..ded(ret)
- return ret
-
- when "FunctionCall"
- name_bits = {}
- for token in *tree.value
- table.insert name_bits, if token.type == "Word" then token.value else "%"
- name = table.concat(name_bits, " ")
- if @macros[name]
- {fn, arg_names} = @macros[name]
- helpers = {:indented, :transform, :ind, :ded, :lua, :comma_separated_items}
- args = [a for a in *tree.value when a.type != "Word"]
- args = {name,args[i] for i,name in ipairs(arg_names)}
- helpers.var = (varname)->
- ded(transform(args[varname]))
- m = fn(self, args, helpers, parent.type)
- if m != nil then return m
- else
- if parent.type == "Statement"
- lua "ret ="
- args = [ded(transform(a)) for a in *tree.value when a.type != "Word"]
- table.insert args, 1, utils.repr(name, true)
- comma_separated_items("game:call(", args, ")")
-
- when "String"
- lua utils.repr(tree.value, true)
-
- when "Number"
- lua tree.value
-
- when "List"
- if #tree.value == 0
- lua "{}"
- elseif #tree.value == 1
- lua "{#{transform(tree.value)}}"
- else
- comma_separated_items("{", [ded(transform(item)) for item in *tree.value], "}")
-
- when "Var"
- lua "vars[#{utils.repr(tree.value,true)}]"
-
- else
- error("Unknown/unimplemented thingy: #{tree.type}")
-
- ret = table.concat ret_lines, "\n"
- return ret
-
- _yield_tree: (tree, indent_level=0)=>
- ind = (s) -> (" ")\rep(indent_level)..s
- switch tree.type
- when "File"
- coroutine.yield(ind"File:")
- self\_yield_tree(tree.value.body, indent_level+1)
-
- when "Errors"
- coroutine.yield(ind"Error:\n#{tree.value}")
-
- when "Block"
- for chunk in *tree.value
- self\_yield_tree(chunk, indent_level)
-
- when "Thunk"
- coroutine.yield(ind"Thunk:")
- self\_yield_tree(tree.value, indent_level+1)
-
- when "Statement"
- self\_yield_tree(tree.value, indent_level)
-
- when "Expression"
- self\_yield_tree(tree.value, indent_level)
-
- when "FunctionCall"
- name_bits = {}
- for token in *tree.value
- table.insert name_bits, if token.type == "Word" then token.value else "%"
- name = table.concat(name_bits, " ")
- if #[a for a in *tree.value when a.type != "Word"] == 0
- coroutine.yield(ind"Call [#{name}]!")
- else
- coroutine.yield(ind"Call [#{name}]:")
- for a in *tree.value
- if a.type != "Word"
- self\_yield_tree(a, indent_level+1)
-
- when "String"
- coroutine.yield(ind(utils.repr(tree.value, true)))
-
- when "Number"
- coroutine.yield(ind(tree.value))
-
- when "List"
- if #tree.value == 0
- coroutine.yield(ind("<Empty List>"))
- else
- coroutine.yield(ind"List:")
- for item in *tree.value
- self\_yield_tree(item, indent_level+1)
-
- when "Var"
- coroutine.yield ind"Var[#{utils.repr(tree.value)}]"
-
- else
- error("Unknown/unimplemented thingy: #{tree.type}")
- return nil -- to prevent tail calls
-
- print_tree:(tree)=>
- for line in coroutine.wrap(-> self\_yield_tree(tree))
- print(line)
-
- stringify_tree:(tree)=>
- result = {}
- for line in coroutine.wrap(-> self\_yield_tree(tree))
- table.insert(result, line)
- return table.concat result, "\n"
-
- compile: (src)=>
- tree = self\parse(src)
- code = self\transform(tree,0)
- return code
-
- test: (src, expected)=>
- if expected == nil
- start,stop = src\find"==="
- if not start or not stop then
- error("WHERE'S THE ===? in:\n#{src}")
- src, expected = src\sub(1,start-1), src\sub(stop+1,-1)
- expected = expected\match'[\n]*(.*[^\n])'
- if not expected then error("WTF???")
- tree = self\parse(src)
- got = if tree.value.errors and #tree.value.errors.value > 0
- self\stringify_tree(tree.value.errors)
- else
- self\stringify_tree(tree.value.body)
- if got != expected
- error"TEST FAILED!\nSource:\n#{src}\nExpected:\n#{expected}\n\nGot:\n#{got}"
-
-
-return Game
diff --git a/utils.moon b/utils.moon
index a048f2c..3004c58 100644
--- a/utils.moon
+++ b/utils.moon
@@ -34,6 +34,7 @@ utils = {
keys: (t)-> [k for k in pairs(t)]
values: (t)-> [v for _,v in pairs(t)]
+ set: (list)-> {i,true for i in *list}
sum: (t)->
with tot = 0