From 0e916161b1b47758e0bb15051028d8c92c6446e7 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 22 Aug 2017 01:02:41 -0700 Subject: [PATCH] Updated core with new syntax. --- core.moon | 186 +++++------- game2.moon | 3 +- nomic.moon | 644 ++++++++++++++++++++++++++---------------- nomic_whitespace.moon | 437 ---------------------------- utils.moon | 1 + 5 files changed, 474 insertions(+), 797 deletions(-) delete mode 100644 nomic_whitespace.moon diff --git a/core.moon b/core.moon index 1ea22dc..8d76915 100755 --- a/core.moon +++ b/core.moon @@ -1,165 +1,107 @@ #!/usr/bin/env moon nomic = require 'nomic' utils = require 'utils' -game = nomic() +g = nomic() -game\def {[[print $str]], [[say $str]]}, (args)=> print(utils.repr(args.str)) -game\def {[[printf $str]]}, (args)=> + +g\def {"say %x", "print %x"}, (vars)=> + print(utils.repr(vars.x)) + +g\def [[printf %str]], (args)=> for s in *args.str do io.write(utils.repr(s)) io.write("\n") -game\def [[return $value]], (args)=> args.value - -game\macro [[let $var = $value]], (var, value)-> - "locals[#{var}] = #{value}" - -game\def [[$signature := $body]], (args)=> - invocations = if type(args.signature) == 'table' then args.signature else {args.signature} - for i in *invocations - unless @check_authorization(i) - print "You are not permitted to redefine this function" - return - - @\def args.signature, args.body - print "Defined new rule: #{utils.repr(args.signature)}" - --print debug.getinfo(args.body, "S").source +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}" return nil -game\def [[compile $body]], (args)=> - print debug.getinfo(args.body, "S").source +g\defmacro "let %varname = %value", (vars, helpers, ftype)=> + with helpers + if ftype == "Expression" then error("Cannot set a variable in an expression.") + .lua "vars[#{.ded(.transform(vars.varname))} = #{.ded(.transform(vars.value))}" return nil -game\def [[help $invocation]], (args)=> - print_rule = (invocation, rule)-> - print "\"#{invocation}\" :=" - info = debug.getinfo(rule, "S") - i = 0 - for line in info.source\gmatch("[^\n]+") - i+=1 - if i >= info.linedefined+1 - print(line) - if i >= info.lastlinedefined-1 - return - print " #{info.short_src}:#{info.linedefined}-#{info.lastlinedefined}" +g\defmacro {"true", "yes"}, (vars,helpers,ftype)=> helpers.lua("true") +g\defmacro {"false", "no"}, (vars,helpers,ftype)=> helpers.lua("false") +g\defmacro "nil", (vars,helpers,ftype)=> helpers.lua("nil") +infix = (ops)-> + for op in *ops + alias = op + if type(op) == 'table' + {alias,op} = op + 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{"+","-","*","/","==",{"!=","~="},"<","<=",">",">=","^","and","or"} +unary{"-","#","not"} +g\def [[%x == %y]], (args)=> utils.equivalent(args.x, args.y) - with args - if rule = @rules[.invocation] - print_rule .invocation, rule - return nil - words = [w for w in .invocation\gmatch("%S+")] - match_count = (i)-> - iws = [w for w in i\gmatch("[^ $]+")] - count = 0 - for w in *words - for iw in *iws - if w == iw then count += 1 - count += 1/#iws - return count - rules = {} - invocations = {} - for i,r in pairs(@rules) - c = match_count(i) - if c > (rules[r] or 0) - rules[r] = c - invocations[r] = i - best = [r for r in pairs rules] - utils.sort best, rules - if rules[best[1]] > 0 - for r in *best - if rules[r] < rules[best[1]] - break - print_rule invocations[r], r - return nil +g\def "rule %spec %body", (vars)=> + self\def vars.spec, vars.body + print "Defined rule: #{utils.repr(vars.spec)}" -game\macro "true", -> "true" -game\macro "yes", -> "true" -game\macro "false", -> "false" -game\macro "no", -> "false" -game\macro "nil", -> "nil" -game\macro "None", -> "nil" -game\macro "null", -> "nil" -game\def [[nop]], ((args)=> nil), "... does nothing, returns nil ..." - -game\def [[$x == $y]], (args)=> utils.equivalent(args.x, args.y) -game\run [=[ - ["$x != $y", "$x <> $y", "$x ~= $y"] := {return (not (x == y))} -]=] -game\def [[$x < $y]], (args)=> args.x < args.y -game\def [[$x <= $y]], (args)=> args.x <= args.y -game\def [[$x > $y]], (args)=> args.x > args.y -game\def [[$x >= $y]], (args)=> args.x >= args.y - -game\macro [[$x + $y]], (x,y)-> "(#{x} + #{y})" -game\macro [[$x - $y]], (x,y)-> "(#{x} - #{y})" -game\macro [[$x * $y]], (x,y)-> "(#{x} * #{y})" -game\macro [[$x / $y]], (x,y)-> "(#{x} / #{y})" -game\macro [[$x ^ $y]], (x,y)-> "(#{x} ^ #{y})" - -game\macro [[$x < $y]], (x,y)-> "(#{x} < #{y})" -game\macro [[$x <= $y]], (x,y)-> "(#{x} <= #{y})" -game\macro [[$x > $y]], (x,y)-> "(#{x} > #{y})" -game\macro [[$x >= $y]], (x,y)-> "(#{x} >= #{y})" - -game\macro [[not $x]], (x)-> "(not #{x})" -game\macro [[$x and $y]], (x,y)-> "(#{x} and #{y})" -game\macro [[$x or $y]], (x,y)-> "(#{x} or #{y})" +-- TODO: write help -[==[ -game\def [[if $condition $body else $else_body]], (args)=> - with args - if .condition - return .body(@, args) - else return .else_body(@, args) +g\def [[random]], -> math.random() -game\run [=[ - ["if $condition $body", "when $condition $body"] := {if $condition $body else {}} - ["unless $condition $body"] := {if (not $condition) $body else {}} - ["unless $condition $body else $else_body"] := {if (not $condition) $body else $else_body} -]=] -]==] - -game\def [[random]], -> math.random() - -game\def [[sum $items]], (args)=> utils.sum(args.items) -game\def [[all $items]], (args)=> utils.all(args.items) -game\def [[any $items]], (args)=> utils.any(args.items) -game\def {[[average $items]], [[avg $items]]}, (args)=> utils.sum(items)/#items -game\def {[[min $items]], [[smallest $items]], [[lowest $items]], [[fewest $items]]}, (args)=> +g\def [[sum %items]], (args)=> utils.sum(args.items) +g\def [[all %items]], (args)=> utils.all(args.items) +g\def [[any %items]], (args)=> utils.any(args.items) +g\def {[[average %items]], [[avg %items]]}, (args)=> utils.sum(items)/#items +g\def {[[min %items]], [[smallest %items]], [[lowest %items]], [[fewest %items]]}, (args)=> utils.min(args.items) -game\def {[[max $items]], [[largest $items]], [[highest $items]], [[most $items]]}, (args)=> +g\def {[[max %items]], [[largest %items]], [[highest %items]], [[most %items]]}, (args)=> utils.max(args.items) -game\def {[[argmin $items]]}, (args)=> +g\def {[[argmin %items]]}, (args)=> utils.min(args.items, ((i)->i[2])) -game\def {[[argmax $items]]}, (args)=> +g\def {[[argmax %items]]}, (args)=> utils.max(args.items, ((i)->i[2])) -game\def {[[min $items with respect to $keys]]}, (args)=> +g\def {[[min %items with respect to %keys]]}, (args)=> utils.min(args.items, args.keys) -game\def {[[max $items with respect to $keys]]}, (args)=> +g\def {[[max %items with respect to %keys]]}, (args)=> utils.max(args.items, args.keys) -game\def {[[$index st in $list]], [[$index nd in $list]], [[$index rd in $list]], [[$index th in $list]]}, (args)=> +g\def {[[%index st in %list]], [[%index nd in %list]], [[%index rd in %list]], [[%index th in %list]]}, (args)=> with args if type(.list) != 'table' print "Not a list: #{.list}" return .list[.index] -game\def {[[index of $item in $list]]}, (args)=> +g\def {[[index of %item in %list]]}, (args)=> with args if type(.list) != 'table' print "Not a list: #{.list}" return utils.key_for(args.list, args.item) -game\run [=[ - ["$item is in $list", "$list contains $item"] := {(index of $item in $list) != (nil)} +g\run [=[ +rule ["%item is in %list", "%list contains %item"]: (index of %item in %list) != (nil) ]=] -game\def {[[# $list]], [[length of $list]], [[size of $list]]}, (args)=> +g\def {[[# %list]], [[length of %list]], [[size of %list]]}, (args)=> with args if type(.list) != 'table' print "Not a list: #{.list}" diff --git a/game2.moon b/game2.moon index a95070a..e487991 100755 --- a/game2.moon +++ b/game2.moon @@ -1,6 +1,6 @@ #!/usr/bin/env moon utils = require 'utils' -Game = require 'nomic_whitespace' +Game = require 'nomic' g = Game() print("===========================================================================================") @@ -21,7 +21,6 @@ g\defmacro "return %retval", (vars,helpers,ftype)=> .lua "do return "..(.ded(.transform(vars.retval))).." end" else error"Unknown: #{ftype}" - return nil g\defmacro "true", (vars,helpers,ftype)=> helpers.lua("true") diff --git a/nomic.moon b/nomic.moon index 12bfb08..a8eb4d5 100644 --- a/nomic.moon +++ b/nomic.moon @@ -2,268 +2,440 @@ re = require 're' lpeg = require 'lpeg' utils = require 'utils' moon = require 'moon' -type = moon.type -currently_parsing = nil -macros = nil -indentation = 0 -indents = -> - (" ")\rep(indentation) -indent = -> - export indentation - indentation += 1 -dedent = -> - export indentation - indentation -= 1 -indent_block = (block)-> - block = block\gsub("\n", "\n"..indents!) - return indents!..block -add_line = (lines, new_line)-> - table.insert lines, (indents!..new_line) +lpeg.setmaxstack 10000 -- whoa -compactify_invocation = (raw_invocation)-> - name_bits = {} - arg_names = {} - for chunk in raw_invocation\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 +linebreak = lpeg.P("\r")^-1 * lpeg.P("\n") -Number = (s)-> s +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] -String = (s)-> '"'..s..'"' +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" -List = (t)-> "{" .. table.concat(t, ", ") .. "}" +pos_to_line_no = (str,pos)-> + with line = 1 + for _ in str\sub(1, pos)\gmatch("\n") + line += 1 -Var = (s)-> "locals[\"#{s}\"]" +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 " + 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, "}" + 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 -Word = (s)-> setmetatable({type:'word', text:s}, {__tostring:=> error("Cannot convert word \"#{@text}\" to string")}) + 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)\sub(1,-2) -Conditional = (condition, if_block, else_block)-> - ret = {} - add_line ret, "(function()" - indent! - add_line ret, "local ret" - table.insert ret, indent_block("local condition = #{condition}") +lingo = [=[ + file <- ({} {| {:body: (" " block) :} ({:errors: errors :})? |} {}) -> File + errors <- ({} {.+} {}) -> Errors + block <- ({} {| statement (%nodent statement)* |} {}) -> Block + statement <- ({} (functioncall / expression) {}) -> Statement + one_liner <- ({} {| + (({} + (({} {| + (expression (%word_boundary fn_bit)+) / (word (%word_boundary fn_bit)*) + |} {}) -> FunctionCall) + {}) -> Statement) + |} {}) -> Block - add_line ret, "if condition then" - indent! - table.insert ret, indent_block("ret = (#{if_block})(game, locals)") - if else_block - dedent! - add_line ret, "else" - indent! - table.insert ret, indent_block("ret = (#{else_block})(game, locals)") - dedent! - add_line ret, "end" - add_line ret, "return ret" - dedent! - add_line ret, "end)()" - code = table.concat(ret, "\n") - return code + functioncall <- ({} {| (expression %word_boundary fn_bits) / (word (%word_boundary fn_bits)?) |} {}) -> FunctionCall + fn_bit <- (expression / word) + 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 ((%nodent / %word_boundary) indented_fn_bits)? + + thunk <- + ({} ":" %ws? + ((%indent %nodent block %dedent (%nodent "..")?) + / (one_liner (%ws? ((%nodent? "..")))?)) {}) -> Thunk -FunctionCall = (tokens)-> - words = [(if t.type == 'word' then t.text else "$") for t in *tokens] - args = [t for t in *tokens when t.type != 'word'] - rule_name = table.concat(words, " ") - if rule_name == "$" - error("Empty rule: #{utils.repr(tokens)}") + word <- ({} !number {%wordchar+} {}) -> Word + expression <- ({} (string / number / variable / list / thunk / subexpression) {}) -> Expression - if macros[rule_name] - return macros[rule_name](unpack(args)) + string <- ({} '"' {(("\\" .) / [^"])*} '"' {}) -> String + number <- ({} {'-'? [0-9]+ ("." [0-9]+)?} {}) -> Number + variable <- ({} ("%" {%wordchar+}) {}) -> Var - if #args == 0 - return indent_block("game:call(\"#{rule_name}\")") + subexpression <- + ("(" %ws? (functioncall / expression) %ws? ")") + / ("(..)" %ws? %indent %nodent (expression / (({} {| indented_fn_bits |} {}) -> FunctionCall)) %dedent (%nodent "..")?) - ret = {} - add_line ret, "game:call(\"#{rule_name}\"," - indent! - arg_strs = [indent_block(arg) for arg in *args] - dedent! - table.insert ret, table.concat(arg_strs, ",\n") - add_line ret, ")" + list <- ({} {| + ("[..]" %ws? %indent %nodent indented_list ","? %dedent (%nodent "..")?) + / ("[" %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)? +]=] - code = table.concat(ret, "\n") - return code +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 + word_boundary: lpeg.S(" \t")^1 + lpeg.B(lpeg.P("..")) + lpeg.B(lpeg.S("\";)]")) + #lpeg.S("\":([") + #lpeg.P("..") -Thunk = (statements)-> - ret = {} - add_line ret, "function(game, locals)" - indent! - for i,statement in ipairs statements - -- TODO: clean up? This is a bit hacky. I should *know* if this is a var assignment. - if statement\match "locals%[\".*\"%] = .*" - table.insert ret, indent_block(statement) - elseif i == #statements - table.insert ret, indent_block("return "..statement..";") - else - table.insert ret, indent_block(statement..";") - dedent! - add_line ret, "end" - return table.concat(ret, "\n") - -lingo = [[ - actions <- {| {:startPos: {}:} (%ws*) (%nl %ws*)* (action ((%nl %ws*)+ action)*)? (%nl %ws*)* {:endPos: {}:} |} -> Thunk - action <- (&conditional conditional) / ({| token (%ws+ token)* %ws* |} -> FunctionCall) - conditional <- ("if" !%wordchars %ws* expression %ws* thunk (%ws* "else" %ws* thunk %ws*)?) -> Conditional - token <- expression / (!"$" {%wordchars+} -> Word) - expression <- number / string / list / variable / thunk / subexpression - number <- ('-'? [0-9]+ ("." [0-9]+)?) -> Number - string <- ('"' {(("\\" .) / [^"])*} '"') -> String - list <- ({| '[' %ws* (%nl %ws*)* (expression (',' %ws* (%nl %ws*)* expression)*)? %ws* (%nl %ws*)* ']' |}) -> List - variable <- ("$" {%wordchars+}) -> Var - subexpression <- ("(" %ws* action ")") - thunk <- ("{" actions "}") - keywords <- "if" / "else" / "let" -]] -defs = { - :Var - :Word - :String - :Number - :List - :FunctionCall - :Thunk - :Conditional - ws: lpeg.S(" \t") - wordchars: lpeg.P(1)-lpeg.S(' \t\n,{}[]()"') -} +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 -cross_compile = (nomic_code)-> - export currently_parsing - old_parsing = currently_parsing - currently_parsing = nomic_code - lua_code = lingo\match nomic_code - currently_parsing = old_parsing - return lua_code - -defaulttable = (table,key)-> - new = {} - table[key] = new - return new class Game - new:(@parent)=> - @rules = setmetatable({}, {__index:parent and parent.rules or nil}) - @macros = setmetatable({}, {__index:parent and parent.macros or nil}) - @arg_names = setmetatable({}, {__index:parent and parent.arg_names or nil}) - @relations = setmetatable({}, {__index:parent and parent.relations or defaulttable}) - @invocations = setmetatable({}, {__index:parent and parent.invocations or nil}) - @authorized = setmetatable({}, {__index:parent and parent.authorized or nil}) - @callstack = {} + new:=> + @defs = {} + @macros = {} @debug = false - @you = "Anonymous" - - def: (invocations, fn)=> - if not fn then fn = false - invocations = if type(invocations) == 'table' then invocations else {invocations} - if fn then @invocations[fn] = {} - else @invocations[fn] = false - for raw_invocation in *invocations - invocation, arg_names = compactify_invocation raw_invocation - if invocation == "$" - error("Anonymous function: #{raw_invocation}") - @rules[invocation] = fn - if fn - table.insert @invocations[fn], invocation - @arg_names[invocation] = arg_names - else - @arg_names[invocation] = false - - macro: (invocation, fn)=> - invocation, _ = compactify_invocation invocation - @macros[invocation] = fn - - undefine: (invocations)=> - @\def invocations, false - - all_aliases: (invocations)=> - if type(invocations) != 'table' then invocations = {invocations} - all_aliases = {} - for i in *invocations - all_aliases[i] = true - if not @invocations[@rules[i]] - error "Could not find aliases of [[#{i}]]" - for alias in *@invocations[@rules[i]] - all_aliases[alias] = true - return [a for a in pairs(all_aliases)] - - canonicalize: (invocations)=> - if type(invocations) == 'string' - return @invocations[@rules[invocations]][1] - canonicals = {} - for i in *invocations - if @rules[i] == nil - error "Attempt to canonicalize invalid invocation: #{i}" - canonicals[@invocations[@rules[i]][1]] = true - return [c for c in pairs canonicals] - - set_whitelist: (actions, whitelist)=> - if utils.is_list whitelist then whitelist = {w,true for w in *whitelist} - for action in *@all_aliases(actions) - @authorized[action] = whitelist - - check_authorization:(action)=> - authority = @authorized[action] - return true if authority == nil - for call in *@callstack - if authority[call] then return true - return false - - run: (str, user)=> - user or= "anon" + 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("SOURCE NOMIC CODE:\n#{str}") - export macros - old_macros = macros - macros = (self.macros) - lua_code = cross_compile str - macros = old_macros + 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("\nGENERATED LUA CODE:\n#{lua_code}") + print "returned #{utils.repr(ret,true)}" + return ret - lua_thunk, err = loadstring("return "..lua_code) + def: (spec, fn)=> + invocations,arg_names = self\get_invocations spec + for invocation in *invocations + @defs[invocation] = {fn, arg_names} + + get_invocations:(text)=> + if type(text) == 'string' then text = {text} + invocations = {} + local arg_names + for _text in *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, " " + table.insert(invocations, invocation) + if arg_names and not utils.equivalent(utils.set(arg_names), utils.set(_arg_names)) + error("Conflicting argument names #{utils.repr(arg_names)} and #{utils.repr(_arg_names)} for #{utils.repr(text)}") + arg_names = _arg_names + return invocations, arg_names + + defmacro: (spec, fn)=> + invocations,arg_names = self\get_invocations spec + for invocation in *invocations + @macros[invocation] = {fn, arg_names} + + run: (text)=> + if @debug + print("RUNNING TEXT:\n") + print(text) + code = self\compile(text) + if @debug + print("\nGENERATED LUA CODE:") + print(code) + lua_thunk, err = loadstring(code) if not lua_thunk - print("Parsing: "..lua_code) - error(err) - lua_fn = lua_thunk! - - ret = lua_fn @, {} - return ret - - call: (invocation, ...)=> - if not @rules[invocation] - error "Could not find rule: '#{invocation}'" - if not @\check_authorization invocation - print "Not authorized to #{invocation} from callstack: #{utils.repr(@callstack)}" - return - table.insert @callstack, invocation - arg_names = @arg_names[invocation] - args = {...} - ret = (@rules[invocation])(@, {arg_names[i],arg for i, arg in ipairs(args)}) - table.remove @callstack - return ret - - run_debug:(...)=> + error("Failed to compile generated code:\n#{code}") + action = lua_thunk! + if @debug + print("Running...") + return action(self, {}) + + run_debug:(text)=> + old_debug = @debug @debug = true - print("Debugging:") - @run ... - @debug = false + 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("\nPARSE TREE:") + self\print_tree(tree) + assert tree, "Failed to parse: #{str}" + return tree + + transform: (tree, indent_level=0, parent=nil)=> + indented = (fn)-> + export indent_level + indent_level += 1 + fn! + indent_level -= 1 + transform = (t,parent)-> self\transform(t, indent_level, parent or tree) + ind = (line) -> (" ")\rep(indent_level)..line + 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 + so_far = indent_level*2 + indented -> + export buffer,so_far + for i,item in ipairs(items) + if i < #items then item ..= ", " + if so_far + #item >= 80 and #buffer > 0 + lua buffer + so_far -= #buffer + buffer = item + else + so_far += #item + buffer ..= item + buffer ..= close + lua buffer + + switch tree.type + when "File" + 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" + -- 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" + assert tree.value.type == "Block", "Non-block value in Thunk" + lua transform(tree.value) + lua "return ret" + lua "end)" + + when "Statement" + 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 = {} + for token in *tree.value + table.insert name_bits, if token.type == "Word" then token.value else "%" + name = table.concat(name_bits, " ") + if @macros[name] + {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 = {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) + + when "Number" + lua tree.value + + when "List" + if #tree.value == 0 + lua "{}" + elseif #tree.value == 1 + lua "{#{transform(tree.value)}}" + else + comma_separated_items("{", [ded(transform(item)) for item in *tree.value], "}") + + when "Var" + lua "vars[#{utils.repr(tree.value,true)}]" + + else + 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) + + 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("")) + 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}" - repl:=> - while true - io.write(">> ") - buf = "" - while buf\sub(-2,-1) != "\n\n" - buf ..= io.read("*line").."\n" - if buf == "exit\n\n" or buf == "quit\n\n" - break - @\run buf return Game diff --git a/nomic_whitespace.moon b/nomic_whitespace.moon deleted file mode 100644 index 783c29c..0000000 --- a/nomic_whitespace.moon +++ /dev/null @@ -1,437 +0,0 @@ -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 " - 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, "}" - 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)\sub(1,-2) - -lingo = [=[ - file <- ({} {| {:body: (" " block) :} ({:errors: errors :})? |} {}) -> File - errors <- ({} {.+} {}) -> Errors - block <- ({} {| statement (%nodent statement)* |} {}) -> Block - 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 <- - ((".." %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 ((%nodent / %word_boundary) indented_fn_bits)? - - thunk <- - ({} ":" %ws? - ((%indent %nodent block %dedent (%nodent "..")?) - / (one_liner (%ws? ((%nodent? "..")))?)) {}) -> Thunk - - 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? (functioncall / expression) %ws? ")") - / ("(..)" %ws? %indent %nodent (expression / (({} {| indented_fn_bits |} {}) -> FunctionCall)) %dedent (%nodent "..")?) - - list <- ({} {| - ("[..]" %ws? %indent %nodent indented_list ","? %dedent (%nodent "..")?) - / ("[" %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 - word_boundary: lpeg.S(" \t")^1 + lpeg.B(lpeg.P("..")) + lpeg.B(lpeg.S("\";)]")) + #lpeg.S("\":([") + #lpeg.P("..") - -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)=> - invocation,arg_names = self\get_invocation spec - - 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(text) - 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 generated code:\n#{code}") - 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("\nPARSE TREE:") - self\print_tree(tree) - assert tree, "Failed to parse: #{str}" - return tree - - transform: (tree, indent_level=0, parent=nil)=> - indented = (fn)-> - export indent_level - indent_level += 1 - fn! - indent_level -= 1 - transform = (t,parent)-> self\transform(t, indent_level, parent or tree) - ind = (line) -> (" ")\rep(indent_level)..line - 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 - so_far = indent_level*2 - indented -> - export buffer,so_far - for i,item in ipairs(items) - if i < #items then item ..= ", " - if so_far + #item >= 80 and #buffer > 0 - lua buffer - so_far -= #buffer - buffer = item - else - so_far += #item - buffer ..= item - buffer ..= close - lua buffer - - switch tree.type - when "File" - 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" - -- 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" - assert tree.value.type == "Block", "Non-block value in Thunk" - lua transform(tree.value) - lua "return ret" - lua "end)" - - when "Statement" - 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 = {} - for token in *tree.value - table.insert name_bits, if token.type == "Word" then token.value else "%" - name = table.concat(name_bits, " ") - if @macros[name] - {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 = {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) - - when "Number" - lua tree.value - - when "List" - if #tree.value == 0 - lua "{}" - elseif #tree.value == 1 - lua "{#{transform(tree.value)}}" - else - comma_separated_items("{", [ded(transform(item)) for item in *tree.value], "}") - - when "Var" - lua "vars[#{utils.repr(tree.value,true)}]" - - else - 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) - - 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("")) - 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 a048f2c..3004c58 100644 --- a/utils.moon +++ b/utils.moon @@ -34,6 +34,7 @@ utils = { keys: (t)-> [k for k in pairs(t)] values: (t)-> [v for _,v in pairs(t)] + set: (list)-> {i,true for i in *list} sum: (t)-> with tot = 0