aboutsummaryrefslogtreecommitdiff
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
parent5fb8fabaf735f9a2ee97f7d07b85293ff8e9c987 (diff)
Reworked to use lua codegen.
-rw-r--r--core.moon93
-rwxr-xr-xcore2.moon230
-rwxr-xr-xgame1.moon37
-rwxr-xr-xgame2.moon317
-rw-r--r--nomic.moon136
-rw-r--r--nomic2.moon285
6 files changed, 984 insertions, 114 deletions
diff --git a/core.moon b/core.moon
index 66649f5..d62cf9e 100644
--- a/core.moon
+++ b/core.moon
@@ -23,43 +23,58 @@ game\def {[[print $str]], [[say $str]]}, (locals)=>
print(repr(.str))
return nil
+game\def [[$invocations := $body]], (locals)=>
+ game\def locals.invocations, locals.body
+ return nil
+
game\def [[help $invocation]], (locals)=>
with locals
- if @rules[.invocation]
- print(@rules[.invocation])
+ if @rules[.invocation\gsub(" ",";")]
+ print(@rules[.invocation\gsub(" ",";")])
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 = {}
for i,r in pairs(@rules)
- if i\match .invocation
- print("Rule: #{@rules[.invocation]}")
+ rules[r] = math.max((rules[r] or 0), match_count(i))
+ best = [r for r in pairs rules]
+ table.sort best, ((a,b)-> rules[a] > rules[b])
+ if rules[best[1]] > 0
+ for r in *best
+ if rules[r] < rules[best[1]]
+ break
+ print("Closest match for \"#{.invocation}\" is:\n#{r}")
return nil
-game\def [[return $retval]], (locals)=> locals.retval
-
-game\def [[do $thunk]], (locals)=>
- locals.thunk\run(@, locals)
+game\def [[return $retval]], ((locals)=> locals.retval), "... returns the specified value ..."
-game\def {[[true]], [[yes]]}, (locals)=> true
-game\def {[[false]], [[no]]}, (locals)=> false
-game\def {[[nil]], [[None]], [[nop]], [[done]]}, (locals)=> nil
+game\def {[[true]], [[yes]]}, ((locals)=> true), "... returns true ..."
+game\def {[[false]], [[no]]}, ((locals)=> false), "... returns false ..."
+game\def {[[nil]], [[None]], [[nop]], [[done]]}, ((locals)=> nil), "... does nothing, returns nil ..."
game\def {[[$x == $y]], [[equal $x $y]]}, (locals)=>
with locals
- if type(.x) != type(.y)
- return false
- if type(.x) == 'table'
- for k,v in pairs(.x)
- if .y[k] != v
- return false
- for k,v in pairs(.y)
- if .x[k] != v
- return false
- return true
- else
- return .x == .y
+ if .x == .y then return true
+ if type(.x) != type(.y) then return false
+ if type(.x) != 'table' then return false
+ for k,v in pairs(.x)
+ if .y[k] != v
+ return false
+ for k,v in pairs(.y)
+ if .x[k] != v
+ return false
+ return true
game\def [[not $x]], (locals)=> not locals.x
-game\def [[$x != $y]], [[return (not (x == y))]]
+game\run [["$x != $y" := {return (not (x == y))}]]
game\def [[$x < $y]], (locals)=> locals.x < locals.y
game\def [[$x <= $y]], (locals)=> locals.x <= locals.y
game\def [[$x > $y]], (locals)=> locals.x > locals.y
@@ -78,8 +93,15 @@ game\def [[if $condition then $body else $else_body]], (locals)=>
return .body\run(@, locals)
else return .else_body\run(@, locals)
-game\def [[if $condition then $body]], [[if $condition then $body else {}]]
-game\def [[when $condition do $body]], [[if $condition then $body else {}]]
+game\run [[
+["if $condition then $body", "when $condition do $body"] := {if $condition then $body else {}}
+]]
+game\run [[
+["unless $condition do $body"] := {if (not $condition) then $body else {}}
+]]
+game\run [[
+["unless $condition do $body else $else_body"] := {if (not $condition) then $body else $else_body}
+]]
game\def [[sum $items]], (locals)=>
@@ -108,4 +130,23 @@ game\def {[[max $items]], [[largest $items]], [[highest $items]], [[most $items]
max = .items[i]
return max
+game\def {[[argmin $items]]}, (locals)=>
+ with locals
+ min = .items[1]
+ for i=2,#.items
+ if .items[i][2] < min[2]
+ min = .items[i]
+ return min
+
+game\def {[[argmax $items]]}, (locals)=>
+ with locals
+ max = .items[1]
+ for i=2,#.items
+ if .items[i][2] > max[2]
+ max = .items[i]
+ return max
+
+game\def {[[$index st in $list]], [[$index nd in $list]], [[$index rd in $list]], [[$index th in $list]]}, (locals)=>
+ locals.list[locals.index]
+
return game
diff --git a/core2.moon b/core2.moon
new file mode 100755
index 0000000..6298426
--- /dev/null
+++ b/core2.moon
@@ -0,0 +1,230 @@
+#!/usr/bin/env moon
+nomic = require 'nomic2'
+game = nomic()
+
+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)
+
+game\def {[[print $str]], [[say $str]]}, (args)=> print(repr(args.str))
+game\def {[[printf $str]]}, (args)=>
+ for s in *args.str do io.write(repr(s))
+ io.write("\n")
+
+game\def [[return $value]], (args)=> args.value
+
+game\macro [[let $var = $value]], (var, value)->
+ "locals[#{var}] = #{value}"
+
+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
+
+ @\def args.signature, args.body
+ print "Defined new rule: \"#{game.repr(args.signature)}\""
+ --print debug.getinfo(args.body, "S").source
+ return nil
+
+game\def [[compile $body]], (args)=>
+ print debug.getinfo(args.body, "S").source
+ 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]
+ table.sort best, ((a,b)-> rules[a] > rules[b])
+ 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)=>
+ with args
+ if .x == .y then return true
+ if type(.x) != type(.y) then return false
+ if type(.x) != 'table' then return false
+ for k,v in pairs(.x)
+ if .y[k] != v
+ return false
+ for k,v in pairs(.y)
+ if .x[k] != v
+ return false
+ return true
+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)=>
+ tot = 0
+ for x in *args.items do tot += x
+ return tot
+
+game\def [[all $items]], (args)=>
+ for x in *args.items
+ if not x then return false
+ return true
+
+game\def [[any $items]], (args)=>
+ for x in *args.items
+ if x then return true
+ return false
+
+game\def {[[average $items]], [[avg $items]]}, (args)=>
+ tot = 0
+ for x in *args.items do tot += x
+ return tot / #args.items
+
+game\def {[[min $items]], [[smallest $items]], [[lowest $items]], [[fewest $items]]}, (args)=>
+ with args
+ min = .items[1]
+ for i=2,#.items
+ if .items[i] < min
+ min = .items[i]
+ return min
+
+game\def {[[max $items]], [[largest $items]], [[highest $items]], [[most $items]]}, (args)=>
+ with args
+ max = .items[1]
+ for i=2,#.items
+ if .items[i] > max
+ max = .items[i]
+ return max
+
+game\def {[[argmin $items]]}, (args)=>
+ with args
+ min = .items[1]
+ for i=2,#.items
+ if .items[i][2] < min[2]
+ min = .items[i]
+ return min
+
+game\def {[[argmax $items]]}, (args)=>
+ with args
+ max = .items[1]
+ for i=2,#.items
+ if .items[i][2] > max[2]
+ max = .items[i]
+ return max
+
+game\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)=>
+ with args
+ if type(.list) != 'table'
+ print "Not a list: #{.list}"
+ return
+ for i,x in ipairs .list
+ if x == .item
+ return i
+ return nil
+game\run [=[
+ ["$item is in $list", "$list contains $item"] := {(index of $item in $list) != (nil)}
+]=]
+
+game\def {[[# $list]], [[length of $list]], [[size of $list]]}, (args)=>
+ with args
+ if type(.list) != 'table'
+ print "Not a list: #{.list}"
+ return
+ #.list
+
+
+return game
diff --git a/game1.moon b/game1.moon
index f60917d..0f40053 100755
--- a/game1.moon
+++ b/game1.moon
@@ -1,5 +1,7 @@
#!/usr/bin/env moon
-game = require 'core'
+Game = require 'nomic'
+core_game = require 'core'
+game = Game(core_game)
------------------ BASIC TESTS ---------------------
game\run [=[
@@ -22,7 +24,6 @@ fart twice
greet
say (return "returned value")
-do {say "did"}
say 6
say -6
say []
@@ -30,7 +31,15 @@ say [1,2,3,4]
say [[1,2],[3,[4,5]]]
say (sum [1,2,3,4])
help "fart"
-help "fart;twice"
+help "fart twice"
+
+"fart thrice" := {
+ fart
+ fart
+ fart
+}
+help "fart lol"
+help "yes"
"five" := {return 5}
say (6 times 6)
@@ -46,13 +55,20 @@ foobar 55
"$x foo" := {($x * $x) + 1}
say (5 foo)
+say (1 st in [1,2,3,4,5])
+say (2 nd in [1,2,3,4,5])
+say (3 rd in [1,2,3,4,5])
+say (4 th in [1,2,3,4,5])
+
]=]
game\def [[you]], (_)=> @you
game\run [[you]]
game\run [[say (you)]]
-game\def [[five]], [[return 5]]
+game\run [[
+ "five" := {return 5}
+]]
game\run [[say (five)]]
game\def [[$x squared]], (locals)=> locals.x^2
game\run [[say ((five) squared)]]
@@ -65,7 +81,9 @@ game\def [[remember that $key $relation $value]], (locals)=>
@relations[.relation][.key] = .value
return nil
-game\def [[remember that $key $relation]], [[remember that $key $relation (true)]]
+game\run [[
+ "remember that $key $relation" := {remember that $key $relation (true)}
+]]
game\def [[forget about $key $relation]], (locals)=>
with locals
@@ -77,9 +95,8 @@ game\def [[the value of $key $relation]], (locals)=>
with locals
return (@relations[.relation] or {})[.key]
-game\def [[it is true that $key $relation $value]], [[(the value of $key $relation) == $value]]
-
-game\def [[it is true that $key $relation]], [[(the value of $key $relation) == (true)]]
+game\run [["it is true that $key $relation $value" := {(the value of $key $relation) == $value}]]
+game\run [["it is true that $key $relation" := {(the value of $key $relation) == (true)}]]
game\run[[
remember that "socrates" "is mortal"
@@ -105,8 +122,8 @@ game\run [[if (1 == 2) then {say "Affirmative"}]]
game\run [[say (if (1 == 1) then {return "Ternary yes"} else {return "Ternary no"})]]
-game\def [[$who is a member]], [[return (it is true that $who "is a member")]]
-game\def [[you are a member]], [[return ((you) is a member)]]
+game\run [["$who is a member" := {return (it is true that $who "is a member")}]]
+game\run [["you are a member" := {return ((you) is a member)}]]
game\run [[say (you are a member)]]
game\run [[
diff --git a/game2.moon b/game2.moon
new file mode 100755
index 0000000..4227faa
--- /dev/null
+++ b/game2.moon
@@ -0,0 +1,317 @@
+#!/usr/bin/env moon
+Game = require 'nomic2'
+core_game = require 'core2'
+
+game = Game(core_game)
+
+game\def "# $key $relation = $value", (args)=>
+ --print "Setting #{args.key} #{args.relation} = #{args.value}"
+ @relations[args.relation][args.key] = args.value
+ return args.value
+
+game\def "set all * $relation = $value", (args)=>
+ rels = @relations[args.relation]
+ for k,_ in pairs rels
+ rels[k] = args.value
+ if next(rels) == nil
+ @relations[args.relation] = nil
+ return args.value
+
+game\def "$key $relation ?", (args)=>
+ return @relations[args.relation][args.key]
+
+deep_pairs = (t)->
+ coroutine.wrap ->
+ for k,v in pairs(t)
+ coroutine.yield k, v
+ mt = getmetatable(t)
+ return unless mt and mt.__index and type(mt.__index) == 'table' and mt.__index != t
+ for k,v in deep_pairs mt.__index
+ if t[k] == nil
+ coroutine.yield k,v
+
+game\def "* $relation = $value", (args)=>
+ --print "Finding all * #{args.relation} = #{args.value}"
+ [key for key, value in deep_pairs(@relations[args.relation]) when value == args.value]
+
+game\def {"restrict $actions to $whitelist"}, (args)=>
+ with args
+ actions = @canonicalize(if type(.actions) == 'table' then .actions else {.actions})
+ whitelist = @all_aliases(if type(.whitelist) == 'table' then .whitelist else {.whitelist})
+ @\set_whitelist actions, whitelist
+ for action in *actions
+ print("Restricting #{Game.repr(action)} to #{Game.repr(whitelist)}")
+
+game\def {"permit $whitelist to $actions"}, (args)=>
+ with args
+ actions = @canonicalize(if type(.actions) == 'table' then .actions else {.actions})
+ whitelist = @all_aliases(if type(.whitelist) == 'table' then .whitelist else {.whitelist})
+ for action in *actions
+ if not @authorized[action]
+ print "#{action} is already available to everyone."
+ continue
+ print("Permitting #{Game.repr(action)} to #{Game.repr(whitelist)}")
+ for w in *whitelist
+ @authorized[action][w] = true
+
+game\def {"revoke $actions rights from $whitelist"}, (args)=>
+ with args
+ actions = @canonicalize(if type(.actions) == 'table' then .actions else {.actions})
+ whitelist = @all_aliases(if type(.whitelist) == 'table' then .whitelist else {.whitelist})
+ for action in *actions
+ if not @authorized[action]
+ print "#{action} is available to everyone, it can't be restricted."
+ continue
+ print("Revoking the right of #{Game.repr(action)} to use #{Game.repr(whitelist)}")
+ for w in *whitelist
+ @authorized[action][w] = nil
+
+game\def "print callstack", (args)=>
+ print("Callstack:")
+ for fn in *@callstack
+ print fn
+
+game\def {"do $action"}, (args)=>
+ (args.action)(@, {})
+
+game\def {"make $who $action"}, (args)=>
+ with args
+ old_you = @you
+ print("Setting you=#{.who}")
+ rawset(@, "you", .who)
+ (.action)(@, {})
+ rawset(@, "you", old_you)
+
+game\def "you", (args)=> @you
+
+game\run[=[
+say "===================================================="
+say " NEW GAME"
+say "===================================================="
+"everyone approves $action" := {yes}
+
+"sudo $action" := {
+ if (everyone approves $action) {
+ do $action
+ } else {
+ say "You do not have the will of the people! >:("
+ }
+}
+restrict "$ := $" to "sudo $"
+restrict "make $ $" to "sudo $"
+restrict "set all * $ = $" to "sudo $"
+restrict "# $ $ = $" to "sudo $"
+restrict "restrict $ to $" to "sudo $"
+
+sudo {
+ "propose $action" := {
+ if ("pending proposal" "is" ?) {
+ say "Sorry, an action is already pending."
+ } else {
+ say "Proposing..."
+ # "pending proposal" "is" = $action
+ }
+ }
+ "unpropose" := {
+ let "pending" = ("pending proposal" "is" ?)
+ set all * $pending = (nil)
+ # "pending proposal" "is" = (nil)
+ }
+
+
+ "mark $who as approving $action" := {
+ # $who $action = "approved"
+ }
+
+ "mark $who as rejecting $action" := {
+ # $who $action = "rejected"
+ }
+
+ ["approve", "vote yes", "vote yea"] := {
+ let "pending" = ("pending proposal" "is" ?)
+ mark (you) as approving $pending
+ say "Voted yes."
+ if (everyone approves $pending) {
+ sudo $pending
+ unpropose
+ }
+ }
+
+ ["reject", "vote no", "vote nay", "veto", "disapprove"] := {
+ let "pending" = ("pending proposal" "is" ?)
+ mark (you) as rejecting $pending
+ say "Voted no."
+ unpropose
+ }
+
+ ["players", "everyone", "everybody", "all players"] := {
+ * "is a player" = (yes)
+ }
+
+ "join" := {
+ # (you) "is a player" = (yes)
+ printf ["Welcome to the game, ",(you),"!"]
+ }
+ permit "unpropose" to "set all * $ = $"
+ permit ["join", "mark $ as approving $", "mark $ as rejecting $", "propose $", "unpropose"] to "# $ $ = $"
+ restrict "unpropose" to ["approve", "reject"]
+ restrict "mark $ as approving $" to ["propose $", "approve"]
+ restrict "mark $ as rejecting $" to ["propose $", "reject"]
+
+ "everyone approves $action" := {
+ (# (players)) == (# (* $action = "approved"))
+ }
+}
+
+join
+
+propose {
+ say "fart"
+}
+approve
+
+
+"cheat" := {
+ say "CHEATER!!!"
+}
+sudo {
+ say "CHEATER!!!"
+}
+
+propose {
+ # "democracy" "is possible" = (yes)
+ if ("democracy" "is possible" ?) {
+ say "DEMOCRACY WORKS!!!"
+ }
+}
+approve
+
+propose {
+ "fart" := {
+ say "poot"
+ }
+}
+approve
+fart
+
+propose {
+ "open election $candidates" := {
+ if ("candidates" "are" ?) {
+ say "An election is already in progress."
+ } else {
+ # "candidates" "are" = $candidates
+ }
+ }
+
+ "close election" := {
+ let "pending" = ("pending proposal" "is" ?)
+ set all * "votes for" = (nil)
+ # "candidates" "are" = (nil)
+ }
+
+ "vote for $candidate" := {
+ # (you) "votes for" = $candidate
+ let "vote-percent" = ((# (* "votes for" = $candidate)) / (# (players)))
+ printf ["Vote cast. ",$candidate," now has ",(100 * $vote-percent),"% of the votes."]
+ if ($vote-percent > 0.5) {
+ printf ["The winner of the election is:", $candidate]
+ close election
+ }
+ }
+
+ permit ["open election $", "close election", "vote for $"] to ["# $ $ = $"]
+ permit ["close election"] to ["set all * $ = $"]
+}
+approve
+
+propose {
+ "as bill: $action" := {
+ if ((you) == "Anonymous") {
+ make "bill" $action
+ } else {
+ printf ["Who do you think you are?", (you)]
+ }
+ }
+ permit ["as bill: $"] to ["make $ $"]
+}
+approve
+as bill: {join}
+
+propose {
+ "as dave: $action" := {
+ if ((you) == "Anonymous") {
+ make "dave" $action
+ } else {
+ printf ["Who do you think you are?", (you)]
+ }
+ }
+ permit ["as dave: $"] to ["make $ $"]
+}
+approve
+as bill: {approve}
+as dave: {join}
+
+open election ["tom", "dick", "harry"]
+vote for "dick"
+as bill: {vote for "dick"}
+
+propose {
+ "take a shit" := {say "shit taken."}
+}
+approve
+as bill: {approve}
+as dave: {approve}
+
+
+
+sudo {
+ "everyone approves" := {
+ (# (players)) == (# (* "votes" (yes)))
+ }
+ ["approve", "vote yes", "vote yea"] := {
+ # (you) "votes" = (yes)
+ if (everyone approves) {
+ do pending action
+ }
+ }
+ ["disapprove", "vote no", "vote nay", "veto"] := {
+ say "The proposal has failed."
+ # (you) "approves" = (yes)
+ if (everyone approves) {
+ do pending action
+ }
+ }
+}
+
+sudo {
+ "everyone approves" := {
+ say "Going into this code"
+ no
+ }
+}
+sudo {
+ say "BROKEN"
+}
+
+
+propose {
+ "arbitrarily define $signature := $body" := {
+ if ($signature == "butts") {
+ $signature := $body
+ } else {
+ say "Not my style."
+ }
+ }
+ permit "arbitrarily define $ := $" to "$ := $"
+ say "Arbitrary is a go."
+}
+as bill: {approve}
+as dave: {approve}
+approve
+arbitrarily define "butts" := {say "BUTTS"}
+butts
+
+arbitrarily define "ass" := {say "ASS"}
+ass
+
+]=]
diff --git a/nomic.moon b/nomic.moon
index 62b54c0..cca67e0 100644
--- a/nomic.moon
+++ b/nomic.moon
@@ -3,11 +3,8 @@ lpeg = require 'lpeg'
moon = require 'moon'
type = moon.type
-export __DEBUG__
-
currently_parsing = nil
-
as_value = (x, game, locals)->
assert (game and locals), "Shit's fucked"
if type(x) == 'number' or type(x) == 'string' or type(x) == 'table'
@@ -18,16 +15,8 @@ as_value = (x, game, locals)->
class Var
new:(@name)=>
as_value:(game, locals)=>
- if __DEBUG__
- print("Looking up variable #{@name} = #{locals[@name]}")
if locals[@name] == nil
- print("LOCALS:")
- for k,v in pairs locals
- print(" #{k} = #{v}")
- print("game:")
- for k,v in pairs game
- print(" #{k} = #{v}")
- error("Could not find #{@name}")
+ error("Attempt to access undefined variable: #{@name}")
locals[@name]
__tostring:=> "Var(#{@text})"
@@ -39,10 +28,6 @@ class Action
new:(tokens)=>
words = [(if type(t) == Word then t.text else "$") for t in *tokens]
@name = table.concat(words, ";")
- if __DEBUG__
- print("ACTION: #{@name}")
- for t in *tokens
- print(" TOKEN: #{t}")
@args = [t for t in *tokens when type(t) != Word]
__tostring:=> "Action(#{@name})"
@@ -66,21 +51,21 @@ class Action
return ret
class Thunk
- new:(@actions)=>
+ new:(@actions, @body_str)=>
if @actions.startPos
assert currently_parsing, "Not currently parsing!"
- @body_str = currently_parsing\sub(@actions.startPos, @actions.endPos-1)
- if __DEBUG__
- print("CONSTRUCTING THUNK WITH ACTIONS:")
- for k,v in pairs @actions
- print(" #{k} = #{v}")
+ unless @body_str
+ @body_str = currently_parsing\sub(@actions.startPos, @actions.endPos-1)
+
as_value:=>@
+
run:(game, locals)=>
assert((game and locals), "f'd up")
ret = nil
for a in *@actions
ret = a\run game,locals
return ret
+
__tostring:=>
--"Thunk(#{table.concat([tostring(a) for a in *@actions], ", ") .. tostring(@actions.returnValue or "") })"
"{#{@body_str}}"
@@ -110,22 +95,12 @@ defs = {
lingo = re.compile lingo, defs
class Rule
- new:(invocations, action)=>
- if type(action) == 'string'
- @body_str = action
- export currently_parsing
- old_parsing = currently_parsing
- currently_parsing = action
- thunk = lingo\match action
- currently_parsing = old_parsing
- unless thunk
- error("failed to parse!")
- @fn = (game,locals)-> thunk\run(game, locals)
- elseif type(action) == Thunk
- @body_str = tostring(action)
+ 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 = "<lua function>"
+ @body_str = docstring or "<lua function>"
@fn = action
else
error("Invalid action type: #{type(action)}")
@@ -158,46 +133,51 @@ class Rule
"Rule: #{table.concat(@raw_invocations, " | ")} :=\n #{@body_str\gsub("\n","\n ")}"
-def = (game, invocation, action)->
- invocations = if type(invocation) == 'table' then invocation else {invocation}
- rule = Rule(invocations, action)
- if game.src
- rule.body_str = game.src
- for invocation in *rule.invocations
- game.rules[invocation] = rule
- if __DEBUG__
- print rule
-
-run = (game, str)->
- if __DEBUG__
- print(">> #{str\gsub("\n", "\n.. ")}")
- export currently_parsing
- old_parsing = currently_parsing
- currently_parsing = str
- thunk = lingo\match str
- currently_parsing = old_parsing
- unless thunk
- error("failed to parse nomic:\n#{str}")
- -- TODO: remove
- game.you = "@spill"
- ret = thunk\run game, {}
- if ret != nil
- print("= #{ret}")
- return ret
+class Game
+ new:(parent)=>
+ @rules = setmetatable({}, {__index:parent and parent.rules or nil})
+ @relations = setmetatable({}, {__index:parent and parent.relations or nil})
+
+ 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
+
+ proxy: (rule)=>
+ for i in *rule.invocations
+ @rules[i] = rule
+
+ 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}")
+ return ret
-repl = (game)->
- 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
- game\run buf
-
-return ()->
- game = {rules:{}, relations:{}, :run, :def, :repl}
- game\def [[$invocations := $body]], (locals)=>
- game\def locals.invocations, locals.body
- return nil
- return game
+ 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/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