diff --git a/lib/class.nom b/lib/class.nom index e65fb81..b7506f3 100644 --- a/lib/class.nom +++ b/lib/class.nom @@ -57,7 +57,7 @@ compile [object %classname %class_body] to body_lua, repr(names), repr(\%line:get_line_no()), table.concat(args, ", "), repr(repr(stub)), table.concat(arg_nomsus, ".."), - repr(nomsu:dedent(\%line.src)), + repr(nomsu:dedent(\%line:get_src())), \%class_identifier, repr(stub), repr(stub))); return ".." diff --git a/lib/collections.nom b/lib/collections.nom index 15f27ed..c31bbee 100644 --- a/lib/collections.nom +++ b/lib/collections.nom @@ -12,7 +12,7 @@ use "lib/operators.nom" 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 "utils.nth_to_last(\(%list as lua), \(%index as lua))" +..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 @@ -32,25 +32,28 @@ action [..] if (%key is %item): return (no) return (yes) -compile [%list has key %index, %list has index %index] to ".." - ((\(%list as lua))[\(%index as lua)] ~= nil) +# 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 "((\(%list as lua))[\(%index as lua)] ~= nil)" +..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 - "utils.size(\(%list as lua))" + {expr:"utils.size(\(%list as lua expr))"} compile [append %item to %list, add %item to %list] to - "table.insert(\(%list as lua), \(%item as lua))" + {statements:"table.insert(\(%list as lua expr), \(%item as lua expr))"} compile [pop from %list, remove last from %list] to - "table.remove(\(%list as lua))" + {statements:"table.remove(\(%list as lua expr))"} compile [remove index %index from %list] to - "table.remove(\(%list as lua), \(%index as lua))" + {statements:"table.remove(\(%list as lua expr), \(%index as lua expr))"} action [flatten %lists] @@ -74,14 +77,15 @@ immediately compile [%expression for %item in %iterable] to assume ((%item's "type") is "Var") or barf ".." List comprehension has the wrong type for the loop variable. Expected Var, but got: \(%item's "type") - return ".." - (function() - local comprehension = {}; - for i,\(%item as lua) in ipairs(\(%iterable as lua)) do - comprehension[i] = \(%expression as lua); - end - return comprehension; - end)() + return {..} + expr:".." + (function() + local comprehension = {}; + for i,\(%item as lua expr) in ipairs(\(%iterable as lua expr)) do + comprehension[i] = \(%expression as lua expr); + end + return comprehension; + end)() parse [%expression for all %iterable] as: %expression for % in %iterable compile [%expression for %key = %value in %iterable] to @@ -89,28 +93,31 @@ immediately List comprehension has the wrong type for the key loop variable. Expected Var, but got: \(%key's "type") assume ((%value's "type") is "Var") or barf ".." List comprehension has the wrong type for the value loop variable. Expected Var, but got: \(%value's "type") - return ".." - (function() - local comprehension = {}; - for \(%key as lua), \(%value as lua) in pairs(\(%iterable as lua)) do - comprehension[i] = \(%expression as lua) - end - return comprehension; - end)() + return {..} + expr: ".." + (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) + end + return comprehension; + end)() # Dict comprehensions immediately compile [%key = %value for %item in %iterable] to assume ((%item's "type") is "Var") or barf ".." Dict comprehension has the wrong type for the loop variable. Expected Var, but got: \(%item's "type") - return ".." - (function() - local comprehension = {}; - for i,\(%item as lua) in ipairs(\(%iterable as lua)) do - comprehension[\(%key as lua)] = \(%value as lua) - end - return comprehension; - end)() + # Note: it's important to have the space after "[" to prevent confusion if %key is a string + return {..} + expr: ".." + (function() + local comprehension = {}; + for i,\(%item as lua expr) in ipairs(\(%iterable as lua expr)) do + comprehension[ \(%key as lua expr)] = \(%value as lua expr) + end + return comprehension; + end)() parse [%key = %value for all %iterable] as: %key = %value for % in %iterable compile [%key = %value for %src_key = %src_value in %iterable] to @@ -118,21 +125,24 @@ immediately Dict comprehension has the wrong type for the key loop variable. Expected Var, but got: \(%src_key's "type") assume ((%src_value's "type") is "Var") or barf ".." Dict comprehension has the wrong type for the value loop variable. Expected Var, but got: \(%src_value's "type") - return ".." - (function() - local comprehension = {}; - for \(%src_key as lua), \(%src_value as lua) in pairs(\(%iterable as lua)) do - comprehension[\(%key as lua)] = \(%value as lua); - end - return comprehension; - end)() + # Note: it's important to have the space after "[" to prevent confusion if %key is a string + return {..} + expr: ".." + (function() + local comprehension = {}; + for \(%src_key as lua expr), \(%src_value as lua expr) in pairs(\(%iterable as lua expr)) do + comprehension[ \(%key as lua expr)] = \(%value as lua expr); + end + return comprehension; + end)() # Sorting: -compile [sort %items] to "table.sort(\(%items as lua))" -compile [sort %items by %key_expr] to ".." - utils.sort(\(%items as lua), function(\(\% as lua)) - return \(%key_expr as lua); - end) +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) @@ -147,16 +157,17 @@ action [unique %items] [%k for %k=%v in (%=(yes) for all %items)] # Metatable stuff -compile [set %dict's metatable to %metatable] to code ".." - setmetatable(\(%dict as lua), \(%metatable as lua)); +compile [set %dict's metatable to %metatable] to {..} + statements: "setmetatable(\(%dict as lua expr), \(%metatable as lua expr));" -compile [new counter] to "setmetatable({}, {__index=function() return 0; end})" +compile [new counter] to {expr:"setmetatable({}, {__index=function() return 0; end})"} -compile [new default dict] to ".." - 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 7347a29..a6f460a 100644 --- a/lib/control_flow.nom +++ b/lib/control_flow.nom @@ -8,27 +8,35 @@ use "lib/operators.nom" # No-Op immediately - compile [do nothing] to code "" - -# Return -immediately - compile [return] to code "do return; end" - compile [return %return_value] to code "do return \(%return_value as lua); end" + compile [do nothing] to {statements:""} # Conditionals immediately - compile [if %condition %if_body] to code ".." - if \(%condition as lua) then - \(%if_body as lua statements) - end --end if + compile [if %condition %if_body] to + %if_body <- (%if_body as lua) + return {..} + locals: %if_body's "locals" + statements:".." + if \(%condition as lua expr) then + \((%if_body's "statements") or "\(%if_body's "expr");") + end parse [unless %condition %unless_body] as: if (not %condition) %unless_body - compile [if %condition %if_body else %else_body, unless %condition %else_body else %if_body] to code ".." - if \(%condition as lua) then - \(%if_body as lua statements) - else - \(%else_body as lua statements) - end --end if + compile [if %condition %if_body else %else_body, unless %condition %else_body else %if_body] to + %if_body <- (%if_body as lua) + %else_body <- (%else_body as lua) + lua> ".." + local \%locals = {unpack(\%if_body.locals or {})}; + for i,loc in ipairs(\%else_body.locals or {}) do table.insert(\%locals, loc); end + utils.deduplicate(\%locals); + return {..} + locals:%locals + statements:".." + if \(%condition as lua expr) then + \((%if_body's "statements") or "\(%if_body's "expr");") + else + \((%else_body's "statements") or "\(%else_body's "expr");") + end # Conditional expression (ternary operator) #.. Note: this uses a function instead of "(condition and if_expr or else_expr)" @@ -43,151 +51,139 @@ immediately #.. If %when_true_expr is guaranteed to be truthy, we can use Lua's idiomatic equivalent of a conditional expression: (cond and if_true or if_false) if: (%when_true_expr's "type") in {Text:yes, List:yes, Dict:yes, Number:yes} - return "(\(%condition as lua) and \(%when_true_expr as lua) or \(%when_false_expr as lua))" + return {..} + expr:"(\(%condition as lua expr) and \(%when_true_expr as lua expr) or \(%when_false_expr as lua expr))" ..else #.. Otherwise, need to do an anonymous inline function (yuck, too bad lua doesn't have a proper ternary operator!) To see why this is necessary consider: (random()<.5 and false or 99) - return ".." - (function() - if \(%condition as lua) then - return \(%when_true_expr as lua); - else - return \(%when_false_expr as lua); - end - end)() - + return {..} + expr: ".." + (function() + if \(%condition as lua expr) then + return \(%when_true_expr as lua expr); + else + return \(%when_false_expr as lua expr); + end + end)() # GOTOs immediately - compile [=== %label ===, --- %label ---] to code ".." - ::label_\(nomsu "var_to_lua_identifier" [%label])::; - compile [go to %label] to code ".." - goto label_\(nomsu "var_to_lua_identifier" [%label]); + compile [=== %label ===, --- %label ---, *** %label ***] to {..} + statements:"::label_\(nomsu "var_to_lua_identifier" [%label])::;" + compile [go to %label] to {..} + statements:"goto label_\(nomsu "var_to_lua_identifier" [%label]);" # Basic loop control immediately - compile [do next] to code "continue;" - compile [stop] to code "break;" + compile [do next] to {statements:"continue;"} + compile [stop] to {statements:"break;"} # Helper function immediately - compile [for subtree %subtree where %condition in %tree %body] to code ".." - for \(%subtree as lua) in coroutine.wrap(function() nomsu:walk_tree(\(%tree as lua)) end) do - if type(\(%subtree as lua)) == 'table' and \(%subtree as lua).type then - if \(%condition as lua) then - \(%body as lua statements) + compile [if %tree has subtree %subtree where %condition %body] to + %body_lua <- (%body as lua) + %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");") + return {..} + locals: %body_lua's "locals" + statements:".." + for \(%subtree as lua expr) in coroutine.wrap(function() nomsu:walk_tree(\(%tree as lua expr)) end) do + if type(\(%subtree as lua expr)) == 'table' and \(%subtree as lua expr).type then + if \(%condition as lua expr) then + \%body_statements + break; + end + end end - end - end # While loops immediately - compile [do next repetition] to code "goto continue_repeat;" - compile [stop repeating] to code "goto stop_repeat;" - compile [repeat while %condition %body] to code - %continue_labels <- "" - for subtree % where - ((%'s "type") = "FunctionCall") and ((%'s "stub") == "do next repetition") - ..in %body - %continue_labels <- "\n::continue_repeat::;" - stop + compile [do next repetition] 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::;" %code <- ".." - while \(%condition as lua) do - \(%body as lua statements)\ - ..\%continue_labels + while \(%condition as lua expr) do + \%body_statements end --while-loop - for subtree % where - ((%'s "type") = "FunctionCall") and ((%'s "stub") == "stop repeating") - ..in %body + if %body has subtree % where + ((%'s "type") = "FunctionCall") and ((%'s "stub") is "stop repeating") + .. %code <- ".." do -- scope of "stop repeating" label \%code ::stop_repeat::; end -- end of "stop repeating" label scope - %continue_labels <- "\n::continue_repeat::;" - stop - return %code + return {statements:%code, locals:%body_lua's "locals"} parse [repeat %body] as: repeat while (yes) %body parse [repeat until %condition %body] as: repeat while (not %condition) %body compile [..] repeat %n times %body - ..to code - %continue_labels <- "" - for subtree % where - ((%'s "type") = "FunctionCall") and ((%'s "stub") == "do next repetition") - ..in %body - %continue_labels <- "\n::continue_repeat::;" - stop - # This trashes the loop variables, just like in Python. + ..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_statements + "\n::continue_repeat::;" %code <- ".." - for i=1,\(%n as lua) do - \(%body as lua statements)\ - ..\%continue_labels + for i=1,\(%n as lua expr) do + \%body_statements end --numeric for-loop - %stop_labels <- "" - for subtree % where - ((%'s "type") = "FunctionCall") and ((%'s "stub") == "stop repeating") - ..in %body + if %body has subtree % where + ((%'s "type") = "FunctionCall") and ((%'s "stub") is "stop repeating") + .. %code <- ".." do -- scope of "stop repeating" label \%code ::stop_repeat::; end -- end of "stop repeating" label scope - %continue_labels <- "\n::continue_repeat::;" - stop - return %code + return {statements:%code, locals:%body_lua's "locals"} # For loop control flow: immediately - compile [stop %var] to code ".." - goto stop_\(nomsu "var_to_lua_identifier" [%var]); - compile [do next %var] to code ".." - goto continue_\(nomsu "var_to_lua_identifier" [%var]); + compile [stop %var] to {..} + statements:"goto stop_\(nomsu "var_to_lua_identifier" [%var]);" + compile [do next %var] to {..} + statements:"goto continue_\(nomsu "var_to_lua_identifier" [%var]);" # Numeric range for loops immediately compile [..] for %var from %start to %stop by %step %body for %var from %start to %stop via %step %body - ..to code - %continue_labels <- "" - for subtree % where + ..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") == "do next %") and - ((3rd in (%'s "value"))'s "src") == (%var's "src") - ..in %body - %continue_labels <- "\n::continue_\(nomsu "var_to_lua_identifier" [%var])::;" - stop + ((%'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])::;" - if: (%var's "type") is "Var" - %loop_var <- (%var as lua) - %loop_var_shim <- "" - ..else - %loop_var <- "i" - %loop_var_shim <- "\n\(%var as lua) = i;" - # This trashes the loop variables, just like in Python. + # 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 <- ".." - for \(%loop_var)=\(%start as lua),\(%stop as lua),\(%step as lua) do\ - ..\%loop_var_shim - \(%body as lua statements)\ - ..\%continue_labels + for \(%var as lua expr)=\(%start as lua expr),\(%stop as lua expr),\(%step as lua expr) do + \%body_statements end --numeric for-loop - for subtree % where + if %body has subtree % where ((%'s "type") = "FunctionCall") and - ((%'s "stub") == "stop %") and - ((2nd in (%'s "value"))'s "src") == (%var's "src") - ..in %body + ((%'s "stub") is "stop %") and + ((2nd in (%'s "value"))'s "value") is (%var's "value") + .. %code <- ".." do -- scope for stopping for-loop \%code ::stop_\(nomsu "var_to_lua_identifier" [%var])::; end -- end of scope for stopping for-loop - stop - return %code + return {statements:%code, locals:%body_lua's "locals"} immediately parse [for %var from %start to %stop %body] as: for %var from %start to %stop via 1 %body @@ -197,113 +193,89 @@ immediately ..as: for % from %start to %stop via %step %body parse [for all %start to %stop %body] as: for all %start to %stop via 1 %body -# For-each loop +# For-each loop (lua's "ipairs()") immediately - compile [for %var in %iterable %body] to code - %continue_labels <- "" - for subtree % where + compile [for %var in %iterable %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") == "do next %") and - ((3rd in (%'s "value"))'s "src") == (%var's "src") - ..in %body - %continue_labels <- "\n::continue_\(nomsu "var_to_lua_identifier" [%var])::;" - stop - if: (%var's "type") is "Var" - %loop_var <- (%var as lua) - %loop_var_shim <- "" - ..else - %loop_var <- "value" - %loop_var_shim <- "\n\(%var as lua) = value;" - # This trashes the loop variables, just like in Python. + ((%'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])::;" + # 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 <- ".." - for i,\%loop_var in ipairs(\(%iterable as lua)) do\ - ..\%loop_var_shim - \(%body as lua statements)\ - ..\%continue_labels + for i,\(%var as lua expr) in ipairs(\(%iterable as lua expr)) do + \%body_statements end --foreach-loop - for subtree % where + if %body has subtree % where ((%'s "type") = "FunctionCall") and - ((%'s "stub") == "stop %") and - ((2nd in (%'s "value"))'s "src") == (%var's "src") - ..in %body + ((%'s "stub") is "stop %") and + ((2nd in (%'s "value"))'s "value") is (%var's "value") + .. %code <- ".." do -- scope for stopping for-loop \%code ::stop_\(nomsu "var_to_lua_identifier" [%var])::; end -- end of scope for stopping for-loop - stop - return %code + return {statements:%code, locals:%body_lua's "locals"} + parse [for all %iterable %body] as: for % in %iterable %body # Dict iteration (lua's "pairs()") immediately - compile [for %key = %value in %iterable %body] to code - %continue_labels <- "" - for subtree % where + compile [for %key = %value in %iterable %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") == "do next %") and - ((3rd in (%'s "value"))'s "src") == (%key's "src") - ..in %body - <- %continue_labels + "\n::continue_\(nomsu "var_to_lua_identifier" [%key])::;" - stop + ((%'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])::;" - for subtree % where + if %body has subtree % where ((%'s "type") = "FunctionCall") and - ((%'s "stub") == "do next %") and - ((3rd in (%'s "value"))'s "src") == (%value's "src") - ..in %body - <- %continue_labels + "\n::continue_\(nomsu "var_to_lua_identifier" [%value])::;" - stop + ((%'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])::;" - if: (%key's "type") is "Var" - %key_loop_var <- (%key as lua) - %key_loop_var_shim <- "" - ..else - %key_loop_var <- "key" - %key_loop_var_shim <- "\n\(%key as lua) = key;" - if: (%value's "type") is "Var" - %value_loop_var <- (%value as lua) - %value_loop_var_shim <- "" - ..else - %value_loop_var <- "value" - %value_loop_var_shim <- "\n\(%value as lua) = value;" - # This trashes the loop variables, just like in Python. + # 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)" + assume ((%value's "type") is "Var") or barf "Loop expected variable, not: \(%value's source code)" %code <- ".." - for \%key_loop_var,\%value_loop_var in pairs(\(%iterable as lua)) do\ - ..\%key_loop_var_shim\%value_loop_var_shim - \(%body as lua statements)\ - ..\%continue_labels + for \(%key as lua expr),\(%value as lua expr) in pairs(\(%iterable as lua expr)) do + \%body_statements end --foreach-loop + %stop_labels <- "" - for subtree % where + if %body has subtree % where ((%'s "type") = "FunctionCall") and - ((%'s "stub") == "stop %") and - ((2nd in (%'s "value"))'s "src") == (%key's "src") - ..in %body - <- %stop_labels + "\n::stop_\(nomsu "var_to_lua_identifier" [%key])::;" - stop + ((%'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])::;" - for subtree % where + if %body has subtree % where ((%'s "type") = "FunctionCall") and - ((%'s "stub") == "stop %") and - ((2nd in (%'s "value"))'s "src") == (%value's "src") - ..in %body - <- %stop_labels + "\n::stop_\(nomsu "var_to_lua_identifier" [%value])::;" - stop + ((%'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])::;" if: %stop_labels is not "" %code <- ".." do -- scope for stopping for % = % loop \%code\%stop_labels end - return %code + return {statements:%code, locals:%body_lua's "locals"} # Switch statement/multi-branch if immediately - compile [when %body] to code - %result <- "" + compile [when %body] to + %code <- "" %fallthroughs <- [] + %locals <- [] %is_first <- (yes) + %seen_else <- (no) for %func_call in (%body's "value") assume ((%func_call's "type") is "FunctionCall") or barf ".." Invalid format for 'when' statement. Only '*' blocks are allowed. @@ -318,37 +290,44 @@ 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 do> "table.insert(\%fallthroughs, \%condition)" + lua> "table.insert(\%fallthroughs, \%condition);" do next %func_call + %action <- (%action as lua) + %action_statements <- ((%action's "statements") or "\(%action's "expr");") + for %local in ((%action's "locals") or []) + lua> "table.insert(\%locals, \%local);" if: =lua "\%condition.type == 'Word' and \%condition.value == 'else'" - - <- %result + ".." + <- %code + ".." else - \(%action as lua statements) - stop + \%action_statements + %seen_else <- (yes) ..else - %condition <- (%condition as lua) + 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)" - <- %result + ".." + <- %code + ".." \("if" if %is_first else "elseif") \%condition then - \(%action as lua statements) + \%action_statements %fallthroughs <- [] %is_first <- (no) - if: %result is not "" - <- %result + "\nend" - return %result + assume (%fallthroughs = []) or barf "Unfinished fallthrough conditions in 'when' block" + if: %code is not "" + <- %code + "\nend" + lua> "utils.deduplicate(\%locals);" + return {statements:%code, locals:%locals} # Switch statement immediately - compile [when %branch_value = ? %body, when %branch_value is ? %body] to code - %result <- "" + compile [when %branch_value = ? %body, when %branch_value is ? %body] to + %code <- "" %fallthroughs <- [] + %locals <- [] %is_first <- (yes) %seen_else <- (no) for %func_call in (%body's "value") @@ -364,65 +343,82 @@ immediately lua> "table.insert(\%fallthroughs, \%condition)" do next %func_call + %action <- (%action as lua) + %action_statements <- ((%action's "statements") or "\(%action's "expr");") + for %local in ((%action's "locals") or []) + lua> "table.insert(\%locals, \%local);" + if: =lua "\%condition.type == 'Word' and \%condition.value == 'else'" assume (not %is_first) or barf "'else' clause cannot be first in 'when % = ?' block" - <- %result + ".." + <- %code + ".." else - \(%action as lua statements) + \%action_statements end %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))" + %clause <- "branch_value == (\(%condition as lua expr))" ..else - %clause <- "utils.equivalent(branch_value, \(%condition as lua))" + %clause <- "utils.equivalent(branch_value, \(%condition as lua expr))" for all %fallthroughs if: ((%'s "type") is "Text") or ((%'s "type") is "Number") - <- %clause + " or branch_value == (\(%condition as lua))" + <- %clause + " or branch_value == (\(%condition as lua expr))" ..else - <- %clause + " or utils.equivalent(branch_value, \(%condition as lua))" - <- %result + ".." + <- %clause + " or utils.equivalent(branch_value, \(%condition as lua expr))" + <- %code + ".." \("if" if %is_first else "elseif") \%clause then - \(%action as lua statements) + \%action_statements %fallthroughs <- [] %is_first <- (no) - assume (%result is not "") or barf "No body for 'when % = ?' block!" + assume (%fallthroughs = []) or barf "Unfinished fallthrough conditions in 'when' block" + assume (%code is not "") or barf "No body for 'when % = ?' block!" unless %seen_else - <- %result + "\nend" - %result <- ".." + <- %code + "\nend" + %code <- ".." do --when % = ? - local branch_value = \(%branch_value as lua);\ - ..\%result + local branch_value = \(%branch_value as lua expr);\ + ..\%code end --when % = ? - return %result + lua> "utils.deduplicate(\%locals);" + return {statements:%code, locals:%locals} # 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 - ..to code ".." - do - local fell_through = false; - local ok, ret = pcall(function() - \(%action as lua statements) - fell_through = true; - end); - if ok then - \(%success as lua statements) - end - if not ok then - \(%fallback as lua statements) - elseif not fell_through then - return ret; - end - end + ..to + %locals <- [] + %action <- (%action as lua) + %fallback <- (%fallback as lua) + for %sub_locals in [%action's "locals", %success's "locals", %fallback's "locals"] + for %local in %sub_locals + lua> "table.insert(\%locals, \%local);" + lua> "utils.deduplicate(\%locals);" + return {..} + locals: %locals + statements: ".." + do + local fell_through = false; + local ok, ret = pcall(function() + \((%action's "statements") or "\(%action's "expr");") + fell_through = true; + end); + if ok then + \((%success's "statements") or "\(%success's "expr");") + end + if not ok then + \((%fallback's "statements") or "\(%fallback'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 @@ -434,24 +430,39 @@ immediately # Do/finally: immediately - compile [do %action] to code - "do\n \(%action as lua statements)\nend" if ((%action's "type") is "Block") - ..else "(\(%action as lua))(nomsu);" + compile [do %action] to + %action <- (%action as lua) + return {..} + locals: %action's "locals" + statements: ".." + do + \((%action's "statements") or "\(%action's "expr");") + end - compile [do %action then always %final_action] to code ".." - do - local fell_through = false; - local ok, ret1 = pcall(function() - \(%action as lua statements) - fell_through = true; - end); - local ok2, ret2 = pcall(function() - \(%final_action as lua statements) - end); - if not ok then error(ret1); end - if not ok2 then error(ret2); end - if not fell_through then - return ret1; - end - end + compile [do %action then always %final_action] to + %action <- (%action as lua) + %final_action <- (%action as lua) + %locals <- [] + for %sub_locals in [%action's "locals", %final_action's "locals"] + for %local in %sub_locals + lua> "table.insert(\%locals, \%local);" + lua> "utils.deduplicate(\%locals);" + return {..} + locals: %locals + statements: ".." + do + local fell_through = false; + local ok, ret1 = pcall(function() + \((%action's "statements") or "\(%action's "expr");") + fell_through = true; + end); + local ok2, ret2 = pcall(function() + \((%final_action's "statements") or "\(%final_action's "expr");") + end); + if not ok then error(ret1); end + if not ok2 then error(ret2); end + if not fell_through then + return ret1; + end + end diff --git a/lib/math.nom b/lib/math.nom index 0210293..ea1769a 100644 --- a/lib/math.nom +++ b/lib/math.nom @@ -5,65 +5,71 @@ use "lib/metaprogramming.nom" use "lib/control_flow.nom" # Literals: -compile [infinity, inf] to "math.huge" -compile [not a number, NaN, nan] to "(0/0)" -compile [pi, Pi, PI] to "math.pi" -compile [tau, Tau, TAU] to "(2*math.pi)" -compile [golden ratio] to "((1+math.sqrt(5))/2)" -compile [e] to "math.e" +compile [infinity, inf] to {expr:"math.huge"} +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"} # Functions: -compile [% as number] to "tonumber(\(% as lua))" -compile [absolute value %, | % |, abs %] to "math.abs(\(% as lua))" -compile [square root %, √%, sqrt %] to "math.sqrt(\(% as lua))" -compile [sine %, sin %] to "math.sin(\(% as lua))" -compile [cosine %, cos %] to "math.cos(\(% as lua))" -compile [tangent %, tan %] to "math.tan(\(% as lua))" -compile [arc sine %, asin %] to "math.asin(\(% as lua))" -compile [arc cosine %, acos %] to "math.acos(\(% as lua))" -compile [arc tangent %, atan %] to "math.atan(\(% as lua))" -compile [arc tangent %y/%x, atan2 %y %x] to "math.atan2(\(%y as lua), \(%x as lua))" -compile [hyperbolic sine %, sinh %] to "math.sinh(\(% as lua))" -compile [hyperbolic cosine %, cosh %] to "math.cosh(\(% as lua))" -compile [hyperbolic tangent %, tanh %] to "math.tanh(\(% as lua))" -compile [e^%, exp %] to "math.exp(\(% as lua))" -compile [natural log %, ln %, log %] to "math.log(\(% as lua))" -compile [log % base %base, log_%base %, log base %base %] to "math.log(\(% as lua), \(%base as lua))" -compile [floor %] to "math.floor(\(% as lua))" -compile [ceiling %, ceil %] to "math.ceil(\(% as lua))" -compile [round %, % rounded] to "math.floor(\(% as lua) + .5)" +compile [% as 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))"} +compile [cosine %, cos %] to {expr:"math.cos(\(% as lua expr))"} +compile [tangent %, tan %] to {expr:"math.tan(\(% as lua expr))"} +compile [arc sine %, asin %] to {expr:"math.asin(\(% as lua expr))"} +compile [arc cosine %, acos %] to {expr:"math.acos(\(% as lua expr))"} +compile [arc tangent %, atan %] to {expr:"math.atan(\(% as lua expr))"} +compile [arc tangent %y/%x, atan2 %y %x] to {expr:"math.atan2(\(%y as lua expr), \(%x as lua expr))"} +compile [hyperbolic sine %, sinh %] to {expr:"math.sinh(\(% as lua expr))"} +compile [hyperbolic cosine %, cosh %] to {expr:"math.cosh(\(% as lua expr))"} +compile [hyperbolic tangent %, tanh %] to {expr:"math.tanh(\(% as lua expr))"} +compile [e^%, exp %] to {expr:"math.exp(\(% as lua expr))"} +compile [natural log %, ln %, log %] to {expr:"math.log(\(% as lua expr))"} +compile [log % base %base, log_%base %, log base %base %] to {expr:"math.log(\(% as lua expr), \(%base as lua expr))"} +compile [floor %] to {expr:"math.floor(\(% as lua expr))"} +compile [ceiling %, ceil %] to {expr:"math.ceil(\(% as lua expr))"} +compile [round %, % rounded] to {expr:"math.floor(\(% as lua expr) + .5)"} action [%n to the nearest %rounder] =lua "(\%rounder)*math.floor((\%n / \%rounder) + .5)" # Any/all/none -compile [all of %items, all %items] to - "(\(joined ((% as lua) for all (%items' "value")) with " and "))" - ..if ((%items' "type") is "List") else "utils.all(\(%items as lua))" +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))" parse [not all of %items, not all %items] as: not (all of %items) -compile [any of %items, any %items] to - "(\(joined ((% as lua) for all (%items' "value")) with " or "))" - ..if ((%items' "type") is "List") else "utils.any(\(%items as lua))" +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))" parse [none of %items, none %items] as: not (any of %items) -compile [sum of %items, sum %items] to - "(\(joined ((% as lua) for all (%items' "value")) with " + "))" - ..if ((%items' "type") is "List") else "utils.sum(\(%items as lua))" -compile [product of %items, product %items] to - "(\(joined ((% as lua) for all (%items' "value")) with " * "))" - ..if ((%items' "type") is "List") else "utils.product(\(%items as lua))" +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))" action [avg of %items, average of %items] =lua "(utils.sum(\%items)/#\%items)" -compile [min of %items, smallest of %items, lowest of %items] to - "utils.min(\(%items as lua))" -compile [max of %items, biggest of %items, largest of %items, highest of %items] to - "utils.max(\(%items as lua))" -compile [min of %items by %value_expr] to ".." - utils.min(\(%items as lua), function(\(\% as lua)) - return \(%value_expr as lua) - end) -compile [max of %items by %value_expr] to ".." - utils.max(\(%items as lua), function(\(\% as lua)) - return \(%value_expr as lua) - end) +compile [min of %items, smallest of %items, lowest of %items] to {..} + expr:"utils.min(\(%items as lua expr))" +compile [max of %items, biggest of %items, largest of %items, highest of %items] to {..} + expr:"utils.max(\(%items as lua expr))" +compile [min of %items by %value_expr] to {..} + expr: ".." + utils.min(\(%items as lua expr), function(\(\% as lua expr)) + return \(%value_expr as lua expr) + end) +compile [max of %items by %value_expr] to {..} + expr: ".." + utils.max(\(%items as lua expr), function(\(\% as lua expr)) + return \(%value_expr as lua expr) + end) # Random functions action [seed random with %] @@ -71,9 +77,9 @@ action [seed random with %] math.randomseed(\%); for i=1,20 do math.random(); end parse [seed random] as: seed random with (=lua "os.time()") -compile [random number, random, rand] to "math.random()" -compile [random int %n, random integer %n, randint %n] to "math.random(\(%n as lua))" +compile [random number, random, rand] to {expr:"math.random()"} +compile [random int %n, random integer %n, randint %n] to {expr:"math.random(\(%n as lua expr))"} compile [random from %low to %high, random number from %low to %high, rand %low %high] to - "math.random(\(%low as lua), \(%high as lua))" + "math.random(\(%low as lua expr), \(%high as lua expr))" action [random choice from %elements, random choice %elements, random %elements] =lua "\%elements[math.random(#\%elements)]" diff --git a/lib/metaprogramming.nom b/lib/metaprogramming.nom index e7de602..49e20ee 100644 --- a/lib/metaprogramming.nom +++ b/lib/metaprogramming.nom @@ -2,134 +2,99 @@ This File contains actions for making actions and compile-time actions and some helper functions to make that easier. -# Helper function -immediately - lua> ".." - nomsu.parse_spec = function(nomsu, spec) - if spec.type == 'List' then - local names = {}; - for i, alias in ipairs(spec.value) do - if alias.type == "FunctionCall" then - names[i] = alias.src; - elseif alias.type == "Text" then - names[i] = nomsu:tree_to_value(alias); - end - end - local junk, arg_names, junk = nomsu:get_stub(names[1]); - local args = {}; - for i, a in ipairs(arg_names) do args[i] = nomsu:var_to_lua_identifier(a); end - return names, args; - else - local alias = nomsu:tree_to_value(spec); - local junk, arg_names, junk = nomsu:get_stub(alias); - local args = {}; - for i, a in ipairs(arg_names) do args[i] = nomsu:var_to_lua_identifier(a); end - return {alias}, args; - end - end - # Compile-time action to make compile-time actions: -# TODO: reduce code duplication here immediately lua> ".." - do - local function compile_to(name_tree, body_tree, kind) - local names, args = nomsu:parse_spec(name_tree); - local declared_locals = {}; - for i, arg in ipairs(args) do declared_locals[arg] = true; end - names, args = repr(names), table.concat(args, ", "); - local body_lua = nomsu:tree_to_lua(body_tree); - if body_lua.expr and not body_lua.locals then - return [[ - nomsu:define_compile_action(]]..names..[[, ]]..repr(name_tree:get_line_no())..[[, function(]]..args..[[) - return {]]..kind..[[=]]..body_lua.expr..[[}; - end, ]]..repr(nomsu:source_code())..[[); - ]]; - end - local body_code = body_lua.statements or ("return "..body_lua.expr..";"); - local undeclared_locals = {}; - for i, body_local in ipairs(body_lua.locals or {}) do - if not declared_locals[body_local] then - table.insert(undeclared_locals, body_local); - end - end - if #undeclared_locals > 0 then - body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code; - end - return [[ - do - local function compile_action(]]..args..[[) - ]]..body_code.."\\n"..[[ + nomsu:define_compile_action("compile %actions to %lua", \(!! code location !!), function(\%actions, \%lua) + local signature = {}; + for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end + local stubs = nomsu:get_stubs_from_signature(signature); + local stub_args = nomsu:get_args_from_signature(signature); + local arg_set = {}; + for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end + if \%lua.type == "Text" then + error("Invalid type for 'compile % to %', expected a dict with expr/statements, but got text.", 0); end - nomsu:define_compile_action(]]..names..[[, ]]..repr(name_tree:get_line_no())..[[, function(]]..args..[[) - return {]]..kind..[[=compile_action(]]..args..[[)}; - end, ]]..repr(nomsu:source_code())..[[); - end]]; - end - local src = \(__src__ 1); - nomsu:define_compile_action("compile %names to %body", \(__line_no__), function(\%names, \%body) - return {statements=compile_to(\%names, \%body, "expr")}; - end, src); - nomsu:define_compile_action("compile %names to code %body", \(__line_no__), function(\%names, \%body) - return {statements=compile_to(\%names, \%body, "statements")}; - end, src); - end - -# Compile-time action to make actions -immediately - compile [action %names %body] to code - lua> ".." - local names, args = nomsu:parse_spec(\%names); - local declared_locals = {}; - for i, arg in ipairs(args) do declared_locals[arg] = true; end - names, args = repr(names), table.concat(args, ", "); - local body_lua = nomsu:tree_to_lua(\%body); + local body_lua = nomsu:tree_to_lua(\%lua); local body_code = body_lua.statements or ("return "..body_lua.expr..";"); local undeclared_locals = {}; for i, body_local in ipairs(body_lua.locals or {}) do - if not declared_locals[body_local] then + if not arg_set[body_local] then table.insert(undeclared_locals, body_local); end end if #undeclared_locals > 0 then body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code; end - local src = nomsu:dedent(nomsu:source_code(0)); - local def_lua = ([[ - nomsu:define_action(%s, \(__line_no__), function(%s) - %s - end, %s);]]):format(names, args, body_code, repr(src)); - return def_lua; + local lua_fn_args = table.concat(stub_args[1], ", "); + local def_tree = nomsu.compilestack[#nomsu.compilestack]; + local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop); + return {statements=([[ + nomsu:define_compile_action(]]..repr(signature)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[) + ]]..body_code.."\\n"..[[ + end); + ]])}; + end); + +# Compile-time action to make actions +immediately + compile [action %actions %body] to + lua> ".." + local signature = {}; + for i, action in ipairs(\%actions.value) do signature[i] = action:get_src(); end + local stubs = nomsu:get_stubs_from_signature(signature); + local stub_args = nomsu:get_args_from_signature(signature); + local arg_set = {}; + for i, arg in ipairs(stub_args[1]) do arg_set[arg] = true; end + local body_lua = nomsu:tree_to_lua(\%body); + local body_code = body_lua.statements or ("return "..body_lua.expr..";"); + local undeclared_locals = {}; + for i, body_local in ipairs(body_lua.locals or {}) do + if not arg_set[body_local] then + table.insert(undeclared_locals, body_local); + end + end + if #undeclared_locals > 0 then + body_code = "local "..table.concat(undeclared_locals, ", ")..";\\n"..body_code; + end + local lua_fn_args = table.concat(stub_args[1], ", "); + local def_tree = nomsu.compilestack[#nomsu.compilestack]; + local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop); + return {statements=[[ + nomsu:define_action(]]..repr(signature)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[) + ]]..body_code.."\\n"..[[ + end); + ]]}; # Macro to make nomsu macros: immediately - lua> ".." - nomsu:define_compile_action("parse %shorthand as %longhand", \(__line_no__), (function(\%shorthand, \%longhand) - local names, args = nomsu:parse_spec(\%shorthand); - names, args = repr(names), table.concat(args, ", "); + compile [parse %shorthand as %longhand] to + lua> ".." + local signature = {}; + for i, action in ipairs(\%shorthand.value) do signature[i] = action:get_src(); end + local stubs = nomsu:get_stubs_from_signature(signature); + local stub_args = nomsu:get_args_from_signature(signature); + local lua_fn_args = table.concat(stub_args[1], ", "); local template; if \%longhand.type == "Block" then - template = {}; - for i, line in ipairs(\%longhand.value) do - template[i] = nomsu:dedent(line.src); - end - template = repr(table.concat(template, "\\n")); + local lines = {}; + for i, line in ipairs(\%longhand.value) do lines[i] = nomsu:dedent(line:get_src()); end + template = repr(table.concat(lines, "\\n")); else - template = repr(nomsu:dedent(\%longhand.src)); + template = repr(nomsu:dedent(\%longhand:get_src())); end - local junk, arg_names, junk = nomsu:get_stub(\%shorthand.value[1]); local replacements = {}; - for i, a in ipairs(arg_names) do replacements[i] = "["..repr(a).."]="..nomsu:var_to_lua_identifier(a); end + for i, a in ipairs(stub_args[1]) do replacements[i] = a.."="..a; end replacements = "{"..table.concat(replacements, ", ").."}"; - local lua_code = ([[ - nomsu:define_compile_action(%s, %s, (function(%s) - local template = nomsu:parse(%s, %s); - local replacement = nomsu:tree_with_replaced_vars(template, %s); - return nomsu:tree_to_lua(replacement); - end), %s)]]):format(names, repr(\%shorthand:get_line_no()), args, template, - repr(\%shorthand:get_line_no()), replacements, repr(nomsu:source_code(0))); - return {statements=lua_code}; - end), \(__src__ 1)); + local def_tree = nomsu.compilestack[#nomsu.compilestack]; + local code_location = ("%s:%s,%s"):format(def_tree.filename, def_tree.start, def_tree.stop); + return {statements=[[ + nomsu:define_compile_action(]]..repr(signature)..[[, ]]..repr(code_location)..[[, function(]]..lua_fn_args..[[) + local template = nomsu:parse(]]..template..[[, ]]..repr(def_tree.filename)..[[); + local replacement = nomsu:tree_with_replaced_vars(template, ]]..replacements..[[); + return nomsu:tree_to_lua(replacement); + end); + ]]}; action [remove action %stub] lua> ".." @@ -143,33 +108,41 @@ action [remove action %stub] immediately action [%tree as lua] - =lua "nomsu:tree_to_lua(\%tree).expr" + =lua "nomsu:tree_to_lua(\%tree)" + + action [%tree as lua expr] + lua> ".." + local lua = nomsu:tree_to_lua(\%tree); + if lua.locals or not lua.expr then + error("Invalid thing to convert to lua expr: "..\%tree:get_src()); + end + return lua.expr; + action [%tree as lua statements] lua> ".." local lua = nomsu:tree_to_lua(\%tree); - return lua.statements or (lua.expr..";"); + local code = lua.statements or (lua.expr..";"); + if lua.locals then + code = "local "..table.concat(lua.locals, ", ")..";\\n"..code; + end + return code; + action [%tree as value] =lua "nomsu:tree_to_value(\%tree)" - compile [repr %obj] to - "repr(\(%obj as lua))" - compile [type of %obj] to - "type(\(%obj as lua))" - + immediately - parse [lua do> %block] as - lua> "do" - lua> %block - lua> "end" + compile [%tree's source code, %tree' source code] to {expr:"\(%tree as lua expr):get_src()"} -compile [nomsu] to "nomsu" + compile [repr %obj] to {expr:"repr(\(%obj as lua expr))"} + compile [type of %obj] to {expr:"type(\(%obj as lua expr))"} -compile [nomsu's %key] to "nomsu[\(%key as lua)]" -compile [nomsu %method %args] to "nomsu[\(%method as lua)](nomsu, unpack(\(%args as lua)))" -compile [tree %tree with %replacements] to ".." - nomsu:tree_with_replaced_vars(\(%tree as lua), \(%replacements as lua)) +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)))"} action [action %names metadata] - =lua "ACTION_METADATA[ACTIONS[\%names]]" + =lua "ACTION_METADATA[ACTIONS[\%names]]" # Get the source code for a function action [help %action] @@ -186,22 +159,34 @@ parse [run %code] as: nomsu "run" [%code] parse [enable debugging] as: lua> "nomsu.debug = true" parse [disable debugging] as: lua> "nomsu.debug = false" -compile [say %message] to - lua> ".." - if \%message.type == "Text" then - return "nomsu:writeln("..\(%message as lua)..")"; - else - return "nomsu:writeln(stringify("..\(%message as lua).."))"; - end +immediately + compile [say %message] to + lua> ".." + if \%message.type == "Text" then + return {statements="nomsu:writeln("..\(%message as lua expr)..");"}; + else + return {statements="nomsu:writeln(stringify("..\(%message as lua expr).."));"}; + end + +# Return +immediately + #.. Return statement is wrapped in a do..end block because Lua is unhappy if you + put code after a return statement, unless you wrap it in a block. + compile [return] to {statements:"do return; end"} + compile [return %return_value] to {statements:"do return \(%return_value as lua expr); end"} # Error functions -compile [barf!] to "error(nil, 0)" -compile [barf %msg] to "error(\(%msg as lua), 0)" -compile [assume %condition] to "assert(\(%condition as lua))" -compile [assume %condition or barf %msg] to "assert(\(%condition as lua), \(%msg as lua))" +immediately + 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 or barf %msg] to {..} + statements:"if not \(%condition as lua expr) then error(\(%msg as lua expr), 0); end" # Literals -compile [yes] to "true" -compile [no] to "false" -compile [nothing, nil, null] to "nil" +immediately + compile [yes] to {expr:"true"} + compile [no] to {expr:"false"} + compile [nothing, nil, null] to {expr:"nil"} diff --git a/lib/operators.nom b/lib/operators.nom index 32e29db..8be0ea0 100644 --- a/lib/operators.nom +++ b/lib/operators.nom @@ -16,138 +16,131 @@ immediately %key st in %obj, %key nd in %obj, %key rd in %obj, %key th in %obj, ..to lua> ".." - local obj_lua = \(%obj as lua); + local obj_lua = \(%obj as lua expr); if not obj_lua:sub(-1,-1):match("[a-zA-Z)]") then obj_lua = "("..obj_lua..")"; end - local key_lua = \(%key as lua); + local key_lua = \(%key as lua expr); local key_attr = (key_lua:match("'([a-zA-Z][a-zA-Z0-9]*)'") or key_lua:match('"([a-zA-Z][a-zA-Z0-9]*)"')); if key_attr then - return obj_lua.."."..key_attr; + return {expr=obj_lua.."."..key_attr}; elseif key_lua:sub(1,1) == "[" then key_lua = " "..key_lua.." "; end - return obj_lua.."["..key_lua.."]"; + return {expr=obj_lua.."["..key_lua.."]"}; # Comparison Operators immediately - compile [%x < %y] to "(\(%x as lua) < \(%y as lua))" - compile [%x > %y] to "(\(%x as lua) > \(%y as lua))" - compile [%x <= %y] to "(\(%x as lua) <= \(%y as lua))" - compile [%x >= %y] to "(\(%x as lua) >= \(%y as lua))" + 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))"} + compile [%x >= %y] to {expr:"(\(%x as lua expr) >= \(%y as lua expr))"} compile [%a is %b, %a = %b, %a == %b] to lua> ".." local safe = {Text=true, Number=true}; local a_lua, b_lua = nomsu:tree_to_lua(\%a).expr, nomsu:tree_to_lua(\%b).expr; if safe[\%a.type] or safe[\%b.type] then - return "("..a_lua.." == "..b_lua..")"; + return {expr="("..a_lua.." == "..b_lua..")"}; else - return "utils.equivalent("..a_lua..", "..b_lua..")"; + return {expr="utils.equivalent("..a_lua..", "..b_lua..")"}; end compile [%a isn't %b, %a is not %b, %a not= %b, %a != %b] to lua> ".." local safe = {Text=true, Number=true}; local a_lua, b_lua = nomsu:tree_to_lua(\%a).expr, nomsu:tree_to_lua(\%b).expr; if safe[\%a.type] or safe[\%b.type] then - return "("..a_lua.." ~= "..b_lua..")"; + return {expr="("..a_lua.." ~= "..b_lua..")"}; else - return "(not utils.equivalent("..a_lua..", "..b_lua.."))"; + return {expr="(not utils.equivalent("..a_lua..", "..b_lua.."))"}; end # For strict identity checking, use (%x's id) is (%y's id) - compile [%'s id, id of %] to "nomsu.ids[\(% as lua)]" + compile [%'s id, id of %] to {expr:"nomsu.ids[\(% as lua expr)]"} # Variable assignment operator immediately - lua> ".." - nomsu:define_compile_action("%var <- %value", \(__line_no__), function(\%var, \%value) - local lua = {}; - lua.statements = ("%s = %s;"):format( - assert(nomsu:tree_to_lua(\%var).expr, "Invalid target for assignment: "..\%var.src), - assert(nomsu:tree_to_lua(\%value).expr, "Invalid value for assignment: "..\%value.src)); - if \%var.type == "Var" then - lua.locals = {nomsu:tree_to_lua(\%var).expr}; - end - return lua; - end, \(__src__ 1)); + compile [%var <- %value] to + lua> "local \%var_lua = nomsu:tree_to_lua(\%var);" + assume (%var_lua's "expr") or barf "Invalid target for assignment: \(%var's source code)" + lua> "local \%value_lua = nomsu:tree_to_lua(\%value);" + 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");" + locals: =lua "(\%var.type == 'Var' and {\%var_lua.expr} or nil)" - lua> ".." - nomsu:define_compile_action("export %var <- %value", \(__line_no__), function(\%var, \%value) - local lua = {}; - lua.statements = ("%s = %s;"):format( - assert(nomsu:tree_to_lua(\%var).expr, "Invalid target for assignment: "..\%var.src), - assert(nomsu:tree_to_lua(\%value).expr, "Invalid value for assignment: "..\%value.src)); - return lua; - end, \(__src__ 1)); +immediately + compile [export %var <- %value] to + %var_lua <- (%var as lua) + assume (%var_lua's "expr") or barf "Invalid target for assignment: \(%var's source code)" + %value_lua <- (%value as lua) + 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");"} - lua> ".." - nomsu:define_compile_action("with %assignments %body", \(__line_no__), function(\%assignments, \%body) - local body_lua = nomsu:tree_to_lua(\%body); - local declarations = ""; - local leftover_locals = {}; - for _, body_local in ipairs(body_lua.locals or {}) do - leftover_locals[body_local] = true; - end - assert(\%assignments.type == "List", - "Expected a List for the assignments part of 'with' statement, not "..\%assignments.src); + compile [with %assignments %body] to + %body_lua <- (%body as lua) + %locals <- [] + %declarations <- [] + %leftover_locals <- (=lua "{unpack(\%body_lua.locals or {})}") + assume ((%assignments' "type") is "List") or barf ".." + Expected a List for the assignments part of 'with' statement, not \(%assignments' source code) + lua> ".." for i, item in ipairs(\%assignments.value) do if item.type == "Var" then local var = nomsu:tree_to_lua(item).expr; - leftover_locals[var] = nil; - declarations = declarations.."local "..var..";\\n "; + utils.remove_from_list(\%leftover_locals, var); + table.insert(\%locals, var); else - assert(item.type == "FunctionCall" and #item.value == 3 and item.value[2].src == "<-", - "'with' statement expects entries of the form: '%var <- %value', not: "..item.src); - local target, value = item.value[1], item.value[3]; - if target.type == "Var" then - local var = nomsu:tree_to_lua(target).expr; - leftover_locals[var] = nil; - declarations = declarations..(("local %s = %s;\\n "):format( - var, assert(nomsu:tree_to_lua(value).expr, "Invalid value for assignment: "..value.src))); - else - declarations = declarations..(("%s = %s;\\n "):format( - assert(nomsu:tree_to_lua(target).expr, "Invalid target for assignment: "..target.src), - assert(nomsu:tree_to_lua(value).expr, "Invalid value for assignment: "..value.src))); + if not (item.type == "FunctionCall" and #item.value == 3 and item.value[2].value == "<-") then + error("'with' statement expects entries of the form: '%var <- %value', not: "..item:get_src()); end + local target, value = item.value[1], item.value[3]; + 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 + utils.remove_from_list(\%leftover_locals, target_lua.expr); + end + table.insert(\%declarations, (("local %s = %s;\\n "):format( + target_lua.expr, value_lua.expr))); end end - local code = ([[ - do - %s%s - end]]):format(declarations, body_lua.statements or (body_lua.expr..";")); - return {statements=code, locals=utils.keys(leftover_locals)}; - end, \(__src__ 1)); + local locals_code = ""; + if #\%locals > 0 then + locals_code = "\\nlocal "..table.concat(\%locals, ", ")..";"; + end + local declaration_code = ""; + if #\%declarations > 0 then + declaration_code = "\\n"..table.concat(\%declarations, "\\n"); + end + return {locals=\%leftover_locals, statements=([[ + do%s%s + %s + end]]):format(locals_code, declaration_code, \%body_lua.statements or (\%body_lua.expr..";"))}; - lua> ".." - nomsu:define_compile_action("exporting %exported %body", \(__line_no__), function(\%exported, \%body) - local body_lua = nomsu:tree_to_lua(\%body); - local declarations = ""; - local leftover_locals = {}; - for _, body_local in ipairs(body_lua.locals or {}) do - leftover_locals[body_local] = true; - end - assert(\%exported.type == "List", - "Expected a List for the export part of 'exporting' statement, not "..\%exported.src); + 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 - assert(item.type == "Var", "exporting statement expects Vars, not: "..item.src); + if item.type ~= "Var" then + error("'exporting' statement expects Vars, not: "..item:get_src()); + end local var = nomsu:tree_to_lua(item).expr; - leftover_locals[var] = nil; + utils.remove_from_list(leftover_locals, var); end - local code = ([[ - do - %s%s - end]]):format(declarations, body_lua.statements or (body_lua.expr..";")); - return {statements=code, locals=utils.keys(leftover_locals)}; - end, \(__src__ 1)); + return {locals:%leftover_locals, statements:=lua "\%body_lua.statements or (\%body_lua.expr..';')"} immediately # Math Operators - compile [%x + %y] to "(\(%x as lua) + \(%y as lua))" - compile [%x - %y] to "(\(%x as lua) - \(%y as lua))" - compile [%x * %y] to "(\(%x as lua) * \(%y as lua))" - compile [%x / %y] to "(\(%x as lua) / \(%y as lua))" - compile [%x ^ %y] to "(\(%x as lua) ^ \(%y as lua))" - compile [%x wrapped around %y, %x mod %y] to "(\(%x as lua) % \(%y as lua))" + 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))"} + 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 wrapped around %y, %x mod %y] to {expr:"(\(%x as lua expr) % \(%y as lua expr))"} # 3-part chained comparisons # (uses a lambda to avoid re-evaluating middle value, while still being an expression) @@ -162,22 +155,22 @@ immediately # TODO: optimize for common case where x,y,z are all either variables or number literals # Boolean Operators - compile [%x and %y] to "(\(%x as lua) and \(%y as lua))" - compile [%x or %y] to "(\(%x as lua) or \(%y as lua))" + compile [%x and %y] to {expr:"(\(%x as lua expr) and \(%y as lua expr))"} + compile [%x or %y] to {expr:"(\(%x as lua expr) or \(%y as lua expr))"} # Bitwise Operators - compile [%a OR %b, %a | %b] to "bit32.bor(\(%a as lua), \(%b as lua))" - compile [%a XOR %b] to "bit32.bxor(\(%a as lua), \(%b as lua))" - compile [%a AND %b, %a & %b] to "bit32.band(\(%a as lua), \(%b as lua))" - compile [NOT %, ~ %] to "bit32.bnot(\(% as lua))" - compile [%x LSHIFT %shift, %x << %shift] to "bit32.lshift(\(%x as lua), \(%shift as lua))" - compile [%x RSHIFT %shift, %x >>> %shift] to "bit32.rshift(\(%x as lua), \(%shift as lua))" - compile [%x ARSHIFT %shift, %x >> %shift] to "bit32.arshift(\(%x as lua), \(%shift as lua))" + compile [%a OR %b, %a | %b] to {expr:"bit32.bor(\(%a as lua expr), \(%b as lua expr))"} + compile [%a XOR %b] to {expr:"bit32.bxor(\(%a as lua expr), \(%b as lua expr))"} + compile [%a AND %b, %a & %b] to {expr:"bit32.band(\(%a as lua expr), \(%b as lua expr))"} + compile [NOT %, ~ %] to {expr:"bit32.bnot(\(% as lua expr))"} + compile [%x LSHIFT %shift, %x << %shift] to {expr:"bit32.lshift(\(%x as lua expr), \(%shift as lua expr))"} + compile [%x RSHIFT %shift, %x >>> %shift] to {expr:"bit32.rshift(\(%x as lua expr), \(%shift as lua expr))"} + compile [%x ARSHIFT %shift, %x >> %shift] to {expr:"bit32.arshift(\(%x as lua expr), \(%shift as lua expr))"} # TODO: implement OR, XOR, AND for multiple operands? # Unary operators - compile [- %] to "(- \(% as lua))" - compile [not %] to "(not \(% as lua))" + compile [- %] to {expr:"(- \(% as lua expr))"} + compile [not %] to {expr:"(not \(% as lua expr))"} # Update operators immediately @@ -188,4 +181,4 @@ immediately parse [<- %var ^ %] as: %var <- (%var ^ %) parse [<- %var and %] as: %var <- (%var and %) parse [<- %var or %] as: %var <- (%var or %) - parse [wrap %var around %] as: "\(%var as lua) = \(%var as lua) % \(% as lua);" + parse [wrap %var around %] as: %var <- (%var wrapped around %) diff --git a/lib/text.nom b/lib/text.nom index 0bd7a8a..982db63 100644 --- a/lib/text.nom +++ b/lib/text.nom @@ -13,41 +13,43 @@ action [%texts joined with %glue] parse [joined %texts, %texts joined] as: %texts joined with "" compile [capitalized %text capitalized] to - "(\(%text as lua)):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 - "((\(%text as lua)):gsub(\(%patt as lua), \(%sub as lua)))" + {expr:"((\(%text as lua expr)):gsub(\(%patt as lua expr), \(%sub as lua expr)))"} -compile [indented %text, %text indented] to "\%text:gsub('\\n','\\n'..(' '))" -compile [dedented %obj, %obj dedented] to "nomsu:dedent(\(%obj as lua))" -compile [%text indented %n times] to "\%text:gsub('\\n','\\n'..(' '):rep(\%n))" +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))"} # Text literals -lua do> ".." - local escapes = { - nl="\\\\n", newline="\\\\n", tab="\\\\t", bell="\\\\a", cr="\\\\r", ["carriage return"]="\\\\r", - backspace="\\\\b", ["form feed"]="\\\\f", formfeed="\\\\f", ["vertical tab"]="\\\\v", - }; - local colors = { - ["reset color"]="\\\\27[0m", bright="\\\\27[1m", dim="\\\\27[2m", underscore="\\\\27[4m", - blink="\\\\27[5m", inverse="\\\\27[7m", hidden="\\\\27[8m", +lua> ".." + do + local escapes = { + nl="\\\\n", newline="\\\\n", tab="\\\\t", bell="\\\\a", cr="\\\\r", ["carriage return"]="\\\\r", + backspace="\\\\b", ["form feed"]="\\\\f", formfeed="\\\\f", ["vertical tab"]="\\\\v", + }; + for name, e in pairs(escapes) do + local lua = "'"..e.."'"; + nomsu:define_compile_action(name, \(!! code location !!), function() return {expr=text}; end); + end + local colors = { + ["reset color"]="\\\\27[0m", bright="\\\\27[1m", dim="\\\\27[2m", underscore="\\\\27[4m", + blink="\\\\27[5m", inverse="\\\\27[7m", hidden="\\\\27[8m", - black="\\\\27[30m", red="\\\\27[31m", green="\\\\27[32m", yellow="\\\\27[33m", blue="\\\\27[34m", - magenta="\\\\27[35m", cyan="\\\\27[36m", white="\\\\27[37m", + black="\\\\27[30m", red="\\\\27[31m", green="\\\\27[32m", yellow="\\\\27[33m", blue="\\\\27[34m", + magenta="\\\\27[35m", cyan="\\\\27[36m", white="\\\\27[37m", - ["on black"]="\\\\27[40m", ["on red"]="\\\\27[41m", ["on green"]="\\\\27[42m", ["on yellow"]="\\\\27[43m", - ["on blue"]="\\\\27[44m", ["on magenta"]="\\\\27[45m", ["on cyan"]="\\\\27[46m", ["on white"]="\\\\27[47m", - }; - for name, e in pairs(escapes) do - local lua = "'"..e.."'"; - nomsu:define_compile_action(name, \(__line_no__), function() return {expr=text}; end, \(__src__ 1)); - end - for name, c in pairs(colors) do - local color = "'"..c.."'"; - local reset = "'"..colors["reset color"].."'"; - nomsu:define_compile_action(name, \(__line_no__), function() return {expr=color}; end, \(__src__ 1)); - nomsu:define_compile_action(name.." %", \(__line_no__), function(\%) - return {expr=color..".."..nomsu:tree_to_lua(\%).expr..".."..reset}; - end, \(__src__ 1)); + ["on black"]="\\\\27[40m", ["on red"]="\\\\27[41m", ["on green"]="\\\\27[42m", ["on yellow"]="\\\\27[43m", + ["on blue"]="\\\\27[44m", ["on magenta"]="\\\\27[45m", ["on cyan"]="\\\\27[46m", ["on white"]="\\\\27[47m", + }; + for name, c in pairs(colors) do + local color = "'"..c.."'"; + local reset = "'"..colors["reset color"].."'"; + nomsu:define_compile_action(name, \(!! code location !!), function() return {expr=color}; end); + nomsu:define_compile_action(name.." %", \(!! code location !!), function(\%) + return {expr=color..".."..nomsu:tree_to_lua(\%).expr..".."..reset}; + end); + end end diff --git a/nomsu.lua b/nomsu.lua index 282831c..5689426 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -35,9 +35,6 @@ do return string[i] end end - STRING_METATABLE.__mul = function(self, other) - return string.rep(self, other) - end end lpeg.setmaxstack(10000) local P, R, V, S, Cg, C, Cp, B, Cmt @@ -45,7 +42,7 @@ P, R, V, S, Cg, C, Cp, B, Cmt = lpeg.P, lpeg.R, lpeg.V, lpeg.S, lpeg.Cg, lpeg.C, local NOMSU_DEFS do local _with_0 = { } - _with_0.nl = P("\n") + _with_0.nl = P("\r") ^ -1 * P("\n") _with_0.ws = S(" \t") _with_0.tonumber = tonumber _with_0.print = function(src, pos, msg) @@ -100,8 +97,8 @@ do end end) _with_0.error = function(src, pos, err_msg) - if lpeg.userdata.source_code:sub(pos, pos) == "\n" then - pos = pos + #lpeg.userdata.source_code:match("[ \t\n]*", pos) + if lpeg.userdata.source_code:sub(pos, pos):match("[\r\n]") then + pos = pos + #lpeg.userdata.source_code:match("[ \t\n\r]*", pos) end local line_no = 1 while (lpeg.userdata.line_starts[line_no + 1] or math.huge) < pos do @@ -109,14 +106,14 @@ do end local prev_line if line_no > 1 then - prev_line = lpeg.userdata.source_code:match("[^\n]*", lpeg.userdata.line_starts[line_no - 1]) + prev_line = lpeg.userdata.source_code:match("[^\r\n]*", lpeg.userdata.line_starts[line_no - 1]) else prev_line = "" end - local err_line = lpeg.userdata.source_code:match("[^\n]*", lpeg.userdata.line_starts[line_no]) + local err_line = lpeg.userdata.source_code:match("[^\r\n]*", lpeg.userdata.line_starts[line_no]) local next_line if line_no < #lpeg.userdata.line_starts then - next_line = lpeg.userdata.source_code:match("[^\n]*", lpeg.userdata.line_starts[line_no + 1]) + next_line = lpeg.userdata.source_code:match("[^\r\n]*", lpeg.userdata.line_starts[line_no + 1]) else next_line = "" end @@ -138,13 +135,14 @@ do end)(), " ") local src = lpeg.userdata.source_code:sub(start, stop - 1) return { + type = "FunctionCall", start = start, stop = stop, - type = "FunctionCall", - src = src, - get_line_no = lpeg.userdata.get_line_no, value = value, - stub = stub + stub = stub, + filename = lpeg.userdata.filename, + get_line_no = lpeg.userdata.get_line_no, + get_src = lpeg.userdata.get_src } end NOMSU_DEFS = _with_0 @@ -154,12 +152,13 @@ setmetatable(NOMSU_DEFS, { local make_node make_node = function(start, value, stop) return { + type = key, start = start, stop = stop, value = value, - src = lpeg.userdata.source_code:sub(start, stop - 1), - get_line_no = lpeg.userdata.get_line_no, - type = key + filename = lpeg.userdata.filename, + get_src = lpeg.userdata.get_src, + get_line_no = lpeg.userdata.get_line_no } end self[key] = make_node @@ -183,28 +182,28 @@ end local NomsuCompiler do local _class_0 + local line_counter, stub_defs, stub_pattern, var_pattern local _base_0 = { writeln = function(self, ...) self:write(...) return self:write("\n") end, - errorln = function(self, ...) - self:write_err(...) - return self:write_err("\n") - end, - define_action = function(self, signature, line_no, fn, src, compile_time) - if compile_time == nil then - compile_time = false + define_action = function(self, signature, source, fn) + if self.debug then + self:writeln(tostring(colored.bright("DEFINING ACTION:")) .. " " .. tostring(colored.green(repr(signature)))) + end + if type(fn) ~= 'function' then + error('function', "Bad fn: " .. tostring(repr(fn))) end if type(signature) == 'string' then - signature = self:get_stubs({ + signature = { signature - }) - elseif type(signature) == 'table' and type(signature[1]) == 'string' then - signature = self:get_stubs(signature) + } + elseif type(signature) ~= 'table' or signature.type ~= nil then + error("Invalid signature, expected list of strings, but got: " .. tostring(repr(signature)), 0) end - assert(type(fn) == 'function', "Bad fn: " .. tostring(repr(fn))) - local aliases = { } + local stubs = self:get_stubs_from_signature(signature) + local stub_args = self:get_args_from_signature(signature) self.__class.def_number = self.__class.def_number + 1 local fn_info = debug.getinfo(fn, "u") local fn_arg_positions, arg_orders @@ -218,11 +217,10 @@ do end arg_orders = { } end - for sig_i = 1, #signature do - local stub, arg_names = unpack(signature[sig_i]) - assert(stub, "NO STUB FOUND: " .. tostring(repr(signature))) + for sig_i = 1, #stubs do + local stub, args = stubs[sig_i], stub_args[sig_i] if self.debug then - self:writeln(tostring(colored.bright("DEFINING ACTION:")) .. " " .. tostring(colored.underscore(colored.magenta(repr(stub)))) .. " " .. tostring(colored.bright("WITH ARGS")) .. " " .. tostring(colored.dim(repr(arg_names))) .. " ON: " .. tostring(self.environment.ACTIONS)) + self:writeln(tostring(colored.bright("ALIAS:")) .. " " .. tostring(colored.underscore(colored.magenta(repr(stub)))) .. " " .. tostring(colored.bright("WITH ARGS")) .. " " .. tostring(colored.dim(repr(args))) .. " ON: " .. tostring(self.environment.ACTIONS)) end self.environment.ACTIONS[stub] = fn if not (fn_info.isvararg) then @@ -230,33 +228,31 @@ do do local _accum_0 = { } local _len_0 = 1 - for _index_0 = 1, #arg_names do - local a = arg_names[_index_0] - _accum_0[_len_0] = fn_arg_positions[self:var_to_lua_identifier(a)] + for _index_0 = 1, #args do + local a = args[_index_0] + _accum_0[_len_0] = fn_arg_positions[a] _len_0 = _len_0 + 1 end arg_positions = _accum_0 end - assert(#arg_positions == #arg_names, "Mismatch in args between lua function's " .. tostring(repr(fn_arg_positions)) .. " and stub's " .. tostring(repr(arg_names))) + if #arg_positions ~= #args then + error("Mismatch in args between lua function's " .. tostring(repr(fn_arg_positions)) .. " and stub's " .. tostring(repr(args)) .. " for " .. tostring(repr(stub)), 0) + end arg_orders[stub] = arg_positions end end self.action_metadata[fn] = { fn = fn, - src = src, - line_no = line_no, - aliases = aliases, + source = source, + aliases = stubs, arg_orders = arg_orders, arg_positions = fn_arg_positions, def_number = self.__class.def_number } end, - define_compile_action = function(self, signature, line_no, fn, src) - self:define_action(signature, line_no, fn, src, true) + define_compile_action = function(self, signature, source, fn, src) + self:define_action(signature, source, fn) self.action_metadata[fn].compile_time = true - if self.debug then - return self:writeln(tostring(colored.bright(colored.green("(it was compile time)")))) - end end, serialize_defs = function(self, scope, after) if scope == nil then @@ -265,68 +261,7 @@ do if after == nil then after = nil end - error("Not currently functional.") - after = after or (self.core_defs or 0) - scope = scope or self.defs - local defs_by_num = { } - for stub, def in pairs(scope) do - if def and stub:sub(1, 1) ~= "#" then - defs_by_num[def.def_number] = def - end - end - local keys - do - local _accum_0 = { } - local _len_0 = 1 - for k, v in pairs(defs_by_num) do - _accum_0[_len_0] = k - _len_0 = _len_0 + 1 - end - keys = _accum_0 - end - table.sort(keys) - local buff = { } - local k_i = 1 - local _using = nil - local _using_do = { } - for k_i, i in ipairs(keys) do - local _continue_0 = false - repeat - if i <= after then - _continue_0 = true - break - end - local def = defs_by_num[i] - if def.defs == scope then - if def.src then - insert(buff, def.src) - end - _continue_0 = true - break - end - if _using == def.defs then - if def.src then - insert(_using_do, def.src) - end - else - _using = def.defs - _using_do = { - def.src - } - end - if k_i == #keys or defs_by_num[keys[k_i + 1]].defs ~= _using then - insert(buff, "using:\n " .. tostring(self:indent(self:serialize_defs(_using))) .. "\n..do:\n " .. tostring(self:indent(concat(_using_do, "\n")))) - end - _continue_0 = true - until true - if not _continue_0 then - break - end - end - for k, v in pairs(scope["#vars"] or { }) do - insert(buff, "<%" .. tostring(k) .. "> = " .. tostring(self:value_to_nomsu(v))) - end - return concat(buff, "\n") + return error("Not currently functional.", 0) end, dedent = function(self, code) if not (code:find("\n")) then @@ -336,7 +271,7 @@ do for line in code:gmatch("\n([^\n]*)") do local _continue_0 = false repeat - if line:match("^%s*#.*") then + if line:match("^%s*#.*") or line:match("^%s*$") then _continue_0 = true break else @@ -362,8 +297,10 @@ do end if spaces ~= math.huge and spaces < indent_spaces then return (code:gsub("\n" .. (" "):rep(spaces), "\n")) - else + elseif indent_spaces ~= math.huge then return (code:gsub("\n" .. (" "):rep(indent_spaces), "\n ")) + else + return code end end, indent = function(self, code, levels) @@ -377,7 +314,6 @@ do if self.debug then self:writeln(tostring(colored.bright("PARSING:")) .. "\n" .. tostring(colored.yellow(nomsu_code))) end - nomsu_code = nomsu_code:gsub("\r", "") local userdata do local _with_0 = { @@ -387,11 +323,14 @@ do 0 } } - _with_0.line_starts = re.compile("lines <- {| line ('\n' line)* |} line <- {} [^\n]*"):match(nomsu_code) + _with_0.get_src = function(self) + return nomsu_code:sub(self.start, self.stop - 1) + end + _with_0.line_starts = line_counter:match(_with_0.source_code) _with_0.get_line_no = function(self) if not (self._line_no) then local line_no = 1 - while (_with_0.line_starts[line_no + 1] or math.huge) < self.start do + while line_no < #_with_0.line_starts and _with_0.line_starts[line_no + 1] < self.start do line_no = line_no + 1 end self._line_no = tostring(_with_0.filename) .. ":" .. tostring(line_no) @@ -425,7 +364,7 @@ do local timeout timeout = function() debug.sethook() - return error("Execution quota exceeded. Your code took too long.") + return error("Execution quota exceeded. Your code took too long.", 0) end debug.sethook(timeout, "", max_operations) end @@ -434,9 +373,8 @@ do assert(tree.type == "File", "Attempt to run non-file: " .. tostring(tree.type)) local lua = self:tree_to_lua(tree) local lua_code = lua.statements or (lua.expr .. ";") - local locals = lua_code.locals or { } - if #locals > 0 then - lua_code = "local " .. concat(locals, ", ") .. ";\n" .. lua_code + if lua_code.locals and #lua_code.locals > 0 then + lua_code = "local " .. concat(lua_code.locals, ", ") .. ";\n" .. lua_code end lua_code = "-- File: " .. tostring(filename) .. "\n" .. lua_code local ret = self:run_lua(lua_code) @@ -466,13 +404,13 @@ do end local file = file or io.open(filename) if not file then - error("File does not exist: " .. tostring(filename)) + error("File does not exist: " .. tostring(filename), 0) end local nomsu_code = file:read('*a') file:close() return self:run(nomsu_code, filename) else - return error("Invalid filetype for " .. tostring(filename)) + return error("Invalid filetype for " .. tostring(filename), 0) end end, require_file = function(self, filename) @@ -495,18 +433,21 @@ do return ("\n%-3d|"):format(n) end local code = "1 |" .. lua_code:gsub("\n", fn) - error("Failed to compile generated code:\n" .. tostring(colored.bright(colored.blue(colored.onblack(code)))) .. "\n\n" .. tostring(err)) + error("Failed to compile generated code:\n" .. tostring(colored.bright(colored.blue(colored.onblack(code)))) .. "\n\n" .. tostring(err), 0) end return run_lua_fn() end, tree_to_value = function(self, tree, filename) + if tree.type == 'Text' and #tree.value == 1 and type(tree.value[1]) == 'string' then + return tree.value[1] + end local code = "return " .. tostring(self:tree_to_lua(tree).expr) .. ";" if self.debug then self:writeln(tostring(colored.bright("RUNNING LUA TO GET VALUE:")) .. "\n" .. tostring(colored.blue(colored.bright(code)))) end local lua_thunk, err = load(code, nil, nil, self.environment) if not lua_thunk then - error("Failed to compile generated code:\n" .. tostring(colored.bright(colored.blue(colored.onblack(code)))) .. "\n\n" .. tostring(colored.red(err))) + error("Failed to compile generated code:\n" .. tostring(colored.bright(colored.blue(colored.onblack(code)))) .. "\n\n" .. tostring(colored.red(err)), 0) end return lua_thunk() end, @@ -828,7 +769,7 @@ do for _index_0 = 1, #_list_0 do local line = _list_0[_index_0] nomsu = expression(line) - assert(nomsu, "Failed to produce output for:\n" .. tostring(colored.yellow(line.src))) + assert(nomsu, "Failed to produce output for:\n" .. tostring(colored.yellow(line:get_src()))) insert(lines, nomsu) end return concat(lines, "\n") @@ -884,13 +825,13 @@ do return '".."\n ' .. (self:indent(value)) end else - return error("Unsupported value_to_nomsu type: " .. tostring(type(value))) + return error("Unsupported value_to_nomsu type: " .. tostring(type(value)), 0) end end, tree_to_lua = function(self, tree) assert(tree, "No tree provided.") if not tree.type then - error("Invalid tree: " .. tostring(repr(tree))) + error("Invalid tree: " .. tostring(repr(tree)), 0) end local _exp_0 = tree.type if "File" == _exp_0 then @@ -899,12 +840,13 @@ do end local declared_locals = { } local lua_bits = { } + local line_no = 1 local _list_0 = tree.value for _index_0 = 1, #_list_0 do local line = _list_0[_index_0] local lua = self:tree_to_lua(line) if not lua then - error("No lua produced by " .. tostring(repr(line))) + error("No lua produced by " .. tostring(repr(line)), 0) end if lua.locals then local new_locals @@ -944,7 +886,7 @@ do } elseif "Nomsu" == _exp_0 then return { - expr = "nomsu:parse(" .. tostring(repr(tree.value.src)) .. ", " .. tostring(repr(tree:get_line_no())) .. ").value[1]" + expr = "nomsu:parse(" .. tostring(repr(tree.value:get_src())) .. ", " .. tostring(repr(tree:get_line_no())) .. ").value[1]" } elseif "Block" == _exp_0 then local lua_bits = { } @@ -971,9 +913,10 @@ do insert(lua_bits, tostring(lua.expr) .. ";") end end + utils.deduplicate(locals) return { statements = concat(lua_bits, "\n"), - locals = (next(locals) and utils.keys(locals) or nil) + locals = (#locals > 0 and locals or nil) } elseif "FunctionCall" == _exp_0 then insert(self.compilestack, tree) @@ -1034,7 +977,7 @@ do insert(bits, tok.value) else local lua = self:tree_to_lua(tok) - assert(lua.expr, "non-expression value inside math expression: " .. tostring(tok.src)) + assert(lua.expr, "non-expression value inside math expression: " .. tostring(tok:get_src())) insert(bits, lua.expr) end end @@ -1054,7 +997,7 @@ do break end local lua = self:tree_to_lua(tok) - assert(lua.expr, tostring(tree:get_line_no()) .. ": Cannot use:\n" .. tostring(colored.yellow(tok.src)) .. "\nas an argument to " .. tostring(tree.stub) .. ", since it's not an expression, it produces: " .. tostring(repr(lua))) + assert(lua.expr, tostring(tree:get_line_no()) .. ": Cannot use:\n" .. tostring(colored.yellow(tok:get_src())) .. "\nas an argument to " .. tostring(tree.stub) .. ", since it's not an expression, it produces: " .. tostring(repr(lua))) insert(args, lua.expr) _continue_0 = true until true @@ -1104,7 +1047,7 @@ do self:print_tree(bit) self:writeln(tostring(colored.bright("EXPR:")) .. " " .. tostring(lua.expr) .. ", " .. tostring(colored.bright("STATEMENT:")) .. " " .. tostring(lua.statements)) end - assert(lua.expr, "Cannot use [[" .. tostring(bit.src) .. "]] as a string interpolation value, since it's not an expression.") + assert(lua.expr, "Cannot use [[" .. tostring(bit:get_src()) .. "]] as a string interpolation value, since it's not an expression.") insert(concat_parts, "stringify(" .. tostring(lua.expr) .. ")") _continue_0 = true until true @@ -1134,7 +1077,7 @@ do for _index_0 = 1, #_list_0 do local item = _list_0[_index_0] local lua = self:tree_to_lua(item) - assert(lua.expr, "Cannot use [[" .. tostring(item.src) .. "]] as a list item, since it's not an expression.") + assert(lua.expr, "Cannot use [[" .. tostring(item:get_src()) .. "]] as a list item, since it's not an expression.") insert(items, lua.expr) end return { @@ -1153,9 +1096,9 @@ do else key_lua = self:tree_to_lua(entry.dict_key) end - assert(key_lua.expr, "Cannot use [[" .. tostring(entry.dict_key.src) .. "]] as a dict key, since it's not an expression.") + assert(key_lua.expr, "Cannot use [[" .. tostring(entry.dict_key:get_src()) .. "]] as a dict key, since it's not an expression.") local value_lua = self:tree_to_lua(entry.dict_value) - assert(value_lua.expr, "Cannot use [[" .. tostring(entry.dict_value.src) .. "]] as a dict value, since it's not an expression.") + assert(value_lua.expr, "Cannot use [[" .. tostring(entry.dict_value:get_src()) .. "]] as a dict value, since it's not an expression.") local key_str = key_lua.expr:match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=]) if key_str then insert(items, tostring(key_str) .. "=" .. tostring(value_lua.expr)) @@ -1177,7 +1120,7 @@ do expr = self:var_to_lua_identifier(tree.value) } else - return error("Unknown/unimplemented thingy: " .. tostring(tree.type)) + return error("Unknown/unimplemented thingy: " .. tostring(tree.type), 0) end end, walk_tree = function(self, tree, depth) @@ -1239,8 +1182,9 @@ do end local _exp_0 = tree.type if "Var" == _exp_0 then - if replacements[tree.value] ~= nil then - tree = replacements[tree.value] + local id = self:var_to_lua_identifier(tree.value) + if replacements[id] ~= nil then + tree = replacements[id] end elseif "File" == _exp_0 or "Nomsu" == _exp_0 or "Block" == _exp_0 or "List" == _exp_0 or "FunctionCall" == _exp_0 or "Text" == _exp_0 then local new_value = self:tree_with_replaced_vars(tree.value, replacements) @@ -1289,69 +1233,41 @@ do end return tree end, - get_stub = function(self, x) - if not x then - error("Nothing to get stub from") + get_stubs_from_signature = function(self, signature) + if type(signature) ~= 'table' or signature.type then + error("Invalid signature: " .. tostring(repr(signature)), 0) end - if type(x) == 'string' then - local spec = concat(self.__class.stub_patt:match(x), " ") - local arg_names = { } - local stub = spec:gsub("%%(%S*)", function(arg) - insert(arg_names, arg) - return "%" - end) - return stub, arg_names - end - if type(x) ~= 'table' then - error("Invalid type for getting stub: " .. tostring(type(x)) .. " for:\n" .. tostring(repr(x))) - end - local _exp_0 = x.type - if "Text" == _exp_0 then - return self:get_stub(x.value) - elseif "FunctionCall" == _exp_0 then - return self:get_stub(x.src) - else - return error("Unsupported get stub type: " .. tostring(x.type) .. " for " .. tostring(repr(x))) + local stubs = { } + for i, alias in ipairs(signature) do + if type(alias) ~= 'string' then + error("Expected entries in signature to be strings, not " .. tostring(type(alias)) .. "s like: " .. tostring(repr(alias)) .. "\nsignature: " .. tostring(repr(signature)), 0) + end + stubs[i] = stub_pattern:match(alias) + if not (stubs[i]) then + error("Failed to match stub pattern on alias: " .. tostring(repr(alias))) + end end + return stubs end, - get_stubs = function(self, x) - if type(x) ~= 'table' then - return { - { - self:get_stub(x) - } - } + get_args_from_signature = function(self, signature) + if type(signature) ~= 'table' or signature.type then + error("Invalid signature: " .. tostring(repr(signature)), 0) end - local _exp_0 = x.type - if nil == _exp_0 then - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #x do - local i = x[_index_0] - _accum_0[_len_0] = { - self:get_stub(i) - } - _len_0 = _len_0 + 1 + local stub_args = { } + for i, alias in ipairs(signature) do + if type(alias) ~= 'string' then + error("Invalid type for signature: " .. tostring(type(alias)) .. " for:\n" .. tostring(repr(alias)), 0) end - return _accum_0 - elseif "List" == _exp_0 then - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = x.value - for _index_0 = 1, #_list_0 do - local i = _list_0[_index_0] - _accum_0[_len_0] = { - self:get_stub(i) - } - _len_0 = _len_0 + 1 + local args = var_pattern:match(alias) + if not (args) then + error("Failed to match arg pattern on alias: " .. tostring(repr(alias)), 0) end - return _accum_0 + for j = 1, #args do + args[j] = self:var_to_lua_identifier(args[j]) + end + stub_args[i] = args end - return { - { - self:get_stub(x) - } - } + return stub_args end, var_to_lua_identifier = function(self, var) if type(var) == 'table' and var.type == "Var" then @@ -1369,9 +1285,13 @@ do if level == nil then level = 0 end - return self:dedent(self.compilestack[#self.compilestack - level].src) + return self:dedent(self.compilestack[#self.compilestack - level]:get_src()) end, initialize_core = function(self) + local get_line_no + get_line_no = function() + return "nomsu.moon:" .. tostring(debug.getinfo(2).currentline) + end local nomsu = self local nomsu_string_as_lua nomsu_string_as_lua = function(code) @@ -1383,23 +1303,24 @@ do insert(concat_parts, bit) else local lua = nomsu:tree_to_lua(bit) - assert(lua.expr, "Cannot use [[" .. tostring(bit.src) .. "]] as a string interpolation value, since it's not an expression.") + if not (lua.expr) then + error("Cannot use [[" .. tostring(bit:get_src()) .. "]] as a string interpolation value, since it's not an expression.", 0) + end insert(concat_parts, lua.expr) end end return concat(concat_parts) end - self:define_compile_action("immediately %block", "nomsu.moon", function(_block) + 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 .. ";") - lua_code = "-- Immediately:\n" .. lua_code nomsu:run_lua(lua_code) return { - statements = lua_code, + statements = "if IMMEDIATE then\n" .. tostring(lua_code) .. "\nend", locals = lua.locals } end) - self:define_compile_action("lua> %code", "nomsu.moon", function(_code) + self:define_compile_action("lua> %code", get_line_no(), function(_code) if _code.type == "Text" then local lua = nomsu_string_as_lua(_code) return { @@ -1411,26 +1332,22 @@ do } end end) - self:define_compile_action("=lua %code", "nomsu.moon", function(_code) + self:define_compile_action("=lua %code", get_line_no(), function(_code) local lua = nomsu_string_as_lua(_code) return { expr = lua } end) - self:define_compile_action("__line_no__", "nomsu.moon", function() + self:define_compile_action("!! code location !!", get_line_no(), function() + local tree = nomsu.compilestack[#nomsu.compilestack - 1] return { - expr = repr(nomsu.compilestack[#nomsu.compilestack]:get_line_no()) + expr = repr(tostring(tree.filename) .. ":" .. tostring(tree.start) .. "," .. tostring(tree.stop)) } end) - self:define_compile_action("__src__ %level", "nomsu.moon", function(_level) - return { - expr = repr(nomsu:source_code(nomsu:tree_to_value(_level))) - } - end) - self:define_action("run file %filename", "nomsu.moon", function(_filename) + self:define_action("run file %filename", get_line_no(), function(_filename) return nomsu:run_file(_filename) end) - return self:define_compile_action("use %filename", "nomsu.moon", function(_filename) + return self:define_compile_action("use %filename", get_line_no(), function(_filename) local filename = nomsu:tree_to_value(_filename) nomsu:require_file(filename) return { @@ -1445,9 +1362,6 @@ do self.write = function(self, ...) return io.write(...) end - self.write_err = function(self, ...) - return io.stderr:write(...) - end local NaN_surrogate = { } local nil_surrogate = { } self.ids = setmetatable({ }, { @@ -1534,6 +1448,11 @@ do _base_0.__class = _class_0 local self = _class_0 self.def_number = 0 + line_counter = re.compile([[ lines <- {| line (%nl line)* |} + line <- {} (!%nl .)* + ]], { + nl = NOMSU_DEFS.nl + }) self.math_patt = re.compile([[ "%" (" " [*/^+-] " %")+ ]]) self.unescape_string = function(self, str) return Cs(((P("\\\\") / "\\") + (P("\\\"") / '"') + NOMSU_DEFS.escaped_char + P(1)) ^ 0):match(str) @@ -1557,10 +1476,13 @@ do insert(bits, close) return concat(bits) end - self.stub_patt = re.compile("{|(' '+ / '\n..' / {'%' %id*} / {%id+} / {%op})*|}", { - id = NOMSU_DEFS.ident_char, - op = NOMSU_DEFS.operator - }) + stub_defs = { + space = (P(' ') + P('\n..')) ^ 0, + word = (NOMSU_DEFS.ident_char ^ 1 + NOMSU_DEFS.operator ^ 1), + varname = (R('az', 'AZ', '09') + P('_') + NOMSU_DEFS.utf8_char) ^ 0 + } + stub_pattern = re.compile("{~ (%space->'') (('%' (%varname->'')) / %word)? ((%space->' ') (('%' (%varname->'')) / %word))* (%space->'') ~}", stub_defs) + var_pattern = re.compile("{| %space ((('%' {%varname}) / %word) %space)+ |}", stub_defs) NomsuCompiler = _class_0 end if arg then @@ -1608,7 +1530,8 @@ if arg then input = io.open(args.input):read("*a") end local retval, code = nomsu:run(input, args.input) - if args.output then + if compiled_output then + compiled_output:write("local IMMEDIATE = true;\n") compiled_output:write(code) end end @@ -1671,7 +1594,22 @@ if arg then do local metadata = nomsu.action_metadata[calling_fn.func] if metadata then - line = colored.yellow(metadata.line_no) + local filename, start, stop = metadata.source:match("([^:]*):([0-9]*),([0-9]*)") + if filename then + local file = io.open(filename):read("*a") + local line_no = 1 + for _ in file:sub(1, tonumber(start)):gmatch("\n") do + line_no = line_no + 1 + end + local offending_statement = file:sub(tonumber(start), tonumber(stop)) + if #offending_statement > 50 then + offending_statement = offending_statement:sub(1, 50) .. "..." + end + offending_statement = colored.red(offending_statement) + line = colored.yellow(filename .. ":" .. tostring(line_no) .. "\n " .. offending_statement) + else + line = colored.yellow(metadata.source) + end name = colored.bright(colored.yellow(metadata.aliases[1])) else if calling_fn.istailcall and not name then diff --git a/nomsu.moon b/nomsu.moon index 222f129..52ce1ea 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -29,7 +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] - STRING_METATABLE.__mul = (other)=> string.rep(@, other) + --STRING_METATABLE.__mul = (other)=> string.rep(@, other) -- TODO: -- consider non-linear codegen, rather than doing thunks for things like comprehensions @@ -39,12 +39,16 @@ 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 +-- Remove nomsu:write and nomsu:writeln and just use print() instead. +-- Allow ("..") as an expression, which is obviously not an indented text region, instead +-- of having a special syntax for "\.." lpeg.setmaxstack 10000 -- whoa {:P,:R,:V,:S,:Cg,:C,:Cp,:B,:Cmt} = lpeg NOMSU_DEFS = with {} - .nl = P("\n") + -- Newline supports either windows-style CR+LF or unix-style LF + .nl = P("\r")^-1 * P("\n") .ws = S(" \t") .tonumber = tonumber .print = (src,pos,msg)-> @@ -92,16 +96,16 @@ NOMSU_DEFS = with {} return start + lpeg.userdata.indent_stack[#lpeg.userdata.indent_stack] + 4 .error = (src,pos,err_msg)-> - if lpeg.userdata.source_code\sub(pos,pos) == "\n" - pos += #lpeg.userdata.source_code\match("[ \t\n]*", pos) + if lpeg.userdata.source_code\sub(pos,pos)\match("[\r\n]") + pos += #lpeg.userdata.source_code\match("[ \t\n\r]*", pos) line_no = 1 while (lpeg.userdata.line_starts[line_no+1] or math.huge) < pos do line_no += 1 prev_line = if line_no > 1 - lpeg.userdata.source_code\match("[^\n]*", lpeg.userdata.line_starts[line_no-1]) + lpeg.userdata.source_code\match("[^\r\n]*", lpeg.userdata.line_starts[line_no-1]) else "" - err_line = lpeg.userdata.source_code\match("[^\n]*", lpeg.userdata.line_starts[line_no]) + err_line = lpeg.userdata.source_code\match("[^\r\n]*", lpeg.userdata.line_starts[line_no]) next_line = if line_no < #lpeg.userdata.line_starts - lpeg.userdata.source_code\match("[^\n]*", lpeg.userdata.line_starts[line_no+1]) + lpeg.userdata.source_code\match("[^\r\n]*", lpeg.userdata.line_starts[line_no+1]) else "" pointer = ("-")\rep(pos-lpeg.userdata.line_starts[line_no]) .. "^" err_msg = (err_msg or "Parse error").." in #{lpeg.userdata.filename} on line #{line_no}:\n" @@ -111,11 +115,17 @@ NOMSU_DEFS = with {} .FunctionCall = (start, value, stop)-> stub = concat([(t.type == "Word" and t.value or "%") for t in *value], " ") src = lpeg.userdata.source_code\sub(start,stop-1) - return {:start, :stop, type: "FunctionCall", :src, get_line_no:lpeg.userdata.get_line_no, :value, :stub} + return { + type: "FunctionCall", :start, :stop, :value, :stub, filename:lpeg.userdata.filename, + get_line_no:lpeg.userdata.get_line_no, get_src:lpeg.userdata.get_src, + } setmetatable(NOMSU_DEFS, {__index:(key)=> make_node = (start, value, stop)-> - {:start, :stop, :value, src:lpeg.userdata.source_code\sub(start,stop-1), get_line_no:lpeg.userdata.get_line_no, type: key} + { + type: key, :start, :stop, :value, filename:lpeg.userdata.filename, + get_src:lpeg.userdata.get_src, get_line_no:lpeg.userdata.get_line_no, + } self[key] = make_node return make_node }) @@ -140,7 +150,6 @@ class NomsuCompiler @def_number: 0 new:()=> @write = (...)=> io.write(...) - @write_err = (...)=> io.stderr\write(...) -- Weak-key mapping from objects to randomly generated unique IDs NaN_surrogate = {} nil_surrogate = {} @@ -178,17 +187,17 @@ class NomsuCompiler @write(...) @write("\n") - errorln:(...)=> - @write_err(...) - @write_err("\n") - - define_action: (signature, line_no, fn, src, compile_time=false)=> + define_action: (signature, source, fn)=> + if @debug + @writeln "#{colored.bright "DEFINING ACTION:"} #{colored.green repr(signature)}" + if type(fn) != 'function' + error 'function', "Bad fn: #{repr fn}" if type(signature) == 'string' - signature = @get_stubs {signature} - elseif type(signature) == 'table' and type(signature[1]) == 'string' - signature = @get_stubs signature - assert type(fn) == 'function', "Bad fn: #{repr fn}" - aliases = {} + signature = {signature} + elseif type(signature) != 'table' or signature.type != nil + error("Invalid signature, expected list of strings, but got: #{repr signature}", 0) + stubs = @get_stubs_from_signature signature + stub_args = @get_args_from_signature signature @@def_number += 1 fn_info = debug.getinfo(fn, "u") @@ -196,98 +205,69 @@ class NomsuCompiler unless fn_info.isvararg fn_arg_positions = {debug.getlocal(fn, i), i for i=1,fn_info.nparams} arg_orders = {} -- Map from stub -> index where each arg in the stub goes in the function call - for sig_i=1,#signature - stub, arg_names = unpack(signature[sig_i]) - assert stub, "NO STUB FOUND: #{repr signature}" + for sig_i=1,#stubs + stub, args = stubs[sig_i], stub_args[sig_i] if @debug - @writeln "#{colored.bright "DEFINING ACTION:"} #{colored.underscore colored.magenta repr(stub)} #{colored.bright "WITH ARGS"} #{colored.dim repr(arg_names)} ON: #{@environment.ACTIONS}" + @writeln "#{colored.bright "ALIAS:"} #{colored.underscore colored.magenta repr(stub)} #{colored.bright "WITH ARGS"} #{colored.dim repr(args)} ON: #{@environment.ACTIONS}" -- TODO: use debug.getupvalue instead of @environment.ACTIONS? @environment.ACTIONS[stub] = fn unless fn_info.isvararg - arg_positions = [fn_arg_positions[@var_to_lua_identifier(a)] for a in *arg_names] + arg_positions = [fn_arg_positions[a] for a in *args] -- TODO: better error checking? - assert(#arg_positions == #arg_names, - "Mismatch in args between lua function's #{repr fn_arg_positions} and stub's #{repr arg_names}") + if #arg_positions != #args + error("Mismatch in args between lua function's #{repr fn_arg_positions} and stub's #{repr args} for #{repr stub}", 0) arg_orders[stub] = arg_positions @action_metadata[fn] = { - :fn, :src, :line_no, :aliases, :arg_orders, arg_positions:fn_arg_positions, def_number:@@def_number, + :fn, :source, aliases:stubs, :arg_orders, + arg_positions:fn_arg_positions, def_number:@@def_number, } - define_compile_action: (signature, line_no, fn, src)=> - @define_action(signature, line_no, fn, src, true) + define_compile_action: (signature, source, fn, src)=> + @define_action(signature, source, fn) @action_metadata[fn].compile_time = true - if @debug - @writeln "#{colored.bright colored.green "(it was compile time)"}" serialize_defs: (scope=nil, after=nil)=> -- TODO: repair - error("Not currently functional.") - after or= @core_defs or 0 - scope or= @defs - defs_by_num = {} - for stub, def in pairs(scope) - if def and stub\sub(1,1) != "#" - defs_by_num[def.def_number] = def - keys = [k for k,v in pairs(defs_by_num)] - table.sort(keys) - - buff = {} - k_i = 1 - _using = nil - _using_do = {} - for k_i,i in ipairs(keys) - if i <= after then continue - def = defs_by_num[i] - if def.defs == scope - if def.src - insert buff, def.src - continue - if _using == def.defs - if def.src - insert _using_do, def.src - else - _using = def.defs - _using_do = {def.src} - if k_i == #keys or defs_by_num[keys[k_i+1]].defs != _using - insert buff, "using:\n #{@indent @serialize_defs(_using)}\n..do:\n #{@indent concat(_using_do, "\n")}" - - for k,v in pairs(scope["#vars"] or {}) - insert buff, "<%#{k}> = #{@value_to_nomsu v}" - - return concat buff, "\n" + error("Not currently functional.", 0) dedent: (code)=> unless code\find("\n") return code spaces, indent_spaces = math.huge, math.huge for line in code\gmatch("\n([^\n]*)") - if line\match("^%s*#.*") - continue + if line\match("^%s*#.*") or line\match("^%s*$") + continue -- skip comments and blank lines elseif s = line\match("^(%s*)%.%..*") spaces = math.min(spaces, #s) elseif s = line\match("^(%s*)%S.*") indent_spaces = math.min(indent_spaces, #s) if spaces != math.huge and spaces < indent_spaces return (code\gsub("\n"..(" ")\rep(spaces), "\n")) - else + elseif indent_spaces != math.huge return (code\gsub("\n"..(" ")\rep(indent_spaces), "\n ")) + else return code indent: (code, levels=1)=> return code\gsub("\n","\n"..(" ")\rep(levels)) + line_counter = re.compile([[ + lines <- {| line (%nl line)* |} + line <- {} (!%nl .)* + ]], nl:NOMSU_DEFS.nl) parse: (nomsu_code, filename)=> assert type(filename) == "string", "Bad filename type: #{type filename}" if @debug @writeln("#{colored.bright "PARSING:"}\n#{colored.yellow nomsu_code}") - nomsu_code = nomsu_code\gsub("\r","") userdata = with {source_code:nomsu_code, :filename, indent_stack: {0}} - .line_starts = re.compile("lines <- {| line ('\n' line)* |} line <- {} [^\n]*")\match(nomsu_code) + .get_src = => nomsu_code\sub(@start, @stop-1) + .line_starts = line_counter\match(.source_code) .get_line_no = => unless @_line_no line_no = 1 - while (.line_starts[line_no+1] or math.huge) < @start do line_no += 1 + while line_no < #.line_starts and .line_starts[line_no+1] < @start + line_no += 1 @_line_no = "#{.filename}:#{line_no}" return @_line_no @@ -306,7 +286,7 @@ class NomsuCompiler if max_operations timeout = -> debug.sethook! - error "Execution quota exceeded. Your code took too long." + error("Execution quota exceeded. Your code took too long.", 0) debug.sethook timeout, "", max_operations tree = @parse(src, filename) assert tree, "Failed to parse: #{src}" @@ -314,9 +294,8 @@ class NomsuCompiler lua = @tree_to_lua(tree) lua_code = lua.statements or (lua.expr..";") - locals = lua_code.locals or {} - if #locals > 0 - lua_code = "local "..concat(locals, ", ")..";\n"..lua_code + if lua_code.locals and #lua_code.locals > 0 + lua_code = "local "..concat(lua_code.locals, ", ")..";\n"..lua_code lua_code = "-- File: #{filename}\n"..lua_code ret = @run_lua(lua_code) if max_operations @@ -340,12 +319,12 @@ class NomsuCompiler return @run_lua(lua_code) file = file or io.open(filename) if not file - error "File does not exist: #{filename}" + error("File does not exist: #{filename}", 0) nomsu_code = file\read('*a') file\close! return @run(nomsu_code, filename) else - error "Invalid filetype for #{filename}" + error("Invalid filetype for #{filename}", 0) require_file: (filename)=> loaded = @environment.LOADED @@ -363,16 +342,19 @@ class NomsuCompiler n = n + 1 ("\n%-3d|")\format(n) code = "1 |"..lua_code\gsub("\n", fn) - error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack code}\n\n#{err}") + error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack code}\n\n#{err}", 0) return run_lua_fn! tree_to_value: (tree, filename)=> + -- Special case for text literals + if tree.type == 'Text' and #tree.value == 1 and type(tree.value[1]) == 'string' + return tree.value[1] code = "return #{@tree_to_lua(tree).expr};" if @debug @writeln "#{colored.bright "RUNNING LUA TO GET VALUE:"}\n#{colored.blue colored.bright(code)}" lua_thunk, err = load(code, nil, nil, @environment) if not lua_thunk - error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack code}\n\n#{colored.red err}") + error("Failed to compile generated code:\n#{colored.bright colored.blue colored.onblack code}\n\n#{colored.red err}", 0) return lua_thunk! tree_to_nomsu: (tree, indentation="", max_line=80, expr_type=nil)=> @@ -584,7 +566,7 @@ class NomsuCompiler lines = {} for line in *tree.value nomsu = expression(line) - assert nomsu, "Failed to produce output for:\n#{colored.yellow line.src}" + assert nomsu, "Failed to produce output for:\n#{colored.yellow line\get_src!}" insert lines, nomsu return concat lines, "\n" @@ -620,24 +602,25 @@ class NomsuCompiler -- TODO: This might fail if it's being put inside a list or something return '".."\n '..(@indent value) else - error("Unsupported value_to_nomsu type: #{type(value)}") + error("Unsupported value_to_nomsu type: #{type(value)}", 0) @math_patt: re.compile [[ "%" (" " [*/^+-] " %")+ ]] tree_to_lua: (tree)=> -- Return , assert tree, "No tree provided." if not tree.type - error "Invalid tree: #{repr(tree)}" + error("Invalid tree: #{repr(tree)}", 0) switch tree.type when "File" if #tree.value == 1 return @tree_to_lua(tree.value[1]) declared_locals = {} lua_bits = {} + line_no = 1 for line in *tree.value lua = @tree_to_lua line if not lua - error "No lua produced by #{repr line}" + error("No lua produced by #{repr line}", 0) if lua.locals new_locals = [l for l in *lua.locals when not declared_locals[l]] if #new_locals > 0 @@ -651,7 +634,7 @@ class NomsuCompiler return statements:"--"..tree.value\gsub("\n","\n--") when "Nomsu" - return expr:"nomsu:parse(#{repr tree.value.src}, #{repr tree\get_line_no!}).value[1]" + return expr:"nomsu:parse(#{repr tree.value\get_src!}, #{repr tree\get_line_no!}).value[1]" when "Block" lua_bits = {} @@ -664,7 +647,8 @@ class NomsuCompiler for l in *lua.locals do locals[l] = true if lua.statements then insert lua_bits, lua.statements elseif lua.expr then insert lua_bits, "#{lua.expr};" - return statements:concat(lua_bits, "\n"), locals:(next(locals) and utils.keys(locals) or nil) + utils.deduplicate(locals) + return statements:concat(lua_bits, "\n"), locals:(#locals > 0 and locals or nil) when "FunctionCall" insert @compilestack, tree @@ -693,7 +677,7 @@ class NomsuCompiler insert bits, tok.value else lua = @tree_to_lua(tok) - assert(lua.expr, "non-expression value inside math expression: #{tok.src}") + assert(lua.expr, "non-expression value inside math expression: #{tok\get_src!}") insert bits, lua.expr remove @compilestack return expr:"(#{concat bits, " "})" @@ -703,7 +687,7 @@ class NomsuCompiler if tok.type == "Word" then continue lua = @tree_to_lua(tok) assert lua.expr, - "#{tree\get_line_no!}: Cannot use:\n#{colored.yellow tok.src}\nas an argument to #{tree.stub}, since it's not an expression, it produces: #{repr lua}" + "#{tree\get_line_no!}: Cannot use:\n#{colored.yellow tok\get_src!}\nas an argument to #{tree.stub}, since it's not an expression, it produces: #{repr lua}" insert args, lua.expr if metadata and metadata.arg_orders @@ -729,7 +713,7 @@ class NomsuCompiler @print_tree bit @writeln "#{colored.bright "EXPR:"} #{lua.expr}, #{colored.bright "STATEMENT:"} #{lua.statements}" assert lua.expr, - "Cannot use [[#{bit.src}]] as a string interpolation value, since it's not an expression." + "Cannot use [[#{bit\get_src!}]] as a string interpolation value, since it's not an expression." insert concat_parts, "stringify(#{lua.expr})" if string_buffer ~= "" @@ -746,7 +730,7 @@ class NomsuCompiler for item in *tree.value lua = @tree_to_lua item assert lua.expr, - "Cannot use [[#{item.src}]] as a list item, since it's not an expression." + "Cannot use [[#{item\get_src!}]] as a list item, since it's not an expression." insert items, lua.expr return expr:@@comma_separated_items("{", items, "}") @@ -758,10 +742,10 @@ class NomsuCompiler else @tree_to_lua entry.dict_key assert key_lua.expr, - "Cannot use [[#{entry.dict_key.src}]] as a dict key, since it's not an expression." + "Cannot use [[#{entry.dict_key\get_src!}]] as a dict key, since it's not an expression." value_lua = @tree_to_lua entry.dict_value assert value_lua.expr, - "Cannot use [[#{entry.dict_value.src}]] as a dict value, since it's not an expression." + "Cannot use [[#{entry.dict_value\get_src!}]] as a dict value, since it's not an expression." key_str = key_lua.expr\match([=[["']([a-zA-Z_][a-zA-Z0-9_]*)['"]]=]) if key_str insert items, "#{key_str}=#{value_lua.expr}" @@ -778,7 +762,7 @@ class NomsuCompiler return expr:@var_to_lua_identifier(tree.value) else - error("Unknown/unimplemented thingy: #{tree.type}") + error("Unknown/unimplemented thingy: #{tree.type}", 0) walk_tree: (tree, depth=0)=> coroutine.yield(tree, depth) @@ -833,8 +817,9 @@ class NomsuCompiler if type(tree) != 'table' then return tree switch tree.type when "Var" - if replacements[tree.value] ~= nil - tree = replacements[tree.value] + id = @var_to_lua_identifier tree.value + if replacements[id] ~= nil + tree = replacements[id] when "File", "Nomsu", "Block", "List", "FunctionCall", "Text" new_value = @tree_with_replaced_vars tree.value, replacements if new_value != tree.value @@ -861,37 +846,39 @@ class NomsuCompiler tree = new_values return tree - @stub_patt: re.compile "{|(' '+ / '\n..' / {'%' %id*} / {%id+} / {%op})*|}", - id:NOMSU_DEFS.ident_char, op:NOMSU_DEFS.operator - get_stub: (x)=> - if not x - error "Nothing to get stub from" - -- Returns a single stub ("say %"), list of arg names ({"msg"}), and set of arg - -- names that should not be evaluated from a single action def - -- (e.g. "say %msg") or function call (e.g. FunctionCall({Word("say"), Var("msg"))) - if type(x) == 'string' - -- Standardize format to stuff separated by spaces - spec = concat @@stub_patt\match(x), " " - arg_names = {} - stub = spec\gsub "%%(%S*)", (arg)-> - insert(arg_names, arg) - return "%" - return stub, arg_names - if type(x) != 'table' - error "Invalid type for getting stub: #{type(x)} for:\n#{repr x}" - switch x.type - when "Text" then return @get_stub(x.value) - when "FunctionCall" then return @get_stub(x.src) - else error "Unsupported get stub type: #{x.type} for #{repr x}" - get_stubs: (x)=> - if type(x) != 'table' then return {{@get_stub(x)}} - switch x.type - when nil - return [{@get_stub(i)} for i in *x] - when "List" - return [{@get_stub(i)} for i in *x.value] - return {{@get_stub(x)}} + stub_defs = { + space:(P(' ') + P('\n..'))^0 + word:(NOMSU_DEFS.ident_char^1 + NOMSU_DEFS.operator^1) + varname:(R('az','AZ','09') + P('_') + NOMSU_DEFS.utf8_char)^0 + } + stub_pattern = re.compile("{~ (%space->'') (('%' (%varname->'')) / %word)? ((%space->' ') (('%' (%varname->'')) / %word))* (%space->'') ~}", stub_defs) + get_stubs_from_signature: (signature)=> + if type(signature) != 'table' or signature.type + error("Invalid signature: #{repr signature}", 0) + stubs = {} + for i,alias in ipairs(signature) + if type(alias) != 'string' + error("Expected entries in signature to be strings, not #{type(alias)}s like: #{repr alias}\nsignature: #{repr signature}", 0) + stubs[i] = stub_pattern\match(alias) + unless stubs[i] + error("Failed to match stub pattern on alias: #{repr alias}") + return stubs + + var_pattern = re.compile("{| %space ((('%' {%varname}) / %word) %space)+ |}", stub_defs) + get_args_from_signature: (signature)=> + if type(signature) != 'table' or signature.type + error("Invalid signature: #{repr signature}", 0) + stub_args = {} + for i,alias in ipairs(signature) + if type(alias) != 'string' + error("Invalid type for signature: #{type(alias)} for:\n#{repr alias}", 0) + args = var_pattern\match(alias) + unless args + error("Failed to match arg pattern on alias: #{repr alias}", 0) + for j=1,#args do args[j] = @var_to_lua_identifier(args[j]) + stub_args[i] = args + return stub_args var_to_lua_identifier: (var)=> -- Converts arbitrary nomsu vars to valid lua identifiers by replacing illegal @@ -902,10 +889,11 @@ class NomsuCompiler if verboten == "_" then "__" else ("_%x")\format(verboten\byte!)) source_code: (level=0)=> - @dedent @compilestack[#@compilestack-level].src - + @dedent @compilestack[#@compilestack-level]\get_src! + initialize_core: => -- Sets up some core functionality + get_line_no = -> "nomsu.moon:#{debug.getinfo(2).currentline}" nomsu = self nomsu_string_as_lua = (code)-> concat_parts = {} @@ -914,39 +902,37 @@ class NomsuCompiler insert concat_parts, bit else lua = nomsu\tree_to_lua bit - assert lua.expr, - "Cannot use [[#{bit.src}]] as a string interpolation value, since it's not an expression." + unless lua.expr + error("Cannot use [[#{bit\get_src!}]] as a string interpolation value, since it's not an expression.", 0) insert concat_parts, lua.expr return concat(concat_parts) - - @define_compile_action "immediately %block", "nomsu.moon", (_block)-> + + -- 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..";") - lua_code = "-- Immediately:\n"..lua_code nomsu\run_lua(lua_code) - return statements:lua_code, locals:lua.locals + return statements:"if IMMEDIATE then\n#{lua_code}\nend", locals:lua.locals - @define_compile_action "lua> %code", "nomsu.moon", (_code)-> + @define_compile_action "lua> %code", get_line_no!, (_code)-> if _code.type == "Text" lua = nomsu_string_as_lua(_code) return statements:lua else return statements:"nomsu:run_lua(#{nomsu\tree_to_lua(_code).expr});" - @define_compile_action "=lua %code", "nomsu.moon", (_code)-> + @define_compile_action "=lua %code", get_line_no!, (_code)-> lua = nomsu_string_as_lua(_code) return expr:lua - @define_compile_action "__line_no__", "nomsu.moon", -> - expr: repr(nomsu.compilestack[#nomsu.compilestack]\get_line_no!) + @define_compile_action "!! code location !!", get_line_no!, -> + tree = nomsu.compilestack[#nomsu.compilestack-1] + return expr: repr("#{tree.filename}:#{tree.start},#{tree.stop}") - @define_compile_action "__src__ %level", "nomsu.moon", (_level)-> - expr: repr(nomsu\source_code(nomsu\tree_to_value(_level))) - - @define_action "run file %filename", "nomsu.moon", (_filename)-> + @define_action "run file %filename", get_line_no!, (_filename)-> nomsu\run_file(_filename) - @define_compile_action "use %filename", "nomsu.moon", (_filename)-> + @define_compile_action "use %filename", get_line_no!, (_filename)-> filename = nomsu\tree_to_value(_filename) nomsu\require_file(filename) return statements:"nomsu:require_file(#{repr filename});" @@ -993,7 +979,8 @@ if arg io.read('*a') else io.open(args.input)\read("*a") retval, code = nomsu\run(input, args.input) - if args.output + if compiled_output + compiled_output\write("local IMMEDIATE = true;\n") compiled_output\write(code) if args.flags["-p"] @@ -1021,6 +1008,8 @@ if arg print("#{colored.red "ERROR:"} #{colored.bright colored.yellow colored.onred (error_message or "")}") print("stack traceback:") + -- TODO: properly print out the calling site of nomsu code, not just the *called* code + import to_lua from require "moonscript.base" nomsu_file = io.open("nomsu.moon") nomsu_source = nomsu_file\read("*a") @@ -1037,7 +1026,18 @@ if arg if name == "run_lua_fn" then continue line = nil if metadata = nomsu.action_metadata[calling_fn.func] - line = colored.yellow(metadata.line_no) + filename, start, stop = metadata.source\match("([^:]*):([0-9]*),([0-9]*)") + if filename + file = io.open(filename)\read("*a") + line_no = 1 + for _ in file\sub(1,tonumber(start))\gmatch("\n") do line_no += 1 + offending_statement = file\sub(tonumber(start),tonumber(stop)) + if #offending_statement > 50 + offending_statement = offending_statement\sub(1,50).."..." + offending_statement = colored.red(offending_statement) + line = colored.yellow(filename..":"..tostring(line_no).."\n "..offending_statement) + else + line = colored.yellow(metadata.source) name = colored.bright(colored.yellow(metadata.aliases[1])) else if calling_fn.istailcall and not name diff --git a/utils.lua b/utils.lua index 27951ea..80585a5 100644 --- a/utils.lua +++ b/utils.lua @@ -116,12 +116,15 @@ local function split(str, sep) end local function remove_from_list(list, item) - for i=1,#list do + local deleted, N = 0, #list + for i=1,N do if list[i] == item then - table.remove(list, i) - return + deleted = deleted + 1 + else + list[i-deleted] = list[i] end end + for i=N-deleted+1,N do list[i] = nil end end local function accumulate(glue, co) @@ -163,6 +166,18 @@ local function set(list) return ret end +local function deduplicate(list) + local seen, deleted = {}, 0 + for i, item in ipairs(list) do + if seen[item] then + deleted = deleted + 1 + else + seen[item] = true + list[i-deleted] = list[i] + end + end +end + local function sum(t) local tot = 0 for i=1,#t do @@ -343,6 +358,6 @@ end return {is_list=is_list, size=size, repr=repr, stringify=stringify, split=split, remove_from_list=remove_from_list, accumulate=accumulate, nth_to_last=nth_to_last, - keys=keys, values=values, set=set, sum=sum, product=product, all=all, any=any, - min=min, max=max, sort=sort, equivalent=equivalent, key_for=key_for, clamp=clamp, - mix=mix, round=round} + keys=keys, values=values, set=set, deduplicate=deduplicate, sum=sum, product=product, + all=all, any=any, min=min, max=max, sort=sort, equivalent=equivalent, key_for=key_for, + clamp=clamp, mix=mix, round=round}