Totally working everything (probably) with if and for macros.

This commit is contained in:
Bruce Hill 2017-08-22 00:41:25 -07:00
parent ecc4e82919
commit 4713d7db0d
3 changed files with 456 additions and 86 deletions

View File

@ -3,70 +3,201 @@ utils = require 'utils'
Game = require 'nomic_whitespace' Game = require 'nomic_whitespace'
g = Game() g = Game()
print("===========================================================================================")
g\def "rule %spec %body", (vars)=> g\def "rule %spec %body", (vars)=>
self\def vars.spec, vars.body self\def vars.spec, vars.body
print "Defined rule: #{vars.spec}" 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)=> g\def "say %x", (vars)=>
print(utils.repr(vars.x)) print(utils.repr(vars.x))
g\def "return %x", (vars)=> g\defmacro "return %retval", (vars,helpers,ftype)=>
return vars.x 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[[ return nil
say "hello world!"
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" rule "fart": say "poot"
===
Call [rule % %]:
"fart"
Thunk:
Call [say %]:
"poot"
]]
g\test[[
rule "doublefart": rule "doublefart":
say "poot" say "poot"
say "poot" say "poot"
===
Call [rule % %]:
"doublefart"
Thunk:
Call [say %]:
"poot"
Call [say %]:
"poot"
]]
fart g\test[[
doublefart say (subexpressions work)
===
Call [say %]:
Call [subexpressions work]!
]]
rule "say both %x and %y": g\test[[
say %x say ["lists", "work"]
say %y ===
Call [say %]:
List:
"lists"
"work"
]]
say both "vars" and "work!" g\test[[
say []
say ( return "subexpressions work" ) ===
Call [say %]:
say "goodbye" <Empty List>
]]
say [1,2,3]
g\test[[
say [..] say [..]
1, 2, 3 1, 2
4, 5 3
===
Call [say %]:
List:
1
2
3
]]
g\test[[
say both [..] say both [..]
1,2,3 1,2
..and [..] ..and [..]
4,5,6 3,4
===
Call [say both % and %]:
List:
1
2
List:
3
4
]]
g\test[[
say both.. say both..
"hello" "hello"
and "world" 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 [..] say [..]
"this is a stupidly long list", "the items go way past the 80 character", "limit that older consoles" "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" say "doop"
dumbfunc.. 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" "had." "It just keeps going and going"
]]
g\run[[
rule "four": return 4 rule "four": return 4
say both.. rule "say both %one and %two":
say %one
say %two
say both ..
"a list:" "a list:"
and [..] and [..]
1,2,3,(four),(5) 1,2,3,(four),(5)
say "done" 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
]]

View File

