diff --git a/core.moon b/core.moon index f1847c3..4f832f8 100755 --- a/core.moon +++ b/core.moon @@ -1,7 +1,86 @@ #!/usr/bin/env moon -nomic = require 'nomic' +Nomic = require 'nomic' utils = require 'utils' -g = nomic() + + +class PermissionNomic extends Nomic + new: (...)=> + super(...) + @callstack = {} + + call: (fn_name,...)=> + fn_info = @defs[fn_name] + if fn_info == nil + error "Attempt to call undefined function: #{fn_name}" + unless self\check_permission(fn_name) + error "You do not have the authority to call: #{fn_name}" + table.insert @callstack, fn_name + {:fn, :arg_names} = fn_info + args = {name, select(i,...) for i,name in ipairs(arg_names)} + if @debug + print "Calling #{fn_name} with args: #{utils.repr(args)}" + ret = fn(self, args) + table.remove @callstack + return ret + + check_permission: (fn_name)=> + fn_info = @defs[fn_name] + if fn_info == nil + error "Undefined function: #{fn_name}" + if fn_info.whitelist == nil then return true + for caller in *@callstack + if fn_info.whitelist[caller] + return true + return false + + +g = PermissionNomic() + +g\def {"restrict %fn to %whitelist"}, (vars)=> + fns = if type(vars.fn) == 'string' then {vars.fn} else vars.fn + whitelist = if type(vars.whitelist) == 'string' then {vars.whitelist} else vars.whitelist + whitelist = {w,true for w in *whitelist} + for fn in *fns + fn_info = @defs[fn] + if fn_info == nil + print "Undefined function: #{fn}" + continue + unless self\check_permission(fn) + print "You do not have permission to restrict function: #{fn}" + continue + @defs[fn] = whitelist + +g\def {"allow %whitelist to %fn"}, (vars)=> + fns = if type(vars.fn) == 'string' then {vars.fn} else vars.fn + whitelist = if type(vars.whitelist) == 'string' then {vars.whitelist} else vars.whitelist + for fn in *fns + fn_info = @defs[fn] + if fn_info == nil + print "Undefined function: #{fn}" + continue + if fn_info.whitelist == nil + print "Function is already allowed by everyone: #{fn}" + continue + unless self\check_permission(fn) + print "You do not have permission to grant permissions for function: #{fn}" + continue + for w in *whitelist do fn_info.whitelist[w] = true + +g\def {"forbid %blacklist to %fn"}, (vars)=> + fns = if type(vars.fn) == 'string' then {vars.fn} else vars.fn + blacklist = if type(vars.blacklist) == 'string' then {vars.blacklist} else vars.blacklist + for fn in *fns + fn_info = @defs[fn] + if fn_info == nil + print "Undefined function: #{fn}" + continue + if fn_info.whitelist == nil + print "Cannot remove items from a whitelist when there is no whitelist on function: #{fn}" + continue + unless self\check_permission(fn) + print "You do not have permission to restrict function: #{fn}" + continue + for b in *blacklist do fn_info.whitelist[b] = nil g\def {"say %x", "print %x"}, (vars)=> @@ -41,8 +120,10 @@ infix = (ops)-> alias = op if type(op) == 'table' {alias,op} = op - g\defmacro "%x #{op} %y", (vars,helpers,ftype)=> - helpers.lua("(#{helpers.var('x')} #{op} #{helpers.var('y')})") + g\defmacro "%x #{alias} %y", (vars,helpers,ftype)=> + value = "(#{helpers.var('x')} #{op} #{helpers.var('y')})" + if ftype == "Expression" then helpers.lua(value) + else helpers.lua("ret = #{value}") unary = (ops)-> for op in *ops g\defmacro "#{op} %x", (vars,helpers,ftype)=> @@ -51,7 +132,7 @@ unary = (ops)-> singleton {"true","yes"}, "true" singleton {"false","no"}, "false" singleton {"nil","null","nop","pass"}, "nil" -infix{"+","-","*","/","==",{"!=","~="},"<","<=",">",">=","^","and","or"} +infix{"+","-","*","/","==",{"!=","~="},"<","<=",">",">=","^","and","or",{"mod","%"}} unary{"-","#","not"} g\def [[%x == %y]], (args)=> utils.equivalent(args.x, args.y) @@ -176,6 +257,45 @@ g\defmacro "for %varname in %iterable %body", (vars,helpers,ftype)=> .lua "end" return nil +g\defmacro "for %varname = %start to %stop %body", (vars,helpers,ftype)=> + with helpers + switch ftype + when "Expression" + .lua "(function(game, vars)" + .indented -> + .lua "local comprehension, vars = {}, setmetatable({}, {__index=vars})" + .lua "for value=(#{.ded(.transform(vars.start))}),(#{.ded(.transform(vars.stop))}) 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 value=(#{.ded(.transform(vars.start))}),(#{.ded(.transform(vars.stop))}) 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\simplemacro "if %condition %body", [[ if %condition %body ..else: pass @@ -205,4 +325,5 @@ g\defmacro [[lua %lua_code]], (vars,helpers,ftype)=> g\defmacro [[macro %spec %body]], (vars,helpers,ftype)=> self\simplemacro vars.spec.value.value, vars.body.value.value.src + return g diff --git a/game2.moon b/game2.moon index d26d368..3d5f1ae 100755 --- a/game2.moon +++ b/game2.moon @@ -6,157 +6,6 @@ g = Game(require'core') print("===========================================================================================") -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" -]] - -g\test[[ -say (subexpressions work) -=== -Call [say %]: - Call [subexpressions work]! -]] - -g\test[[ -say ["lists", "work"] -=== -Call [say %]: - List: - "lists" - "work" -]] - -g\test[[ -say [] -=== -Call [say %]: - -]] - -g\test[[ -say [..] - 1, 2 - 3 -=== -Call [say %]: - List: - 1 - 2 - 3 -]] - -g\test[[ -say both [..] - 1,2 -..and [..] - 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 [..] @@ -257,3 +106,20 @@ g\run[[ smet "fnord" = 23 say %fnord ]] + +g\run[[ + +rule "fizz buzz": + for "i" = 1 to 100: + if ((%i mod 15) == 0): + say "fizzbuzz" + ..else: if ((%i mod 3) == 0): + say "fizz" + ..else: if ((%i mod 5) == 0): + say "buzz" + ..else: + say %i + +fizz buzz + +]] diff --git a/nomic.moon b/nomic.moon index 4a7ecf3..3c14f7d 100644 --- a/nomic.moon +++ b/nomic.moon @@ -4,8 +4,10 @@ utils = require 'utils' moon = require 'moon' lpeg.setmaxstack 10000 -- whoa +{:P,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg -linebreak = lpeg.P("\r")^-1 * lpeg.P("\n") +wordchar = P(1)-S(' \t\n\r%:;,.{}[]()"') +spaces = S(" \t")^1 get_line_indentation = (line)-> indent_amounts = {[" "]:1, ["\t"]:4} @@ -14,158 +16,27 @@ get_line_indentation = (line)-> 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.."\n") - 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) - / (expression) - }) -> 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)? -]=] - -wordchar = lpeg.P(1)-lpeg.S(' \t\n\r%:;,.{}[]()"') -defs = - eol: #(linebreak) + (lpeg.P("")-lpeg.P(1)) - ws: lpeg.S(" \t")^1 - wordchar: wordchar - 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 = (src, value, ...)-> - token = {type: key, src:src, value: value} - return token - t[key] = fn - return fn -}) -lingo = re.compile lingo, defs - class Game new:(parent)=> @defs = setmetatable({}, {__index:parent and parent.defs}) - @macros = setmetatable({}, {__index: parent and parent.macros}) @debug = false call: (fn_name,...)=> - if @defs[fn_name] == nil + fn_info = @defs[fn_name] + if fn_info == nil error "Attempt to call undefined function: #{fn_name}" - {fn, arg_names} = @defs[fn_name] + {:fn, :arg_names} = fn_info + args = {name, select(i,...) for i,name in ipairs(arg_names)} 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)} = #{utils.repr(select(i,...), true)}") - ret = fn(self, args) - if @debug - print "returned #{utils.repr(ret,true)}" - return ret + print "Calling #{fn_name} with args: #{utils.repr(args)}" + return fn(self, args) def: (spec, fn)=> invocations,arg_names = self\get_invocations spec + fn_info = {:fn, :arg_names, :invocations, is_macro:false} for invocation in *invocations - @defs[invocation] = {fn, arg_names} + @defs[invocation] = fn_info get_invocations:(text)=> if type(text) == 'string' then text = {text} @@ -190,21 +61,55 @@ class Game defmacro: (spec, fn)=> assert fn, "No function supplied" invocations,arg_names = self\get_invocations spec + fn_info = {:fn, :arg_names, :invocations, is_macro:true} for invocation in *invocations - @macros[invocation] = {fn, arg_names} + @defs[invocation] = fn_info simplemacro: (spec, replacement)=> + spec = spec\gsub("\r", "") + replacement = replacement\gsub("\r", "") + indent_stack = {0} + push = (n)-> table.insert indent_stack, n + pop = ()-> table.remove indent_stack + check_indent = (subject,end_pos,spaces)-> + num_spaces = get_line_indentation(spaces) + if num_spaces <= indent_stack[#indent_stack] then return nil + push num_spaces + return end_pos + check_dedent = (subject,end_pos,spaces)-> + num_spaces = get_line_indentation(spaces) + if num_spaces >= indent_stack[#indent_stack] then return nil + pop! + return end_pos + check_nodent = (subject,end_pos,spaces)-> + num_spaces = get_line_indentation(spaces) + if num_spaces != indent_stack[#indent_stack] then return nil + return end_pos + + nl = P("\n") + blank_line = spaces^-1 * nl + defs = + eol: #(nl) + (P("")-P(1)) + ws: S(" \t")^1 + :wordchar + :replacer + :nl, :spaces + word_boundary: S(" \t")^1 + B(P("..")) + B(S("\";)]")) + #S("\":([") + #P("..") + indent: #(nl * blank_line^0 * Cmt(spaces^-1, check_indent)) + dedent: #(nl * blank_line^0 * Cmt(spaces^-1, check_dedent)) + new_line: nl * blank_line^0 * Cmt(spaces^-1, check_nodent) + replace_grammar = [[ - stuff <- {~ (var / string / .)+ ~} + stuff <- {~ (var / longstring / string / .)+ ~} var <- ("%" {%wordchar+}) -> replacer string <- '"' (("\" .) / [^"])* '"' + longstring <- ('"..' %indent %nl {(!%dedent .)*} %new_line '.."') ]] - replacement = add_indent_tokens replacement fn = (vars, helpers, ftype)=> replacer = (varname)-> ret = vars[varname].src return ret - replacement_grammar = re.compile(replace_grammar, {:wordchar, :replacer}) + replacement_grammar = re.compile(replace_grammar, defs) replaced = replacement_grammar\match(replacement) tree = lingo\match replaced if tree.value.errors and #tree.value.errors.value > 0 @@ -242,10 +147,98 @@ class Game parse: (str)=> if @debug print("PARSING:\n#{str}") - indentified = add_indent_tokens str - if @debug - print("\nINDENTIFIED:\n#{indentified}") - tree = lingo\match indentified + lingo = [=[ + file <- ({ {| %new_line? {:body: block :} %new_line? ({:errors: errors :})? |} }) -> File + errors <- ({ {.+} }) -> Errors + block <- ({ {| statement (%new_line statement)* |} }) -> Block + statement <- ({ (functioncall / expression) }) -> Statement + one_liner <- ({ {| + (({ + (({ {| + (expression (%word_boundary fn_bit)+) / (word (%word_boundary fn_bit)*) + |} }) -> FunctionCall) + / (expression) + }) -> Statement) + |} }) -> Block + + functioncall <- ({ {| (expression %word_boundary fn_bits) / (word (%word_boundary fn_bits)?) |} }) -> FunctionCall + fn_bit <- (expression / word) + fn_bits <- + ((".." %ws? (%indent %new_line indented_fn_bits %dedent) (%new_line ".." %ws? fn_bits)?) + / (%new_line ".." fn_bit fn_bits) + / (fn_bit (%word_boundary fn_bits)?)) + indented_fn_bits <- + fn_bit ((%new_line / %word_boundary) indented_fn_bits)? + + thunk <- + ({ ":" %ws? + ((%indent %new_line block %dedent (%new_line "..")?) + / (one_liner (%ws? (%new_line? ".."))?)) }) -> Thunk + + word <- ({ !number {%wordchar+} }) -> Word + expression <- ({ (longstring / string / number / variable / list / thunk / subexpression) }) -> Expression + + string <- ({ (!('"..' %ws? %nl)) '"' {(("\" .) / [^"])*} '"' }) -> String + longstring <- ({ '"..' %ws? %indent %nl {(!%dedent .)* (%nl %ws? %eol)*} %new_line '.."' }) -> Longstring + number <- ({ {'-'? [0-9]+ ("." [0-9]+)?} }) -> Number + variable <- ({ ("%" {%wordchar+}) }) -> Var + + subexpression <- + ("(" %ws? (functioncall / expression) %ws? ")") + / ("(..)" %ws? %indent %new_line ((({ {| indented_fn_bits |} }) -> FunctionCall) / expression) %dedent (%new_line "..")?) + + list <- ({ {| + ("[..]" %ws? %indent %new_line indented_list ","? %dedent (%new_line "..")?) + / ("[" %ws? (list_items ","?)? %ws?"]") + |} }) -> List + list_items <- (expression (list_sep list_items)?) + list_sep <- %ws? "," %ws? + indented_list <- + expression (((list_sep %new_line?) / %new_line) indented_list)? + ]=] + + str = str\gsub("\r", "") + indent_stack = {0} + push = (n)-> table.insert indent_stack, n + pop = ()-> table.remove indent_stack + check_indent = (subject,end_pos,spaces)-> + num_spaces = get_line_indentation(spaces) + if num_spaces <= indent_stack[#indent_stack] then return nil + push num_spaces + return end_pos + check_dedent = (subject,end_pos,spaces)-> + num_spaces = get_line_indentation(spaces) + if num_spaces >= indent_stack[#indent_stack] then return nil + pop! + return end_pos + check_nodent = (subject,end_pos,spaces)-> + num_spaces = get_line_indentation(spaces) + if num_spaces != indent_stack[#indent_stack] then return nil + return end_pos + + nl = P("\n") + blank_line = spaces^-1 * nl + defs = + eol: #(nl) + (P("")-P(1)) + ws: S(" \t")^1 + :wordchar + :nl, :spaces + word_boundary: S(" \t")^1 + B(P("..")) + B(S("\";)]")) + #S("\":([") + #P("..") + indent: #(nl * blank_line^0 * Cmt(spaces^-1, check_indent)) + dedent: #(nl * blank_line^0 * Cmt(spaces^-1, check_dedent)) + new_line: nl * blank_line^0 * Cmt(spaces^-1, check_nodent) + + setmetatable(defs, { + __index: (t,key)-> + --print("WORKING for #{key}") + fn = (src, value, ...)-> + token = {type: key, src:src, value: value} + return token + t[key] = fn + return fn + }) + lingo = re.compile lingo, defs + tree = lingo\match(str\gsub("\r","").."\n") if @debug print("\nPARSE TREE:") self\print_tree(tree) @@ -326,7 +319,8 @@ class Game for token in *tree.value.value table.insert name_bits, if token.type == "Word" then token.value else "%" name = table.concat(name_bits, " ") - if @macros[name] + if @defs[name] and @defs[name].is_macro + -- This case here is to prevent "ret =" from getting prepended when the macro might not want it lua transform(tree.value) ret = table.concat ret_lines, "\n" return ret @@ -340,8 +334,8 @@ class Game 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] + if @defs[name] and @defs[name].is_macro + {:fn, :arg_names} = @defs[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)} @@ -359,6 +353,15 @@ class Game unescaped = tree.value\gsub("\\(.)", ((c)-> escapes[c] or c)) lua utils.repr(unescaped, true) + when "Longstring" + first_nonblank_line = tree.value\match("[^\n]+") + indent = first_nonblank_line\match("[ \t]*") + result = {} + for line in (tree.value.."\n")\gmatch("(.-)\n") + line = line\gsub("^"..indent, "", 1) + table.insert result, line + lua utils.repr(table.concat(result, "\n"), true) + when "Number" lua tree.value @@ -417,6 +420,11 @@ class Game self\_yield_tree(a, indent_level+1) when "String" + -- TODO: Better implement + coroutine.yield(ind(utils.repr(tree.value, true))) + + when "Longstring" + -- TODO: Better implement coroutine.yield(ind(utils.repr(tree.value, true))) when "Number" @@ -453,20 +461,24 @@ class Game return code test: (src, expected)=> - if expected == nil - start,stop = src\find"===" + i = 1 + while i != nil + start,stop = src\find("\n\n", i) + + test = src\sub(i,start) + i = stop + start,stop = test\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}" + error("WHERE'S THE ===? in:\n#{test}") + test_src, expected = test\sub(1,start-1), test\sub(stop+1,-1) + expected = expected\match'[\n]*(.*[^\n])' + tree = self\parse(test_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#{test_src}\nExpected:\n#{expected}\n\nGot:\n#{got}" return Game diff --git a/utils.moon b/utils.moon index 3004c58..ee97903 100644 --- a/utils.moon +++ b/utils.moon @@ -17,9 +17,9 @@ utils = { when 'string' if not add_quotes x - elseif not x\find[["]] + elseif not x\find[["]] and not x\find"\n" "\"#{x}\"" - elseif not x\find[[']] + elseif not x\find[[']] and not x\find"\n" "\'#{x}\'" else for i=0,math.huge