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'
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
g\test[[
say (subexpressions work)
===
Call [say %]:
Call [subexpressions work]!
]]
rule "say both %x and %y":
say %x
say %y
g\test[[
say ["lists", "work"]
===
Call [say %]:
List:
"lists"
"work"
]]
say both "vars" and "work!"
say ( return "subexpressions work" )
say "goodbye"
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
]]

View File

@ -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
statement <- ({} (functioncall / expression) {}) -> 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_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}")
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)=>
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

View File

@ -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)]