@ -39,14 +39,14 @@ add_indent_tokens = (str)->
return return
indent = get_line_indentation(line) indent = get_line_indentation(line)
if indent > indent_stack[#indent_stack] 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 table.insert indent_stack, indent
elseif indent < indent_stack[#indent_stack] elseif indent < indent_stack[#indent_stack]
dedents = 0 dedents = 0
tokens = {} tokens = {}
while indent < indent_stack[#indent_stack] while indent < indent_stack[#indent_stack]
table.remove indent_stack table.remove indent_stack
table.insert tokens, "}" --(" ")\rep(indent_stack[#indent_stack]+1).."}" table.insert tokens, "}"
table.insert tokens, " " table.insert tokens, " "
table.insert result, table.concat(tokens, "\n") table.insert result, table.concat(tokens, "\n")
else else
@ -67,39 +67,48 @@ add_indent_tokens = (str)->
while #indent_stack > 1 while #indent_stack > 1
table.remove indent_stack table.remove indent_stack
table.insert result, "}\n" table.insert result, "}\n"
return table.concat result return (table.concat result)\sub(1,-2)
lingo = [=[ lingo = [=[
file <- ({} {| {:body: (" " block) :} ({:errors: errors :})? |} {}) -> File file <- ({} {| {:body: (" " block) :} ({:errors: errors :})? |} {}) -> File
errors <- ({} {.+} {}) -> Errors errors <- ({} {.+} {}) -> Errors
block <- ({} {| statement (%nodent statement)* |} {}) -> Block block <- ({} {| statement (%nodent statement)* |} {}) -> Block
one_liner <- ({} {| statement |} {}) -> Block statement <- ({} (functioncall / expression) {}) -> Statement
statement <- ({} functioncall {}) -> Statement one_liner <- ({} {|
(({}
(({} {|
(expression (%word_boundary fn_bit)+) / (word (%word_boundary fn_bit)*)
|} {}) -> FunctionCall)
{}) -> Statement)
|} {}) -> Block
functioncall <- ({} {| fn_bits |} {}) -> FunctionCall functioncall <- ({} {| (expression %word_boundary fn_bits) / (word (%word_boundary fn_bits)?) |} {}) -> FunctionCall
fn_bit <- (expression / word) fn_bit <- (expression / word)
fn_bits <- fn_bits <-
((".." (%indent %nodent indented_fn_bits %dedent)) ((".." %ws? (%indent %nodent indented_fn_bits %dedent) (%nodent ".." %ws? fn_bits)?)
/ fn_bit) (fn_sep fn_bits)? / (%nodent ".." fn_bit fn_bits)
/ (fn_bit (%word_boundary fn_bits)?))
indented_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 <- thunk <-
({} ":" %ws? ((one_liner (%ws? ";")?) / (%indent %nodent block %dedent)) {}) -> Thunk ({} ":" %ws?
((%indent %nodent block %dedent (%nodent "..")?)
/ (one_liner (%ws? ((%nodent? "..")))?)) {}) -> Thunk
word <- ({} {%wordchar+} {}) -> Word word <- ({} !number {%wordchar+} {}) -> Word
expression <- string / number / variable / list / thunk / subexpression expression <- ({} (string / number / variable / list / thunk / subexpression) {}) -> Expression
string <- ({} '"' {(("\\" .) / [^"])*} '"' {}) -> String string <- ({} '"' {(("\\" .) / [^"])*} '"' {}) -> String
number <- ({} {'-'? [0-9]+ ("." [0-9]+)?} {}) -> Number number <- ({} {'-'? [0-9]+ ("." [0-9]+)?} {}) -> Number
variable <- ({} ("%" {%wordchar+}) {}) -> Var variable <- ({} ("%" {%wordchar+}) {}) -> Var
subexpression <- "(" %ws? (expression / functioncall) %ws? ")" subexpression <-
("(" %ws? (functioncall / expression) %ws? ")")
/ ("(..)" %ws? %indent %nodent (expression / (({} {| indented_fn_bits |} {}) -> FunctionCall)) %dedent (%nodent "..")?)
list <- ({} {| list <- ({} {|
("[..]" %indent %nodent indented_list ","? %dedent) ("[..]" %ws? %indent %nodent indented_list ","? %dedent (%nodent "..")?)
/ ("[" %ws? (list_items ","?)? %ws?"]") / ("[" %ws? (list_items ","?)? %ws?"]")
|} {}) -> List |} {}) -> List
list_items <- (expression (list_sep list_items)?) list_items <- (expression (list_sep list_items)?)
@ -115,6 +124,7 @@ defs =
indent: linebreak * lpeg.P("{") * lpeg.S(" \t")^0 indent: linebreak * lpeg.P("{") * lpeg.S(" \t")^0
nodent: linebreak * lpeg.P(" ") * lpeg.S(" \t")^0 nodent: linebreak * lpeg.P(" ") * lpeg.S(" \t")^0
dedent: 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, { setmetatable(defs, {
__index: (t,key)-> __index: (t,key)->
@ -166,31 +176,27 @@ class Game
invocation = table.concat name_bits, " " invocation = table.concat name_bits, " "
return invocation, arg_names return invocation, arg_names
defmacro: (spec, fn, advanced_mode=false)=> defmacro: (spec, fn)=>
invocation,arg_names = self\get_invocation spec invocation,arg_names = self\get_invocation spec
if advanced_mode
@macros[invocation] = {fn, arg_names}
return
text_manipulator = fn if type(fn) == 'string'
fn = (args, transform,src,indent_level,macros)-> error("not implemented")
text_args = [transform(src,a,indent_level,macros) for a in *args] str = fn
return text_manipulator(unpack(text_args)) fn = (vars,helper,ftype)=> nil
@macros[invocation] = {fn, arg_names} @macros[invocation] = {fn, arg_names}
run: (text)=> run: (text)=>
if @debug if @debug
print("Running text:\n") print("RUNNING TEXT:\n")
print(text) print(text)
indentified = add_indent_tokens(text)
print("Indentified:\n[[#{indentified}]]")
print("\nCompiling...")
code = self\compile(text) code = self\compile(text)
if @debug if @debug
print("\nGENERATED LUA CODE:")
print(code) print(code)
lua_thunk, err = loadstring(code) lua_thunk, err = loadstring(code)
if not lua_thunk if not lua_thunk
error("Failed to compile") error("Failed to compile generated code:\n#{code}")
action = lua_thunk! action = lua_thunk!
if @debug if @debug
print("Running...") print("Running...")
@ -211,25 +217,29 @@ class Game
print("\nINDENTIFIED:\n#{indentified}") print("\nINDENTIFIED:\n#{indentified}")
tree = lingo\match indentified tree = lingo\match indentified
if @debug if @debug
print("\nRESULT:\n#{utils.repr(tree)}") print("\nPARSE TREE:")
self\print_tree(tree)
assert tree, "Failed to parse: #{str}" assert tree, "Failed to parse: #{str}"
return tree return tree
transform: (tree, indent_level=0)=> transform: (tree, indent_level=0, parent=nil)=>
indented = (fn)-> indented = (fn)->
export indent_level export indent_level
indent_level += 1 indent_level += 1
fn! fn!
indent_level -= 1 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 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 = {} ret_lines = {}
lua = (line, skip_indent=false)-> lua = (line, skip_indent=false)->
unless skip_indent unless skip_indent
line = ind(ded(line)) line = ind(ded(line))
table.insert ret_lines, line table.insert ret_lines, line
return line
comma_separated_items = (open, items, close)-> comma_separated_items = (open, items, close)->
buffer = open buffer = open
@ -250,12 +260,15 @@ class Game
switch tree.type switch tree.type
when "File" when "File"
if tree.value.errors and #tree.value.errors.value > 1 if tree.value.errors and #tree.value.errors.value > 0
return transform(tree.value.errors) ret = transform(tree.value.errors)
return ret
lua "return (function(game, vars)" lua "return (function(game, vars)"
indented -> indented ->
lua "local ret"
lua transform(tree.value.body) lua transform(tree.value.body)
lua "return ret"
lua "end)" lua "end)"
when "Errors" when "Errors"
@ -272,12 +285,20 @@ class Game
lua "(function(game,vars)" lua "(function(game,vars)"
indented -> indented ->
lua "local ret" lua "local ret"
assert tree.value.type == "Block", "Non-block value in Thunk"
lua transform(tree.value) lua transform(tree.value)
lua "return ret" lua "return ret"
lua "end)" lua "end)"
when "Statement" 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" when "FunctionCall"
name_bits = {} name_bits = {}
@ -285,13 +306,20 @@ class Game
table.insert name_bits, if token.type == "Word" then token.value else "%" table.insert name_bits, if token.type == "Word" then token.value else "%"
name = table.concat(name_bits, " ") name = table.concat(name_bits, " ")
if @macros[name] 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"] args = [a for a in *tree.value when a.type != "Word"]
return @macros[name][1](self, args, transform) args = {name,args[i] for i,name in ipairs(arg_names)}
helpers.var = (varname)->
args = [ded(transform(a)) for a in *tree.value when a.type != "Word"] ded(transform(args[varname]))
table.insert args, 1, utils.repr(name, true) m = fn(self, args, helpers, parent.type)
comma_separated_items("game:call(", args, ")") 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" when "String"
lua utils.repr(tree.value, true) lua utils.repr(tree.value, true)
@ -311,15 +339,99 @@ class Game
lua "vars[#{utils.repr(tree.value,true)}]" lua "vars[#{utils.repr(tree.value,true)}]"
else else
error("Unknown/unimplemented thingy: #{utils.repr(tree)}") error("Unknown/unimplemented thingy: #{tree.type}")
return table.concat ret_lines, "\n" 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)=> compile: (src)=>
tree = self\parse(src) tree = self\parse(src)
code = self\transform(tree,0) code = self\transform(tree,0)
return code 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 return Game

View File

@ -30,7 +30,7 @@ utils = {
tostring(x) tostring(x)
split: (str, sep="%s")-> 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)] keys: (t)-> [k for k in pairs(t)]
values: (t)-> [v for _,v in pairs(t)] values: (t)-> [v for _,v in pairs(t)]