diff --git a/game2.moon b/game2.moon new file mode 100755 index 0000000..df411b0 --- /dev/null +++ b/game2.moon @@ -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" +]] diff --git a/nomic_whitespace.moon b/nomic_whitespace.moon new file mode 100644 index 0000000..3f763c7 --- /dev/null +++ b/nomic_whitespace.moon @@ -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 diff --git a/utils.moon b/utils.moon index 069cf4a..5394542 100644 --- a/utils.moon +++ b/utils.moon @@ -11,9 +11,9 @@ utils = { switch type(x) when 'table' 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 - "{#{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' if not add_quotes x @@ -36,9 +36,8 @@ utils = { values: (t)-> [v for _,v in pairs(t)] sum: (t)-> - tot = 0 - for _,x in pairs(t) do tot += x - return tot + with tot = 0 + for _,x in pairs(t) do tot += x all: (t)-> for _,x in pairs t @@ -52,25 +51,23 @@ utils = { min: (list, keyFn=((x)->x))-> assert utils.is_list(list), "min() expects to be operating on a list" - best = list[1] - if type(keyFn) == 'table' - keyTable = keyFn - keyFn = (k)->keyTable[k] - for i=2,#list - if keyFn(list[i]) < keyFn(best) - best = list[i] - return best + with best = list[1] + if type(keyFn) == 'table' + keyTable = keyFn + keyFn = (k)->keyTable[k] + for i=2,#list + if keyFn(list[i]) < keyFn(best) + best = list[i] max: (list, keyFn=((x)->x))-> assert utils.is_list(list), "min() expects to be operating on a list" - best = list[1] - if type(keyFn) == 'table' - keyTable = keyFn - keyFn = (k)->keyTable[k] - for i=2,#list - if keyFn(list[i]) > keyFn(best) - best = list[i] - return best + with best = list[1] + if type(keyFn) == 'table' + keyTable = keyFn + keyFn = (k)->keyTable[k] + for i=2,#list + if keyFn(list[i]) > keyFn(best) + best = list[i] sort: (list, keyFn=((x)->x), reverse=false)-> assert utils.is_list(list), "min() expects to be operating on a list"