In-progress working whitespace stuff.

This commit is contained in:
Bruce Hill 2017-08-20 01:30:04 -07:00
parent a5d5814670
commit 19c42887bc
3 changed files with 432 additions and 21 deletions

93
game2.moon Executable file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env moon
utils = require 'utils'
Game = require 'nomic_whitespace'
g = Game()
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\run_debug[[
say "hello world!"
say (lua "23 + 42")
macro "print %y": say %y
macro "%x + %y":
lua (join ["(",%x," + ",%y,")"])
lua "(#{%x} + #{%y})"
(five) + 4
print "done"
]]
[[
rule "fart": say "poot"
rule "doublefart":
say "poot"
say "poot"
fart
doublefart
rule "say both %x and %y":
say %x
say %y
say both "vars" and "work!"
say ( return "subexpressions work" )
say "goodbye"
say [1,2,3]
say [..]
1, 2, 3
4, 5
say both [..]
1,2,3
..and [..]
4,5,6
say both..
"hello"
and "world"
rule "four": return 4
say both..
"a list:"
and [..]
1,2,3,(four),(5)
say "done"
]]

321
nomic_whitespace.moon Normal file
View File

@ -0,0 +1,321 @@
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 " --(" ")\rep(indent_stack[#indent_stack]+1).."{\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 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
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
fn_bit <- (expression / word)
fn_bits <-
((".." (%indent %nodent indented_fn_bits %dedent))
/ fn_bit) (fn_sep fn_bits)?
indented_fn_bits <-
fn_bit ((%ws / %nodent) indented_fn_bits)?
fn_sep <- (%nodent ".." %ws?) / %ws / (&":") / (&"..") / (&'"') / (&"[")
thunk <-
({} ":" %ws? ((one_liner (%ws? ";")?) / (%indent %nodent block %dedent)) {}) -> Thunk
word <- ({} {%wordchar+} {}) -> Word
expression <- string / number / variable / list / thunk / subexpression
string <- ({} '"' {(("\\" .) / [^"])*} '"' {}) -> String
number <- ({} {'-'? [0-9]+ ("." [0-9]+)?} {}) -> Number
variable <- ({} ("%" {%wordchar+}) {}) -> Var
subexpression <- "(" %ws? (expression / functioncall) %ws? ")"
list <- ({} {|
("[..]" %indent %nodent indented_list ","? %dedent)
/ ("[" %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
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, advanced_mode=false)=>
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))
@macros[invocation] = {fn, arg_names}
run: (text)=>
if @debug
print("Running text:\n")
print(text)
indentified = add_indent_tokens(text)
print("Indentified:\n[[#{indentified}]]")
print("\nCompiling...")
code = compile(text, @macros)
if @debug
print(code)
lua_thunk, err = loadstring(code)
if not lua_thunk
error("Failed to compile")
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("\nRESULT:\n#{utils.repr(tree)}")
assert tree, "Failed to parse: #{str}"
return tree
transform: (tree, indent_level=0)=>
indented = (fn)->
export indent_level
indent_level += 1
fn!
indent_level -= 1
transform = (t)-> self\transform(t, indent_level)
ind = (line) -> (" ")\rep(indent_level)..line
ded = (lines)-> lines\match"^%s*(.*)"
ret_lines = {}
lua = (line, skip_indent=false)->
unless skip_indent
line = ind(ded(line))
table.insert ret_lines, line
switch tree.type
when "File"
if tree.value.errors and #tree.value.errors.value > 1
return transform(tree.value.errors)
lua "return (function(game, vars)"
indented->
lua transform(tree.value.body)
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"
lua transform(tree.value)
lua "return ret"
lua "end)"
return table.concat ret,"\n"
when "Statement"
return ind"ret = #{transform(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, " ")
args = [a for a in *tree.value when a.type != "Word"]
if @macros[name]
-- TODO: figure out args
return @macros[name][1](args, transform,indent_level,@macros)
if #args == 0
return ind"game:call(#{utils.repr(name, true)})"
ret = {
ind"game:call(#{utils.repr(name, true)},"
}
for i,a in ipairs(args)
if a.type != "Word"
line = transform(a,indent_level+1)
if i != #args then line ..=","
table.insert ret, line
table.insert ret, ind")"
return table.concat ret, "\n"
when "String"
return ind"\"#{tree.value}\""
when "Number"
return ind(tree.value)
when "List"
if #tree.value == 0
return "{}"
elseif #tree.value == 1
return ind"{#{transform(tree.value,0)}}"
else
bits = [transform(i, indent_level+1) for i in *tree.value]
-- I like the trailing comma
return ind"{\n"..table.concat(bits, ",\n")..",\n"..ind"}"
when "Var"
return ind"vars[#{utils.repr(tree.value,true)}]"
else
error("Unknown/unimplemented thingy: #{utils.repr(tree)}")
return table.concat ret_lines, "\n"
compile: (src)=>
tree = self\parse(src)
code = self\transform(tree,0)
return code
return Game

View File

@ -11,9 +11,9 @@ utils = {
switch type(x) switch type(x)
when 'table' when 'table'
if utils.is_list x if utils.is_list x
"[#{table.concat([utils.repr(i, true) for i in *x], ", ")}]" "{#{table.concat([utils.repr(i, true) for i in *x], ", ")}}"
else else
"{#{table.concat(["[#{k}]: #{v}" for k,v in pairs x], ", ")}}" "{#{table.concat(["[#{utils.repr(k, true)}]: #{utils.repr(v, true)}" for k,v in pairs x], ", ")}}"
when 'string' when 'string'
if not add_quotes if not add_quotes
x x
@ -36,9 +36,8 @@ utils = {
values: (t)-> [v for _,v in pairs(t)] values: (t)-> [v for _,v in pairs(t)]
sum: (t)-> sum: (t)->
tot = 0 with tot = 0
for _,x in pairs(t) do tot += x for _,x in pairs(t) do tot += x
return tot
all: (t)-> all: (t)->
for _,x in pairs t for _,x in pairs t
@ -52,25 +51,23 @@ utils = {
min: (list, keyFn=((x)->x))-> min: (list, keyFn=((x)->x))->
assert utils.is_list(list), "min() expects to be operating on a list" assert utils.is_list(list), "min() expects to be operating on a list"
best = list[1] with best = list[1]
if type(keyFn) == 'table' if type(keyFn) == 'table'
keyTable = keyFn keyTable = keyFn
keyFn = (k)->keyTable[k] keyFn = (k)->keyTable[k]
for i=2,#list for i=2,#list
if keyFn(list[i]) < keyFn(best) if keyFn(list[i]) < keyFn(best)
best = list[i] best = list[i]
return best
max: (list, keyFn=((x)->x))-> max: (list, keyFn=((x)->x))->
assert utils.is_list(list), "min() expects to be operating on a list" assert utils.is_list(list), "min() expects to be operating on a list"
best = list[1] with best = list[1]
if type(keyFn) == 'table' if type(keyFn) == 'table'
keyTable = keyFn keyTable = keyFn
keyFn = (k)->keyTable[k] keyFn = (k)->keyTable[k]
for i=2,#list for i=2,#list
if keyFn(list[i]) > keyFn(best) if keyFn(list[i]) > keyFn(best)
best = list[i] best = list[i]
return best
sort: (list, keyFn=((x)->x), reverse=false)-> sort: (list, keyFn=((x)->x), reverse=false)->
assert utils.is_list(list), "min() expects to be operating on a list" assert utils.is_list(list), "min() expects to be operating on a list"