aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2017-08-22 00:41:25 -0700
committerBruce Hill <bitbucket@bruce-hill.com>2017-08-22 00:41:25 -0700
commit4713d7db0dcb713b4a36e63e5a5322829fca7c97 (patch)
treeffab954c638652cbb407fd4eb0b43a1d0d04e422
parentecc4e82919e024c46f648918257d35fd6c1537ed (diff)
Totally working everything (probably) with if and for macros.
-rwxr-xr-xgame2.moon340
-rw-r--r--nomic_whitespace.moon204
-rw-r--r--utils.moon2
3 files changed, 458 insertions, 88 deletions
diff --git a/game2.moon b/game2.moon
index 450437f..a95070a 100755
--- a/game2.moon
+++ b/game2.moon
@@ -3,70 +3,201 @@ utils = require 'utils'
Game = require 'nomic_whitespace'
g = Game()
+print("===========================================================================================")
+
g\def "rule %spec %body", (vars)=>
self\def vars.spec, vars.body
print "Defined rule: #{vars.spec}"
-g\defmacro("lua %code", ((args)->
- print("entering macro...: #{utils.repr(args)}")
- return args[1].value
-), true)
-
-g\defmacro("macro %spec %body", ((spec,body)->
- print("entering macro...: #{utils.repr(spec,true)} / #{utils.repr(body,true)}")
- -- TODO: parse better
- lua_thunk, err = loadstring("return "..spec)
- if not lua_thunk
- error("Failed to compile")
- spec = lua_thunk!
- print"SPEC IS NOW #{utils.repr(spec,true)}"
- g\defmacro spec, (args,blargs)->
- print("entering macro...: #{utils.repr(spec,true)} / #{utils.repr(body,true)}")
- return body
- return "nil"
-), false)
-
g\def "say %x", (vars)=>
print(utils.repr(vars.x))
-g\def "return %x", (vars)=>
- return vars.x
+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}"
-g\run_debug[[
-say "hello world!"
+ return nil
+
+g\defmacro "true", (vars,helpers,ftype)=> helpers.lua("true")
+g\defmacro "false", (vars,helpers,ftype)=> helpers.lua("false")
+g\defmacro "nil", (vars,helpers,ftype)=> helpers.lua("nil")
+infix = (ops)->
+ for op in *ops
+ 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{"+","-","*","/","==","!=","<","<=",">",">=","^"}
+unary{"-","#","not"}
+
+g\test[[
+say "foo"
+===
+Call [say %]:
+ "foo"
+]]
+
+g\test[[
+say (4)
+===
+Call [say %]:
+ 4
+]]
+g\test[[
rule "fart": say "poot"
+===
+Call [rule % %]:
+ "fart"
+ Thunk:
+ Call [say %]:
+ "poot"
+]]
+
+g\test[[
rule "doublefart":
say "poot"
say "poot"
+===
+Call [rule % %]:
+ "doublefart"
+ Thunk:
+ Call [say %]:
+ "poot"
+ Call [say %]:
+ "poot"
+]]
-fart
-doublefart
-
-rule "say both %x and %y":
- say %x
- say %y
-
-say both "vars" and "work!"
-
-say ( return "subexpressions work" )
+g\test[[
+say (subexpressions work)
+===
+Call [say %]:
+ Call [subexpressions work]!
+]]
-say "goodbye"
+g\test[[
+say ["lists", "work"]
+===
+Call [say %]:
+ List:
+ "lists"
+ "work"
+]]
-say [1,2,3]
+g\test[[
+say []
+===
+Call [say %]:
+ <Empty List>
+]]
+g\test[[
say [..]
- 1, 2, 3
- 4, 5
+ 1, 2
+ 3
+===
+Call [say %]:
+ List:
+ 1
+ 2
+ 3
+]]
+g\test[[
say both [..]
- 1,2,3
+ 1,2
..and [..]
- 4,5,6
+ 3,4
+===
+Call [say both % and %]:
+ List:
+ 1
+ 2
+ List:
+ 3
+ 4
+]]
+g\test[[
say both..
"hello"
and "world"
+===
+Call [say both % and %]:
+ "hello"
+ "world"
+]]
+
+g\test[[
+say both ..
+ "a list:"
+ and [..]
+ 1,2,(three),(4)
+===
+Call [say both % and %]:
+ "a list:"
+ List:
+ 1
+ 2
+ Call [three]!
+ 4
+]]
+
+g\test[[
+if 1: yes
+..else: no
+===
+Call [if % % else %]:
+ 1
+ Thunk:
+ Call [yes]!
+ Thunk:
+ Call [no]!
+]]
+g\test[[
+if 1: yes ..else: no
+===
+Call [if % % else %]:
+ 1
+ Thunk:
+ Call [yes]!
+ Thunk:
+ Call [no]!
+]]
+g\test[[
+say (do: return 5)
+===
+Call [say %]:
+ Call [do %]:
+ Thunk:
+ Call [return %]:
+ 5
+]]
+g\test[[
+say (..)
+ fn call
+===
+Call [say %]:
+ Call [fn call]!
+]]
+
+g\run[[
say [..]
"this is a stupidly long list", "the items go way past the 80 character", "limit that older consoles"
@@ -76,14 +207,141 @@ rule "dumbfunc %a %b %c %d %e":
say "doop"
dumbfunc..
- "this is a stupidly long list" "the items go way past the 80 character" "limit that older consoles"
+ "this is a stupidly long set of arguments" "the items go way past the 80 character" "limit that older consoles"
"had." "It just keeps going and going"
+]]
+g\run[[
rule "four": return 4
-say both..
+rule "say both %one and %two":
+ say %one
+ say %two
+
+say both ..
"a list:"
and [..]
1,2,3,(four),(5)
say "done"
]]
+
+g\defmacro "if %condition %if_body else %else_body", (vars,helpers,ftype)=>
+ with helpers
+ switch ftype
+ when "Expression"
+ .lua "((#{.ded(.transform(vars.condition))}) and"
+ .indented ->
+ .lua "("..(.ded(.transform(vars.if_body)))..")"
+ .lua "or ("..(.ded(.transform(vars.if_body))).."))(game, vars)"
+ when "Statement"
+ .lua("if (#{.ded(.transform(vars.condition))}) then")
+ .indented ->
+ if_body = vars.if_body
+ while if_body.type != "Block"
+ if_body = if_body.value
+ if if_body == nil then error("Failed to find body.")
+ for statement in *if_body.value
+ .lua(.ded(.transform(statement)))
+ .lua("else")
+ .indented ->
+ else_body = vars.else_body
+ while else_body.type != "Block"
+ else_body = else_body.value
+ if else_body == nil then error("Failed to find body.")
+ for statement in *else_body.value
+ .lua(.ded(.transform(statement)))
+ .lua("end")
+ return nil
+
+g\defmacro "for %varname in %iterable %body", (vars,helpers,ftype)=>
+ with helpers
+ switch ftype
+ when "Expression"
+ .lua "(function(game, vars)"
+ .indented ->
+ .lua "local comprehension, vars = {}, setmetatable({}, {__index=vars})"
+ .lua "for i, value in ipairs(#{.ded(.transform(vars.iterable))}) do"
+ .indented ->
+ .lua "local comp_value"
+ .lua "vars[#{.ded(.transform(vars.varname))}] = value"
+ body = vars.body
+ while body.type != "Block"
+ body = body.value
+ if body == nil then error("Failed to find body.")
+ for statement in *body.value
+ -- TODO: Clean up this ugly bit
+ .lua("comp_value = "..(.ded(.transform(statement.value, {type:"Expression"}))))
+ .lua "table.insert(comprehension, comp_value)"
+ .lua "end"
+ .lua "return comprehension"
+ .lua "end)(game,vars)"
+ when "Statement"
+ .lua "do"
+ .indented ->
+ .lua "local vars = setmetatable({}, {__index=vars})"
+ .lua "for i, value in ipairs(#{.ded(.transform(vars.iterable))}) do"
+ .indented ->
+ .lua "vars[#{.ded(.transform(vars.varname))}] = value"
+ body = vars.body
+ while body.type != "Block"
+ body = body.value
+ if body == nil then error("Failed to find body.")
+ for statement in *body.value
+ .lua(.ded(.transform(statement)))
+ .lua "end"
+ .lua "end"
+ return nil
+
+--g\defmacro "if %condition %if_body", "if %condition %if_body else: return nil"
+
+g\def [[do %action]], (vars)=> return vars.action(self,vars)
+g\run[[
+rule "do %thing also %also-thing":
+ do %thing
+ do %also-thing
+ return 99
+
+do: say "one liner"
+..also: say "another one liner"
+
+say (..)
+ do:
+ say "hi"
+ return 5
+ say "bye"
+
+say (do: return "wow")
+if 1: say "hi1" ..else: say "bye1"
+
+if 1: say "hi2"
+..else: say "bye2"
+
+]]
+g\run[[
+rule "foo %x":
+ if %x:
+ say "YES"
+ 55
+ ..else:
+ say "NO"
+ -99
+
+say (foo 1)
+say (foo (false))
+
+]]
+
+g\run[[
+say (1 + (-(2 * 3)))
+]]
+
+g\run_debug[[
+for "x" in ["A","B","C"]:
+ say %x
+]]
+g\run_debug[[
+say (for "x" in [1,2,3]:%x + 100)
+say (..)
+ for "x" in [1,2,3]:
+ %x + 200
+]]
diff --git a/nomic_whitespace.moon b/nomic_whitespace.moon
index 022cdf4..783c29c 100644
--- a/nomic_whitespace.moon
+++ b/nomic_whitespace.moon
@@ -39,14 +39,14 @@ add_indent_tokens = (str)->
return
indent = get_line_indentation(line)
if indent > indent_stack[#indent_stack]
- table.insert result, "{\n " --(" ")\rep(indent_stack[#indent_stack]+1).."{\n "
+ 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, "}" --(" ")\rep(indent_stack[#indent_stack]+1).."}"
+ table.insert tokens, "}"
table.insert tokens, " "
table.insert result, table.concat(tokens, "\n")
else
@@ -67,39 +67,48 @@ add_indent_tokens = (str)->
while #indent_stack > 1
table.remove indent_stack
table.insert result, "}\n"
- return table.concat result
+ return (table.concat result)\sub(1,-2)
lingo = [=[
file <- ({} {| {:body: (" " block) :} ({:errors: errors :})? |} {}) -> File
errors <- ({} {.+} {}) -> Errors
block <- ({} {| statement (%nodent statement)* |} {}) -> Block
- one_liner <- ({} {| statement |} {}) -> Block
- statement <- ({} functioncall {}) -> Statement
-
- functioncall <- ({} {| fn_bits |} {}) -> FunctionCall
+ 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 <-
- ((".." (%indent %nodent indented_fn_bits %dedent))
- / fn_bit) (fn_sep 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 ((%ws / %nodent) indented_fn_bits)?
+ fn_bit ((%nodent / %word_boundary) indented_fn_bits)?
- fn_sep <- (%nodent ".." %ws?) / %ws / (&":") / (&"..") / (&'"') / (&"[")
-
thunk <-
- ({} ":" %ws? ((one_liner (%ws? ";")?) / (%indent %nodent block %dedent)) {}) -> Thunk
+ ({} ":" %ws?
+ ((%indent %nodent block %dedent (%nodent "..")?)
+ / (one_liner (%ws? ((%nodent? "..")))?)) {}) -> Thunk
- word <- ({} {%wordchar+} {}) -> Word
- expression <- string / number / variable / list / thunk / subexpression
+ 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? (expression / functioncall) %ws? ")"
+ subexpression <-
+ ("(" %ws? (functioncall / expression) %ws? ")")
+ / ("(..)" %ws? %indent %nodent (expression / (({} {| indented_fn_bits |} {}) -> FunctionCall)) %dedent (%nodent "..")?)
list <- ({} {|
- ("[..]" %indent %nodent indented_list ","? %dedent)
+ ("[..]" %ws? %indent %nodent indented_list ","? %dedent (%nodent "..")?)
/ ("[" %ws? (list_items ","?)? %ws?"]")
|} {}) -> List
list_items <- (expression (list_sep list_items)?)
@@ -115,6 +124,7 @@ defs =
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)->
@@ -166,31 +176,27 @@ class Game
invocation = table.concat name_bits, " "
return invocation, arg_names
- defmacro: (spec, fn, advanced_mode=false)=>
+ defmacro: (spec, fn)=>
invocation,arg_names = self\get_invocation spec
- if advanced_mode
- @macros[invocation] = {fn, arg_names}
- return
-
- text_manipulator = fn
- fn = (args, transform,src,indent_level,macros)->
- text_args = [transform(src,a,indent_level,macros) for a in *args]
- return text_manipulator(unpack(text_args))
+
+ 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("RUNNING TEXT:\n")
print(text)
- indentified = add_indent_tokens(text)
- print("Indentified:\n[[#{indentified}]]")
- print("\nCompiling...")
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")
+ error("Failed to compile generated code:\n#{code}")
action = lua_thunk!
if @debug
print("Running...")
@@ -211,25 +217,29 @@ class Game
print("\nINDENTIFIED:\n#{indentified}")
tree = lingo\match indentified
if @debug
- print("\nRESULT:\n#{utils.repr(tree)}")
+ print("\nPARSE TREE:")
+ self\print_tree(tree)
assert tree, "Failed to parse: #{str}"
return tree
- transform: (tree, indent_level=0)=>
+ transform: (tree, indent_level=0, parent=nil)=>
indented = (fn)->
export indent_level
indent_level += 1
fn!
indent_level -= 1
- transform = (t)-> self\transform(t, indent_level)
+ transform = (t,parent)-> self\transform(t, indent_level, parent or tree)
ind = (line) -> (" ")\rep(indent_level)..line
- ded = (lines)-> lines\match"^%s*(.*)"
+ 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
@@ -250,12 +260,15 @@ class Game
switch tree.type
when "File"
- if tree.value.errors and #tree.value.errors.value > 1
- return transform(tree.value.errors)
+ 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"
@@ -272,12 +285,20 @@ class Game
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"
- lua "ret = #{ded(transform(tree.value))}"
+ 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 = {}
@@ -285,13 +306,20 @@ class Game
table.insert name_bits, if token.type == "Word" then token.value else "%"
name = table.concat(name_bits, " ")
if @macros[name]
- -- TODO: figure out args
+ {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"]
- return @macros[name][1](self, args, transform)
-
- 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, ")")
+ 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)
@@ -311,15 +339,99 @@ class Game
lua "vars[#{utils.repr(tree.value,true)}]"
else
- error("Unknown/unimplemented thingy: #{utils.repr(tree)}")
+ 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)
- return table.concat ret_lines, "\n"
+ 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 5394542..a048f2c 100644
--- a/utils.moon
+++ b/utils.moon
@@ -30,7 +30,7 @@ utils = {
tostring(x)
split: (str, sep="%s")->
- [chunk for chunk in str\gmatch("[^#{sep}]")]
+ [chunk for chunk in str\gmatch("[^#{sep}]+")]
keys: (t)-> [k for k in pairs(t)]
values: (t)-> [v for _,v in pairs(t)]