From 90c56d31352a0eeccd382ef5921baf3af4971040 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 26 Jan 2018 20:20:12 -0800 Subject: [PATCH] Added a ton of tests for virtually all the functionality. Helped me find and fix a lot of latent problems. --- compile_lib.sh | 5 + lib/collections.nom | 168 +++++++++++++++++---------------- lib/control_flow.nom | 99 ++++++++++---------- lib/math.nom | 46 +++++---- lib/metaprogramming.nom | 30 +++--- lib/operators.nom | 69 +++++++++----- lib/text.nom | 9 +- nomsu.lua | 3 + nomsu.moon | 6 +- tests/all.nom | 28 ++++++ tests/collections.nom | 43 +++++++++ tests/control_flow.nom | 191 ++++++++++++++++++++++++++++++++++++++ tests/math.nom | 17 ++++ tests/metaprogramming.nom | 55 +++++++++++ tests/operators.nom | 76 +++++++++++++++ tests/text.nom | 13 +++ utils.lua | 4 +- 17 files changed, 675 insertions(+), 187 deletions(-) create mode 100644 tests/all.nom create mode 100644 tests/collections.nom create mode 100644 tests/control_flow.nom create mode 100644 tests/math.nom create mode 100644 tests/metaprogramming.nom create mode 100644 tests/operators.nom create mode 100644 tests/text.nom diff --git a/compile_lib.sh b/compile_lib.sh index c8d573e..8cdfcdb 100755 --- a/compile_lib.sh +++ b/compile_lib.sh @@ -24,3 +24,8 @@ for file in $(cat lib/core.nom | lua -e "for filename in io.read('*a'):gmatch('u ./nomsu.moon -c $file echo "done." done +for file in $(cat tests/all.nom | lua -e "for filename in io.read('*a'):gmatch('run file \"([^\"]*)\"') do print(filename) end") ; do + printf "Compiling $file ..." + ./nomsu.moon -c $file + echo "done." +done diff --git a/lib/collections.nom b/lib/collections.nom index c31bbee..ac1b019 100644 --- a/lib/collections.nom +++ b/lib/collections.nom @@ -9,68 +9,55 @@ use "lib/operators.nom" # List/dict functions: # Indexing -compile [..] - %index st to last in %list, %index nd to last in %list, %index rd to last in %list - %index th to last in %list -..to {expr:"utils.nth_to_last(\(%list as lua expr), \(%index as lua expr))"} +immediately + compile [..] + %index st to last in %list, %index nd to last in %list, %index rd to last in %list + %index th to last in %list + ..to {expr:"utils.nth_to_last(\(%list as lua expr), \(%index as lua expr))"} -parse [first in %list, first %list] as: 1 st in %list -parse [last in %list, last %list] as: 1 st to last in %list +immediately + parse [first in %list, first %list] as: 1 st in %list + parse [last in %list, last %list] as: 1 st to last in %list # Membership testing -action [%item is in %list, %list contains %item, %list has %item] - for %key = %value in %list - if (%key is %item): return (yes) - return (no) +immediately + action [%item is in %list, %list contains %item, %list has %item] + for %key = %value in %list + if (%key is %item): return (yes) + return (no) -action [..] - %item isn't in %list, %item is not in %list - %list doesn't contain %item, %list does not contain %item - %list doesn't have %item, %list does not have %item -.. - for %key = %value in %list - if (%key is %item): return (no) - return (yes) + action [..] + %item isn't in %list, %item is not in %list + %list doesn't contain %item, %list does not contain %item + %list doesn't have %item, %list does not have %item + .. + for %key = %value in %list + if (%key is %item): return (no) + return (yes) -# Note: it's important to have the space after "[" to prevent confusion if %index is a string -compile [%list has key %index, %list has index %index] to {..} - expr: ".." - ((\(%list as lua expr))[ \(%index as lua expr)] ~= nil) +immediately + # Note: it's important to have the space after "[" to prevent confusion if %index is a string + compile [%list has key %index, %list has index %index] to {..} + expr: ".." + ((\(%list as lua expr))[ \(%index as lua expr)] ~= nil) -# Note: it's important to have the space after "[" to prevent confusion if %index is a string -compile [..] - %list doesn't have key %index, %list does not have key %index - %list doesn't have index %index, %list does not have index %index -..to {expr:"((\(%list as lua expr))[ \(%index as lua expr)] ~= nil)"} + # Note: it's important to have the space after "[" to prevent confusion if %index is a string + compile [..] + %list doesn't have key %index, %list does not have key %index + %list doesn't have index %index, %list does not have index %index + ..to {expr:"((\(%list as lua expr))[ \(%index as lua expr)] == nil)"} -compile [length of %list, size of %list, size %list, number of %list, len %list] to - {expr:"utils.size(\(%list as lua expr))"} + compile [length of %list, size of %list, size %list, number of %list, len %list] to + {expr:"utils.size(\(%list as lua expr))"} -compile [append %item to %list, add %item to %list] to - {statements:"table.insert(\(%list as lua expr), \(%item as lua expr))"} + compile [append %item to %list, add %item to %list] to + {statements:"table.insert(\(%list as lua expr), \(%item as lua expr))"} -compile [pop from %list, remove last from %list] to - {statements:"table.remove(\(%list as lua expr))"} + compile [pop from %list, remove last from %list] to + {statements:"table.remove(\(%list as lua expr))"} -compile [remove index %index from %list] to - {statements:"table.remove(\(%list as lua expr), \(%index as lua expr))"} - - -action [flatten %lists] - %flat <- [] - for %list in %lists - for %item in %list - add %item to %flat - return %flat - -action [entries in %dict] - [{key:%k, value:%v} for %k=%v in %dict] - -action [keys in %dict] - [%k for %k=%v in %dict] - -action [values in %dict] - [%v for %k=%v in %dict] + compile [remove index %index from %list] to + {statements:"table.remove(\(%list as lua expr), \(%index as lua expr))"} # List Comprehension immediately @@ -98,7 +85,7 @@ immediately (function() local comprehension = {}; for \(%key as lua expr), \(%value as lua expr) in pairs(\(%iterable as lua expr)) do - comprehension[i] = \(%expression as lua expr) + table.insert(comprehension, \(%expression as lua expr)); end return comprehension; end)() @@ -136,38 +123,59 @@ immediately return comprehension; end)() +immediately + action [%lists flattened] + %flat <- [] + for %list in %lists + for %item in %list + add %item to %flat + return %flat + + parse [entries in %dict] as: {key:%k, value:%v} for %k = %v in %dict + parse [keys in %dict] as: %k for %k = %v in %dict + parse [values in %dict] as: %v for %k = %v in %dict + # Sorting: -compile [sort %items] to {statements:"table.sort(\(%items as lua expr))"} -compile [sort %items by %key_expr] to {..} - statements: ".." - utils.sort(\(%items as lua expr), function(\(\% as lua expr)) - return \(%key_expr as lua expr); - end) +immediately + compile [sort %items] to {statements:"table.sort(\(%items as lua expr))"} + compile [sort %items by %key_expr] to {..} + statements: ".." + utils.sort(\(%items as lua expr), function(\(\% as lua expr)) + return \(%key_expr as lua expr); + end) -action [%items sorted] - %copy <- (% for all %items) - sort %copy - return %copy -action [%items sorted by %key] - %copy <- (% for all %items) - sort %copy by %key - return %copy +immediately + action [%items sorted, sorted %items] + %copy <- (% for all %items) + sort %copy + return %copy + action [%items sorted by %key] + %copy <- (% for all %items) + sort %copy by %key + return %copy -action [unique %items] - [%k for %k=%v in (%=(yes) for all %items)] + action [unique %items] + %unique <- [] + %seen <- {} + for all %items + unless: % in %seen + add % to %unique + (% in %seen) <- (yes) + return %unique -# Metatable stuff -compile [set %dict's metatable to %metatable] to {..} - statements: "setmetatable(\(%dict as lua expr), \(%metatable as lua expr));" +immediately + # Metatable stuff + compile [set %dict's metatable to %metatable] to {..} + statements: "setmetatable(\(%dict as lua expr), \(%metatable as lua expr));" -compile [new counter] to {expr:"setmetatable({}, {__index=function() return 0; end})"} + compile [new counter] to {expr:"setmetatable({}, {__index=function() return 0; end})"} -compile [new default dict] to {..} - expr: ".." - setmetatable({}, {__index=function(self, key) - t = {}; - self[key] = t; - return t; - end}) + compile [new default dict] to {..} + expr: ".." + setmetatable({}, {__index=function(self, key) + t = {}; + self[key] = t; + return t; + end}) # TODO: maybe make a generator/coroutine? diff --git a/lib/control_flow.nom b/lib/control_flow.nom index a6f460a..cfaa7ad 100644 --- a/lib/control_flow.nom +++ b/lib/control_flow.nom @@ -70,9 +70,9 @@ immediately # GOTOs immediately compile [=== %label ===, --- %label ---, *** %label ***] to {..} - statements:"::label_\(nomsu "var_to_lua_identifier" [%label])::;" + statements:"::label_\(%label as lua identifier)::;" compile [go to %label] to {..} - statements:"goto label_\(nomsu "var_to_lua_identifier" [%label]);" + statements:"goto label_\(%label as lua identifier);" # Basic loop control immediately @@ -98,14 +98,14 @@ immediately # While loops immediately - compile [do next repetition] to {statements:"goto continue_repeat;"} + compile [do next repeat] to {statements:"goto continue_repeat;"} compile [stop repeating] to {statements:"goto stop_repeat;"} compile [repeat while %condition %body] to %body_lua <- (%body as lua) %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");") if %body has subtree % where - ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next repetition") - ..: <- %body_statments + "\n::continue_repeat::;" + ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next repeat") + ..: %body_statments +<- "\n::continue_repeat::;" %code <- ".." while \(%condition as lua expr) do \%body_statements @@ -128,8 +128,8 @@ immediately %body_lua <- (%body as lua) %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");") if %body has subtree % where - ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next repetition") - ..: <- %body_statements + "\n::continue_repeat::;" + ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next repeat") + ..: %body_statements +<- "\n::continue_repeat::;" %code <- ".." for i=1,\(%n as lua expr) do \%body_statements @@ -147,9 +147,9 @@ immediately # For loop control flow: immediately compile [stop %var] to {..} - statements:"goto stop_\(nomsu "var_to_lua_identifier" [%var]);" + statements:"goto stop_\(%var as lua identifier);" compile [do next %var] to {..} - statements:"goto continue_\(nomsu "var_to_lua_identifier" [%var]);" + statements:"goto continue_\(%var as lua identifier);" # Numeric range for loops immediately @@ -163,7 +163,7 @@ immediately ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next %") and ((3rd in (%'s "value"))'s "value") is (%var's "value") - ..: <- %body_statements + "\n::continue_\(nomsu "var_to_lua_identifier" [%var])::;" + ..: %body_statements +<- "\n::continue_\(%var as lua identifier)::;" # This uses Lua's approach of only allowing loop-scoped variables in a loop assume ((%var's "type") is "Var") or barf "Loop expected variable, not: \(%var's source code)" @@ -180,7 +180,7 @@ immediately %code <- ".." do -- scope for stopping for-loop \%code - ::stop_\(nomsu "var_to_lua_identifier" [%var])::; + ::stop_\(%var as lua identifier)::; end -- end of scope for stopping for-loop return {statements:%code, locals:%body_lua's "locals"} @@ -202,7 +202,7 @@ immediately ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next %") and ((3rd in (%'s "value"))'s "value") is (%var's "value") - ..: <- %body_statements + "\n::continue_\(nomsu "var_to_lua_identifier" [%var])::;" + ..: %body_statements +<- "\n::continue_\(%var as lua identifier)::;" # This uses Lua's approach of only allowing loop-scoped variables in a loop assume ((%var's "type") is "Var") or barf "Loop expected variable, not: \(%var's source code)" %code <- ".." @@ -217,7 +217,7 @@ immediately %code <- ".." do -- scope for stopping for-loop \%code - ::stop_\(nomsu "var_to_lua_identifier" [%var])::; + ::stop_\(%var as lua identifier)::; end -- end of scope for stopping for-loop return {statements:%code, locals:%body_lua's "locals"} @@ -232,13 +232,13 @@ immediately ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next %") and ((3rd in (%'s "value"))'s "value") is (%key's "value") - ..: <- %body_statements + "\n::continue_\(nomsu "var_to_lua_identifier" [%key])::;" + ..: %body_statements +<- "\n::continue_\(%key as lua identifier)::;" if %body has subtree % where ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next %") and ((3rd in (%'s "value"))'s "value") is (%value's "value") - ..: <- %body_statements + "\n::continue_\(nomsu "var_to_lua_identifier" [%value])::;" + ..: %body_statements +<- "\n::continue_\(%value as lua identifier)::;" # This uses Lua's approach of only allowing loop-scoped variables in a loop assume ((%key's "type") is "Var") or barf "Loop expected variable, not: \(%key's source code)" @@ -253,13 +253,13 @@ immediately ((%'s "type") = "FunctionCall") and ((%'s "stub") is "stop %") and ((2nd in (%'s "value"))'s "value") is (%key's "value") - ..: <- %stop_labels + "\n::stop_\(nomsu "var_to_lua_identifier" [%key])::;" + ..: %stop_labels +<- "\n::stop_\(%key as lua identifier)::;" if %body has subtree % where ((%'s "type") = "FunctionCall") and ((%'s "stub") is "stop %") and ((2nd in (%'s "value"))'s "value") is (%value's "value") - ..: <- %stop_labels + "\n::stop_\(nomsu "var_to_lua_identifier" [%value])::;" + ..: %stop_labels +<- "\n::stop_\(%value as lua identifier)::;" if: %stop_labels is not "" %code <- ".." @@ -290,7 +290,7 @@ immediately assume %condition or barf ".." Invalid format for 'when' statement. Lines must begin with '*' and have a condition or the word "else" if: %action is (nil) - lua> "table.insert(\%fallthroughs, \%condition);" + lua> "table.insert(\%fallthroughs, \(%condition as lua expr));" do next %func_call %action <- (%action as lua) %action_statements <- ((%action's "statements") or "\(%action's "expr");") @@ -298,19 +298,19 @@ immediately lua> "table.insert(\%locals, \%local);" if: =lua "\%condition.type == 'Word' and \%condition.value == 'else'" - <- %code + ".." + assume (not %is_first) or barf "'else' clause cannot be first in 'when % = ?' block" + %code +<- ".." else \%action_statements %seen_else <- (yes) ..else assume (not %seen_else) or barf "'else' clause needs to be last in 'when' block" - %condition <- (%condition as lua expr) - for all %fallthroughs - <- %condition + " or \(% as lua)" - <- %code + ".." + lua> "table.insert(\%fallthroughs, \(%condition as lua expr));" + %condition_code <- (%fallthroughs joined with " or ") + %code +<- ".." - \("if" if %is_first else "elseif") \%condition then + \("if" if %is_first else "elseif") \%condition_code then \%action_statements %fallthroughs <- [] @@ -318,7 +318,7 @@ immediately assume (%fallthroughs = []) or barf "Unfinished fallthrough conditions in 'when' block" if: %code is not "" - <- %code + "\nend" + %code +<- "\nend" lua> "utils.deduplicate(\%locals);" return {statements:%code, locals:%locals} @@ -340,7 +340,7 @@ immediately assume %condition or barf ".." Invalid format for 'when' statement. Lines must begin with '*' and have a condition or the word "else" if: %action is (nil) - lua> "table.insert(\%fallthroughs, \%condition)" + lua> "table.insert(\%fallthroughs, \(%condition as lua expr))" do next %func_call %action <- (%action as lua) @@ -350,7 +350,7 @@ immediately if: =lua "\%condition.type == 'Word' and \%condition.value == 'else'" assume (not %is_first) or barf "'else' clause cannot be first in 'when % = ?' block" - <- %code + ".." + %code +<- ".." else \%action_statements @@ -358,17 +358,14 @@ immediately %seen_else <- (yes) ..else assume (not %seen_else) or barf "'else' clause needs to be last in 'when % = ?' block" - %clause <- "" - if: ((%condition's "type") is "Text") or ((%condition's "type") is "Number") - %clause <- "branch_value == (\(%condition as lua expr))" - ..else - %clause <- "utils.equivalent(branch_value, \(%condition as lua expr))" - for all %fallthroughs + lua> "table.insert(\%fallthroughs, \(%condition as lua expr));" + for %i = % in %fallthroughs if: ((%'s "type") is "Text") or ((%'s "type") is "Number") - <- %clause + " or branch_value == (\(%condition as lua expr))" + (%i'th in %fallthroughs) <- "branch_value == \%" ..else - <- %clause + " or utils.equivalent(branch_value, \(%condition as lua expr))" - <- %code + ".." + (%i'th in %fallthroughs) <- "utils.equivalent(branch_value, \%)" + %clause <- (%fallthroughs joined with " or ") + %code +<- ".." \("if" if %is_first else "elseif") \%clause then \%action_statements @@ -379,7 +376,7 @@ immediately assume (%fallthroughs = []) or barf "Unfinished fallthrough conditions in 'when' block" assume (%code is not "") or barf "No body for 'when % = ?' block!" unless %seen_else - <- %code + "\nend" + %code +<- "\nend" %code <- ".." do --when % = ? local branch_value = \(%branch_value as lua expr);\ @@ -391,14 +388,16 @@ immediately # Try/except immediately compile [..] - try %action and if it succeeds %success or if it fails %fallback - try %action and if it fails %fallback or if it succeeds %success + try %action and if it succeeds %success or if it barfs %fallback + try %action and if it barfs %fallback or if it succeeds %success ..to %locals <- [] - %action <- (%action as lua) + %action_lua <- (%action as lua) + %success_lua <- (%success as lua) + %fallback_lua <- (%fallback as lua) %fallback <- (%fallback as lua) - for %sub_locals in [%action's "locals", %success's "locals", %fallback's "locals"] - for %local in %sub_locals + for %block in [%action_lua, %success_lua, %fallback_lua] + for %local in ((%block's "locals") or []) lua> "table.insert(\%locals, \%local);" lua> "utils.deduplicate(\%locals);" return {..} @@ -407,26 +406,26 @@ immediately do local fell_through = false; local ok, ret = pcall(function() - \((%action's "statements") or "\(%action's "expr");") + \((%action_lua's "statements") or "\(%action_lua's "expr");") fell_through = true; end); if ok then - \((%success's "statements") or "\(%success's "expr");") + \((%success_lua's "statements") or "\(%success_lua's "expr");") end if not ok then - \((%fallback's "statements") or "\(%fallback's "expr");") + \((%fallback_lua's "statements") or "\(%fallback_lua's "expr");") elseif not fell_through then return ret; end end parse [try %action] as try %action and if it succeeds: do nothing - ..or if it fails: do nothing - parse [try %action and if it fails %fallback] as + ..or if it barfs: do nothing + parse [try %action and if it barfs %fallback] as try %action and if it succeeds: do nothing - ..or if it fails %fallback + ..or if it barfs %fallback parse [try %action and if it succeeds %success] as - try %action and if it succeeds %success or if it fails: do nothing + try %action and if it succeeds %success or if it barfs: do nothing # Do/finally: immediately @@ -441,7 +440,7 @@ immediately compile [do %action then always %final_action] to %action <- (%action as lua) - %final_action <- (%action as lua) + %final_action <- (%final_action as lua) %locals <- [] for %sub_locals in [%action's "locals", %final_action's "locals"] for %local in %sub_locals diff --git a/lib/math.nom b/lib/math.nom index ea1769a..13c6cc3 100644 --- a/lib/math.nom +++ b/lib/math.nom @@ -2,6 +2,8 @@ This file defines some common math literals and functions use "lib/metaprogramming.nom" +use "lib/text.nom" +use "lib/operators.nom" use "lib/control_flow.nom" # Literals: @@ -10,10 +12,10 @@ compile [not a number, NaN, nan] to {expr:"(0/0)"} compile [pi, Pi, PI] to {expr:"math.pi"} compile [tau, Tau, TAU] to {expr:"(2*math.pi)"} compile [golden ratio] to {expr:"((1+math.sqrt(5))/2)"} -compile [e] to {expr:"math.e"} +compile [e] to {expr:"math.exp(1)"} # Functions: -compile [% as number] to {expr:"tonumber(\(% as lua expr))"} +compile [% as a number] to {expr:"tonumber(\(% as lua expr))"} compile [absolute value %, | % |, abs %] to {expr:"math.abs(\(% as lua expr))"} compile [square root %, √%, sqrt %] to {expr:"math.sqrt(\(% as lua expr))"} compile [sine %, sin %] to {expr:"math.sin(\(% as lua expr))"} @@ -36,24 +38,32 @@ action [%n to the nearest %rounder] =lua "(\%rounder)*math.floor((\%n / \%rounder) + .5)" # Any/all/none -compile [all of %items, all %items] to {..} - expr: - "(\(joined ((% as lua expr) for all (%items' "value")) with " and "))" - ..if ((%items' "type") is "List") else "utils.all(\(%items as lua expr))" +compile [all of %items, all %items] to + unless: (%items' "type") is "List" + return {expr:"utils.all(\(%items as lua expr))"} + %clauses <- [] + for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" + return {expr:"(\(%clauses joined with " and "))"} parse [not all of %items, not all %items] as: not (all of %items) -compile [any of %items, any %items] to {..} - expr: - "(\(joined ((% as lua expr) for all (%items' "value")) with " or "))" - ..if ((%items' "type") is "List") else "utils.any(\(%items as lua expr))" +compile [any of %items, any %items] to + unless: (%items' "type") is "List" + return {expr:"utils.any(\(%items as lua expr))"} + %clauses <- [] + for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" + return {expr:"(\(%clauses joined with " or "))"} parse [none of %items, none %items] as: not (any of %items) -compile [sum of %items, sum %items] to {..} - expr: - "(\(joined ((% as lua expr) for all (%items' "value")) with " + "))" - ..if ((%items' "type") is "List") else "utils.sum(\(%items as lua expr))" -compile [product of %items, product %items] to {..} - expr: - "(\(joined ((% as lua expr) for all (%items' "value")) with " * "))" - ..if ((%items' "type") is "List") else "utils.product(\(%items as lua expr))" +compile [sum of %items, sum %items] to + unless: (%items' "type") is "List" + return {expr:"utils.sum(\(%items as lua expr))"} + %clauses <- [] + for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" + return {expr:"(\(%clauses joined with " + "))"} +compile [product of %items, product %items] to + unless: (%items' "type") is "List" + return {expr:"utils.product(\(%items as lua expr))"} + %clauses <- [] + for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" + return {expr:"(\(%clauses joined with " * "))"} action [avg of %items, average of %items] =lua "(utils.sum(\%items)/#\%items)" compile [min of %items, smallest of %items, lowest of %items] to {..} diff --git a/lib/metaprogramming.nom b/lib/metaprogramming.nom index f80c144..8f2bde9 100644 --- a/lib/metaprogramming.nom +++ b/lib/metaprogramming.nom @@ -136,10 +136,9 @@ immediately compile [repr %obj] to {expr:"repr(\(%obj as lua expr))"} compile [type of %obj] to {expr:"type(\(%obj as lua expr))"} -compile [nomsu] to {expr:"nomsu"} - -compile [nomsu's %key] to {expr:"nomsu[\(%key as lua expr)]"} -compile [nomsu %method %args] to {expr:"nomsu[\(%method as lua expr)](nomsu, unpack(\(%args as lua expr)))"} +immediately + compile [nomsu] to {expr:"nomsu"} + compile [%var as lua identifier] to {expr:"nomsu:var_to_lua_identifier(\(%var as lua expr))"} action [action %names metadata] =lua "ACTION_METADATA[ACTIONS[\%names]]" @@ -155,9 +154,10 @@ action [help %action] end # Compiler tools -parse [run %code] as: nomsu "run" [%code] -parse [enable debugging] as: lua> "nomsu.debug = true" -parse [disable debugging] as: lua> "nomsu.debug = false" +immediately + compile [run %code] to {expr: "nomsu:run(\(%code as lua expr), '\(!! code location !!)')"} + parse [enable debugging] as: lua> "nomsu.debug = true;" + parse [disable debugging] as: lua> "nomsu.debug = false;" immediately compile [say %message] to @@ -177,12 +177,20 @@ immediately # Error functions immediately - compile [barf!] to {statements:"error(nil, 0);"} + compile [barf] to {statements:"error(nil, 0);"} compile [barf %msg] to {statements:"error(\(%msg as lua expr), 0);"} - compile [assume %condition] to {..} - statements:"if not \(%condition as lua expr) then error('Assumption failed: '..\%condition:get_src(), 0); end" + compile [assume %condition] to + lua> "local \%assumption = 'Assumption failed: '..\%condition:get_src();" + return {..} + statements:".." + if not \(%condition as lua expr) then + error(\(repr %assumption), 0); + end compile [assume %condition or barf %msg] to {..} - statements:"if not \(%condition as lua expr) then error(\(%msg as lua expr), 0); end" + statements:".." + if not \(%condition as lua expr) then + error(\(%msg as lua expr), 0); + end # Literals immediately diff --git a/lib/operators.nom b/lib/operators.nom index 8be0ea0..670e7b7 100644 --- a/lib/operators.nom +++ b/lib/operators.nom @@ -36,6 +36,7 @@ immediately compile [%x > %y] to {expr:"(\(%x as lua expr) > \(%y as lua expr))"} compile [%x <= %y] to {expr:"(\(%x as lua expr) <= \(%y as lua expr))"} compile [%x >= %y] to {expr:"(\(%x as lua expr) >= \(%y as lua expr))"} + # TODO: optimize case of [%x,%y] = [1,2] compile [%a is %b, %a = %b, %a == %b] to lua> ".." local safe = {Text=true, Number=true}; @@ -68,6 +69,30 @@ immediately statements:"\(%var_lua's "expr") = \(%value_lua's "expr");" locals: =lua "(\%var.type == 'Var' and {\%var_lua.expr} or nil)" +immediately + # Simultaneous mutli-assignments like: x,y,z = 1,x,3; + compile [<- %assignments] to + %locals <- [] + %targets <- [] + %values <- [] + assume ((%assignments' "type") is "Dict") or barf ".." + Expected a Dict for the assignments part of '<- %' statement, not \(%assignments' source code) + lua> ".." + for i, item in ipairs(\%assignments.value) do + local target, value = item.dict_key, item.dict_value; + local target_lua = nomsu:tree_to_lua(target); + if not target_lua.expr then error("Invalid target for assignment: "..target:get_src()); end + local value_lua = nomsu:tree_to_lua(value); + if not value_lua.expr then error("Invalid value for assignment: "..value:get_src()); end + if target.type == "Var" then + table.insert(\%locals, target_lua.expr); + end + table.insert(\%targets, target_lua.expr); + table.insert(\%values, value_lua.expr); + end + utils.deduplicate(\%locals); + return {locals=\%locals, statements=(table.concat(\%targets, ", ").." = "..table.concat(\%values, ", ")..";")}; + immediately compile [export %var <- %value] to %var_lua <- (%var as lua) @@ -76,6 +101,21 @@ immediately assume (%value_lua's "expr") or barf "Invalid value for assignment: \(%value's source code)" return {statements:"\(%var_lua's "expr") = \(%value_lua's "expr");"} + compile [exporting %exported %body] to + %body_lua <- (%body as lua) + %leftover_locals <- (=lua "{unpack(\%body_lua.locals or {})}") + assume ((%exported's "type") = "List") or barf ".." + Expected a List for the export part of 'exporting' statement, not \(%exported's source code) + lua> ".." + for i, item in ipairs(\%exported.value) do + if item.type ~= "Var" then + error("'exporting' statement expects Vars, not: "..item:get_src()); + end + local var = nomsu:tree_to_lua(item).expr; + utils.remove_from_list(\%leftover_locals, var); + end + return {locals:%leftover_locals, statements:=lua "\%body_lua.statements or (\%body_lua.expr..';')"} + compile [with %assignments %body] to %body_lua <- (%body as lua) %locals <- [] @@ -118,21 +158,6 @@ immediately %s end]]):format(locals_code, declaration_code, \%body_lua.statements or (\%body_lua.expr..";"))}; - compile [exporting %exported %body] to - %body_lua <- (%body as lua) - %leftover_locals <- (=lua "{unpack(\%body_lua.locals or {})}") - assume ((%exported's "type") = "List") or barf ".." - Expected a List for the export part of 'exporting' statement, not \(%exported's source code) - lua> ".." - for i, item in ipairs(\%exported.value) do - if item.type ~= "Var" then - error("'exporting' statement expects Vars, not: "..item:get_src()); - end - local var = nomsu:tree_to_lua(item).expr; - utils.remove_from_list(leftover_locals, var); - end - return {locals:%leftover_locals, statements:=lua "\%body_lua.statements or (\%body_lua.expr..';')"} - immediately # Math Operators compile [%x + %y] to {expr:"(\(%x as lua expr) + \(%y as lua expr))"} @@ -174,11 +199,11 @@ immediately # Update operators immediately - parse [<- %var + %] as: %var <- (%var + %) - parse [<- %var - %] as: %var <- (%var - %) - parse [<- %var * %] as: %var <- (%var * %) - parse [<- %var / %] as: %var <- (%var / %) - parse [<- %var ^ %] as: %var <- (%var ^ %) - parse [<- %var and %] as: %var <- (%var and %) - parse [<- %var or %] as: %var <- (%var or %) + parse [%var + <- %, %var +<- %] as: %var <- (%var + %) + parse [%var - <- %, %var -<- %] as: %var <- (%var - %) + parse [%var * <- %, %var *<- %] as: %var <- (%var * %) + parse [%var / <- %, %var /<- %] as: %var <- (%var / %) + parse [%var ^ <- %, %var ^<- %] as: %var <- (%var ^ %) + parse [%var and <- %] as: %var <- (%var and %) + parse [%var or <- %] as: %var <- (%var or %) parse [wrap %var around %] as: %var <- (%var wrapped around %) diff --git a/lib/text.nom b/lib/text.nom index cbd0b3f..cd424b9 100644 --- a/lib/text.nom +++ b/lib/text.nom @@ -13,14 +13,15 @@ action [%texts joined with %glue] parse [joined %texts, %texts joined] as: %texts joined with "" compile [capitalized %text, %text capitalized] to - {expr:"(\(%text as lua expr)):gsub('%l', string.upper, 1)"} + {expr:"((\(%text as lua expr)):gsub('%l', string.upper, 1))"} compile [%text with %sub instead of %patt, %text s/%patt/%sub] to {expr:"((\(%text as lua expr)):gsub(\(%patt as lua expr), \(%sub as lua expr)))"} -compile [indented %text, %text indented] to {expr:"\%text:gsub('\\n','\\n'..(' '))"} +# TODO: figure out whether indent/dedent should affect first line +compile [indented %text, %text indented] to {expr:"((\%text):gsub('\\n','\\n'..(' ')))"} compile [dedented %obj, %obj dedented] to {expr:"nomsu:dedent(\(%obj as lua expr))"} -compile [%text indented %n times] to {expr:"\%text:gsub('\\n','\\n'..(' '):rep(\%n))"} +compile [%text indented %n times] to {expr:"((\%text):gsub('\\n','\\n'..(' '):rep(\%n)))"} # Text literals lua> ".." @@ -31,7 +32,7 @@ lua> ".." }; for name, e in pairs(escapes) do local lua = "'"..e.."'"; - nomsu:define_compile_action(name, \(!! code location !!), function() return {expr=text}; end); + nomsu:define_compile_action(name, \(!! code location !!), function() return {expr=lua}; end); end local colors = { ["reset color"]="\\\\27[0m", bright="\\\\27[1m", dim="\\\\27[2m", underscore="\\\\27[4m", diff --git a/nomsu.lua b/nomsu.lua index afa4cb0..d2acc1a 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -1311,6 +1311,9 @@ do self:define_compile_action("immediately %block", get_line_no(), function(_block) local lua = nomsu:tree_to_lua(_block) local lua_code = lua.statements or (lua.expr .. ";") + if lua.locals and #lua.locals > 0 then + lua_code = "local " .. tostring(concat(lua.locals, ", ")) .. ";\n" .. tostring(lua_code) + end nomsu:run_lua(lua_code) return { statements = "if IMMEDIATE then\n" .. tostring(lua_code) .. "\nend", diff --git a/nomsu.moon b/nomsu.moon index 491a584..e2e2f10 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -29,6 +29,7 @@ do if type(i) == 'number' then return string.sub(@, i, i) elseif type(i) == 'table' then return string.sub(@, i[1], i[2]) else return string[i] + -- Can't use this because it breaks some LPEG stuff --STRING_METATABLE.__mul = (other)=> string.rep(@, other) -- TODO: @@ -39,6 +40,7 @@ do -- Add compiler options for optimization level (compile-fast vs. run-fast, etc.) -- Do a pass on all actions to enforce parameters-are-nouns heuristic -- Maybe do some sort of lazy definitions of actions that defer until they're used in code +-- Add a ((%x foo %y) where {x:"asdf", y:"fdsa"}) compile-time action for substitution lpeg.setmaxstack 10000 -- whoa {:P,:R,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg @@ -223,6 +225,7 @@ class NomsuCompiler -- TODO: repair error("Not currently functional.", 0) + -- TODO: figure out whether indent/dedent should affect first line dedent: (code)=> unless code\find("\n") return code @@ -900,10 +903,11 @@ class NomsuCompiler insert concat_parts, lua.expr return concat(concat_parts) - -- TODO: fix how 'immediately' works with locals, e.g. "immediately: %x <- 5" @define_compile_action "immediately %block", get_line_no!, (_block)-> lua = nomsu\tree_to_lua(_block) lua_code = lua.statements or (lua.expr..";") + if lua.locals and #lua.locals > 0 + lua_code = "local #{concat lua.locals, ", "};\n#{lua_code}" nomsu\run_lua(lua_code) return statements:"if IMMEDIATE then\n#{lua_code}\nend", locals:lua.locals diff --git a/tests/all.nom b/tests/all.nom new file mode 100644 index 0000000..5b95786 --- /dev/null +++ b/tests/all.nom @@ -0,0 +1,28 @@ +use "lib/core.nom" + +# Prerequisites: +%var <- 99 +assume (%var = 99) or barf "Var assignment doesn't work." + +try: barf "barf" +..and if it succeeds: barf "try % and if it succeeds failed." + +try: do nothing +..and if it barfs: barf "try % and if it barfs failed." + +immediately + assume (%ONCE is (nil)) or barf "immediately is executing twice" + export %ONCE <- "ONCE" + %foo <- "immediately local" +assume (%ONCE = "ONCE") or barf "Globals don't work." +assume (%foo is (nil)) or barf "Immediately is leaking locals." + +# Run per-file test suites +run file "tests/metaprogramming.nom" +run file "tests/text.nom" +run file "tests/operators.nom" +run file "tests/control_flow.nom" +run file "tests/math.nom" +run file "tests/collections.nom" + +say "All tests passed!" diff --git a/tests/collections.nom b/tests/collections.nom new file mode 100644 index 0000000..ec79653 --- /dev/null +++ b/tests/collections.nom @@ -0,0 +1,43 @@ +#.. + Tests for the stuff defined in lib/control_flow.nom + +use "lib/core.nom" + +assume ((2nd to last in [1,2,3,4,5]) = 4) +assume ((first in [1,2]) = 1) +assume ((last in [1,2]) = 2) +assume (3 is in [1,2,3,4,5]) +assume (99 isn't in [1,2,3]) +assume ({x:no} has key "x") +assume ({x:no} doesn't have key "y") +assume (not ({x:no} doesn't have key "x")) +assume ((size of [1,2,3]) = 3) +%list <- [1,2,3,4,5] +append 6 to %list +assume ((last in %list) = 6) +pop from %list +assume ((last in %list) = 5) +remove index 1 from %list +assume ((first in %list) = 2) +assume (((% * %) for all [1,2,3]) = [1,4,9]) +assume ((%k = (%v * %v) for %k = %v in {x:1,y:2,z:3}) = {x:1,y:4,z:9}) +assume ((%k for %k = %v in {x:1}) = ["x"]) +assume ((% = (% * %) for all [1,2,3]) = {1:1,2:4,3:9}) +assume (([[1,2],[3,4]] flattened) = [1,2,3,4]) +assume ((entries in {x:1}) = [{key:"x",value:1}]) +assume ((keys in {x:1}) = ["x"]) +assume ((values in {x:1}) = [1]) +assume ((sorted [3,1,2]) = [1,2,3]) +%x <- [3,1,2] +sort %x +assume (%x = [1,2,3]) +sort %x by (-%) +assume (%x = [3,2,1]) +%keys <- {1:999,2:0,3:50} +sort %x by (% in %keys) +assume (%x = [2,3,1]) +assume ((unique [1,2,1,3,2,3]) = [1,2,3]) +%c <- (new counter) +for all ["x","y","x","x","y"] + (% in %c) +<- 1 +assume (%c = {x:3,y:2}) diff --git a/tests/control_flow.nom b/tests/control_flow.nom new file mode 100644 index 0000000..f0fc092 --- /dev/null +++ b/tests/control_flow.nom @@ -0,0 +1,191 @@ +#.. + Tests for the stuff defined in lib/control_flow.nom + +use "lib/core.nom" +do nothing + +action [test conditionals] + if: yes + %loc1 <- (yes) + if: no + barf "entered if 'no' conditional" + + unless: yes + barf "entered unless 'yes' conditional" + + if: yes + %loc2 <- (yes) + ..else + barf "entered if 'yes' else conditional" + + unless: no + %loc3 <- (yes) + ..else + barf "entered unless 'no' else conditional" + +assume (all of [%loc1 = (nil), %loc2 = (nil), %loc3 = (nil)]) or barf "conditionals leaking locals" + +assume ((5 if (yes) else 1) = 5) +assume ((5 if (no) else 1) = 1) +action [return nil]: return (nil) +assume (((return nil) if (yes) else 99) = (nil)) + +go to %skip +error "go to failed." +--- %skip --- + +%tot <- 0 +for %x in [1,2,3] + %tot +<- %x +assume (%tot = 6) or barf "for-loop failed" + +%tot <- 0 +for all [1,2,3] + %tot +<- % +assume (%tot = 6) or barf "for-all-loop failed" + +%x <- 0 +repeat + %x +<- 1 + if (%x = 3): stop repeating + if (%x > 3): barf "Failed to stop repeat loop" +assume (%x = 3) or barf "Failed to repeat" + +%x <- 0 +repeat 5 times + %x +<- 1 +assume (%x = 5) or barf "Failed to repeat 5 times" + +<- {%x:0,%y:0} +for all [1,2,3] + repeat 5 times + do next repeat + %x +<- 1 + %y +<- 1 +assume ([%x,%y] = [0,3]) or barf "Failed to continue repeat" + +<- {%x:0,%y:0} +for all [1,2,3] + repeat 5 times + do next % + %x +<- 1 + %y +<- 1 +assume ([%x,%y] = [0,0]) or barf "Failed to continue for" + +<- {%x:0,%y:0} +for all [1,2,3] + repeat 5 times + stop repeating + %x +<- 1 + %y +<- 1 +assume ([%x,%y] = [0,3]) or barf "Failed to stop repeat" + +<- {%x:0,%y:0} +for all [1,2,3] + repeat 5 times + stop % + %x +<- 1 + %y +<- 1 +assume ([%x,%y] = [0,0]) or barf "Failed to stop for" + +%x <- 0 +repeat while: %x < 10 + %x +<- 1 +assume (%x = 10) or barf "repeat-while failed" + +%x <- 0 +repeat until: %x = 10 + %x +<- 1 +assume (%x = 10) or barf "repeat-until failed" + +%x <- 0 +for %i from 1 to 3: %x +<- %i +assume (%x = 6) or barf "Numeric for range failed" + +%x <- 0 +for %i from 3 to 1 via -1: %x +<- %i +assume (%x = 6) or barf "backwards numeric for range failed" + +%x <- 0 +for all 1 to 3: %x +<- % +assume (%x = 6) or barf "terse numeric for range failed" + +%result <- {} +for %key = %value in {x:1,y:2} + (%result's ("\%key\%key")) <- (%value * 11) +assume (%result = {xx:11,yy:22}) or barf "key/value iteration failed" + +for %key = %value in {x:1,y:2} + stop %key + barf "stopping key failed" + +for %key = %value in {x:1,y:2} + stop %value + barf "stopping value failed" + +for %key = %value in {x:1,y:2} + do next %key + barf "skipping key failed" + +for %key = %value in {x:1,y:2} + do next %value + barf "skipping value failed" + +action [barfer]: barf "this should never be reached" +when + * (no): barf "'when' fail" + * (no) + * (3 > 4): barf "'when' fail 2" + * (yes) + * (barfer): do nothing + * (99 > 1): barf "Fell through incorrectly" + +%else_worked <- (no) +when + * (no): barf + * else: %else_worked <- (yes) +assume %else_worked or barf "when..else failed" + +action [test when scope] + when + * (yes): %leaked <- (yes) +test when scope +assume (not %leaked) or barf "'when' is leaking locals" + +%when_worked <- (no) +when 4 = ? + * 1 + * 2: barf "'when = ?' fail" + * 3 + * 4 + * (barfer): %when_worked <- (yes) +assume %when_worked + +%when_worked <- (no) +when 5 = ? + * 6: barf + * else: %when_worked <- (yes) +assume %when_worked + +try: barf +..and if it succeeds: barf "try failed." + +%worked <- (no) +try: barf +..and if it barfs: %worked <- (yes) +assume %worked or barf "try/catch failed" + +%x <- 1 +do + %x <- 2 +assume (%x = 2) or barf "'do' is redefining locals" + +%x <- 1 +try + %x <- 2 + do + barf + ..then always + %x <- 1 +..and if it barfs: do nothing +assume (%x = 1) or barf "do/then always failed" diff --git a/tests/math.nom b/tests/math.nom new file mode 100644 index 0000000..245fe52 --- /dev/null +++ b/tests/math.nom @@ -0,0 +1,17 @@ +#.. + Tests for the stuff defined in lib/control_flow.nom + +use "lib/core.nom" + +assume (all of [inf, pi, tau, golden ratio, e]) or barf "math constants failed" +%nan <- (NaN) +assume (%nan != %nan) or barf "NaN failed" +assume (("5" as a number) = 5) +assume + all of [..] + abs 5, |5|, sqrt 5, √(5), sine 5, cosine 5, tangent 5, arc sine 5, arc cosine 5, + arc tangent 5, arc tangent 5/10, hyperbolic sine 5, hyperbolic cosine 5, + hyperbolic tangent 5, e^5, ln 5, log_(2) 5, floor 5, ceiling 5, round 5, +..or barf "math functions failed" +assume ((463 to the nearest 100) = 500) or barf "rounding failed" +assume ((2.6 to the nearest 0.25) = 2.5) or barf "rounding failed" diff --git a/tests/metaprogramming.nom b/tests/metaprogramming.nom new file mode 100644 index 0000000..37acd3e --- /dev/null +++ b/tests/metaprogramming.nom @@ -0,0 +1,55 @@ +#.. + Tests for the stuff defined in lib/metaprogramming.nom + +use "lib/core.nom" + +immediately + compile [five] to {expr:"5"} +assume ((five) = 5) or barf "Compile to expression failed." + +immediately + compile [loc x] to {statements:"_x = 99", locals:["_x"]} +lua> "do" +loc x +assume (%x is 99) or barf "Compile to statements with locals failed." +lua> "end" +assume (%x is (nil)) or barf "Failed to properly localize a variable." + +immediately + compile [asdf] to + %tmp <- "" + return {statements:%tmp} +asdf +assume (%tmp is (nil)) or barf "compile to is leaking variables" + +action [foo %x] + %y <- (%x + 1) + return %y +assume ((foo 10) = 11) or barf "Action didn't work." +assume (%y is (nil)) or barf "Action leaked a local into globals." + +immediately + parse [baz %] as: foo % +assume ((baz 10) = 11) or barf "Parse as action failed." + +immediately + parse [V] as: five +assume ((V) = 5) or barf "Parse as compile action failed." + +remove action "foo %" +try: foo 99 +..and if it succeeds: barf "Failed to delete action" + +assume ((\(5 + 5) as value) = 10) or barf "%tree as value failed." + +assume ((\(foo %x)'s source code) = "foo %x") or barf "source code failed." + +assume ((repr [1,2]) = "{1, 2}") or barf "repr failed." + +assume ((type of {}) = "table") or barf "type of failed." + +assume ((nomsu) = (=lua "nomsu")) or barf "nomsu failed" + +assume (("x" as lua identifier) = (\%x as lua identifier)) or barf "converting to identifier failed." + +assume ((run "return 99") = 99) or barf "run % failed." diff --git a/tests/operators.nom b/tests/operators.nom new file mode 100644 index 0000000..e3bd0b9 --- /dev/null +++ b/tests/operators.nom @@ -0,0 +1,76 @@ +#.. + Tests for the stuff defined in lib/operators.nom + +use "lib/core.nom" + +assume (({x:5}'s "x") = 5) or barf "indexing doesn't work." +try: % <- ({}'s "[[[\n]]]") +..and if it barfs: barf "failed to index a table literal with a string containing brackets n stuff" + +<-{%x:10,%y:20} +assume ((%x = 10) and (%y = 20)) or barf "mutli-assignment failed." +<-{%x:%y, %y:%x} +assume ((%y = 10) and (%x = 20)) or barf "swapping vars failed." + +% <- [%x < %y, %x <= %y, %x > %y, %x >= %y, %x = %y, %x is %y, %x != %y, %x isn't %y, %x is not %y] + +assume ({} is {}) or barf "Equality check failed." +assume (({}'s id) is not ({}'s id)) or barf "Identity check failed." + +<-{%x:"outer",%y:"outer"} +action [set global x local y] + export %x <- "inner" + %y <- "inner" +set global x local y +assume ((%x = "inner") and (%y = "outer")) or barf "export failed." + +<-{%x:"outer",%y:"outer"} +action [set global x local y] + exporting [%x] + %x <- "inner" + %y <- "inner" +set global x local y +assume ((%x = "inner") and (%y = "outer")) or barf "export failed." + +<-{%x:1,%y:2} +with [%z, %x<-999] + %z <- 999 + assume (%z = 999) or barf "'with' failed." + assume (%x = 999) or barf "'with' assignment failed." +assume (%x = 1) or barf "'with' scoping failed" +assume (%z = (nil)) or barf "'with' scoping failed" + +assume ((1+2*3-4/2^2) = 6) or barf "math expressions not working properly" +assume ((5 wrapped around 2) = 1) or barf "mod not working" +assume (1 <= 2 < 3) or barf "chained operator fail." +%value <- -999 +action [flipflop] + export %value <- (-%value) + return %value +assume (not (1 < (flipflop) < 1)) or barf "3-way inequality evaluated middle term twice" +assume (((yes) and (yes)) = (yes)) +action [barfer] + barf "short circuiting failed" +assume (((no) and (barfer)) = (no)) +assume ((no) or (yes)) +assume ((yes) or (barfer)) + +assume ((1 OR 2) = 3) +assume ((3 XOR 2) = 1) +assume ((3 AND 2) = 2) +assume ((NOT (NOT 6)) = 6) +assume ((1<<1) = 2) +assume ((2>>1) = 1) +assume ((2>>>1) = 1) +#.. Ugh, Lua is stupid when it comes to bitwise arithmetic on negative numbers, so I'm + skipping the tests for those. + +assume ((-(5)) = -5) +assume ((not (yes)) = (no)) +%x <- 1 +%x +<- 1 +assume (%x = 2) or barf "+<- failed" +%x *<- 2 +assume (%x = 4) or barf "*<- failed" +wrap %x around 3 +assume (%x = 1) or barf "wrap around failed" diff --git a/tests/text.nom b/tests/text.nom new file mode 100644 index 0000000..ea18d42 --- /dev/null +++ b/tests/text.nom @@ -0,0 +1,13 @@ +#.. + Tests for the stuff defined in lib/text.nom + +use "lib/core.nom" + +assume ((["x","y"] joined with ",") = "x,y") or barf "joined with failed" +assume ((["x","y"] joined) = "xy") or barf "joined failed" +assume (("asdf" capitalized) = "Asdf") or barf "capitalized failed" +assume (("asdf" with "X" instead of "s") = "aXdf") or barf "substitution failed" +# TODO: add tests for indent/dedent +assume ("\n" = (newline)) or barf "Text literals failed." +%x <- "\(green)hello\(reset color)" +assume (("x" + "y") = "xy") diff --git a/utils.lua b/utils.lua index 80585a5..937252f 100644 --- a/utils.lua +++ b/utils.lua @@ -284,7 +284,7 @@ local function sort(list, keyFn, reverse) end local function equivalent(x, y, depth) - depth = depth or 1 + depth = depth or -1 if x == y then return true end @@ -296,6 +296,8 @@ local function equivalent(x, y, depth) end if depth == 0 then return false + elseif depth < -999 then + error("Exceeded maximum comparison depth") end local checked = {} for k, v in pairs(x) do