From 505fec2a61d2571317cc4bbc36ec0f4822a63f9d Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Fri, 2 Feb 2018 15:48:28 -0800 Subject: Restructured the nomsu files to group all the essentials into core/ and all the optionals into lib/. lib/core.nom and tests/all.nom are no longer needed now. --- compile_lib.sh | 18 +- core/collections.nom | 204 ++++++++++++++++++++ core/control_flow.nom | 467 ++++++++++++++++++++++++++++++++++++++++++++++ core/math.nom | 95 ++++++++++ core/metaprogramming.nom | 200 ++++++++++++++++++++ core/operators.nom | 209 +++++++++++++++++++++ core/text.nom | 56 ++++++ lib/collections.nom | 204 -------------------- lib/control_flow.nom | 467 ---------------------------------------------- lib/core.nom | 10 - lib/file_hash.nom | 46 +++++ lib/habit_breaker.nom | 54 ++++++ lib/math.nom | 95 ---------- lib/metaprogramming.nom | 200 -------------------- lib/object.nom | 2 +- lib/object2.nom | 135 ++++++++++++++ lib/operators.nom | 209 --------------------- lib/text.nom | 56 ------ lib/training_wheels.nom | 2 +- nomsu.lua | 39 ++-- nomsu.moon | 35 ++-- tests/all.nom | 28 --- tests/collections.nom | 4 +- tests/control_flow.nom | 5 +- tests/math.nom | 4 +- tests/metaprogramming.nom | 4 +- tests/object.nom | 5 +- tests/operators.nom | 4 +- tests/text.nom | 4 +- 29 files changed, 1536 insertions(+), 1325 deletions(-) create mode 100644 core/collections.nom create mode 100644 core/control_flow.nom create mode 100644 core/math.nom create mode 100644 core/metaprogramming.nom create mode 100644 core/operators.nom create mode 100644 core/text.nom delete mode 100644 lib/collections.nom delete mode 100644 lib/control_flow.nom delete mode 100644 lib/core.nom create mode 100644 lib/file_hash.nom create mode 100644 lib/habit_breaker.nom delete mode 100644 lib/math.nom delete mode 100644 lib/metaprogramming.nom create mode 100644 lib/object2.nom delete mode 100644 lib/operators.nom delete mode 100644 lib/text.nom delete mode 100644 tests/all.nom diff --git a/compile_lib.sh b/compile_lib.sh index 8cdfcdb..1dc9d07 100755 --- a/compile_lib.sh +++ b/compile_lib.sh @@ -11,20 +11,22 @@ while getopts ":f" opt; do esac done if [ "$FLUSH" = true ] ; then - for file in $(find lib/ -name "*.lua") ; do - rm $file - done + rm core/*.lua + rm lib/*.lua + rm tests/*.lua fi -printf "Compiling lib/core.nom ..." -./nomsu.moon -c lib/core.nom -echo "done." -for file in $(cat lib/core.nom | lua -e "for filename in io.read('*a'):gmatch('use \"([^\"]*)\"') do print(filename) end") ; do +for file in core/*.nom; do printf "Compiling $file ..." ./nomsu.moon -c $file echo "done." done -for file in $(cat tests/all.nom | lua -e "for filename in io.read('*a'):gmatch('run file \"([^\"]*)\"') do print(filename) end") ; do +for file in lib/*.nom; do + printf "Compiling $file ..." + ./nomsu.moon -c $file + echo "done." +done +for file in tests/*.nom; do printf "Compiling $file ..." ./nomsu.moon -c $file echo "done." diff --git a/core/collections.nom b/core/collections.nom new file mode 100644 index 0000000..6882c96 --- /dev/null +++ b/core/collections.nom @@ -0,0 +1,204 @@ +#.. + This file contains code that supports manipulating and using collections like lists + and dictionaries. + +use "core/metaprogramming.nom" +use "core/control_flow.nom" +use "core/operators.nom" + +# List/dict functions: + +# Indexing +immediately: + compile [..] + %index st to last in %list, %index nd to last in %list, %index rd to last in %list + %index th to last in %list + ..to {expr:"utils.nth_to_last(\(%list as lua expr), \(%index as lua expr))"} + +immediately: + parse [first in %list, first %list] as: 1 st in %list + parse [last in %list, last %list] as: 1 st to last in %list + +# Membership testing +immediately: + action [%item is in %list, %list contains %item, %list has %item] + for %key = %value in %list + if (%key is %item): return (yes) + return (no) + + action [..] + %item isn't in %list, %item is not in %list + %list doesn't contain %item, %list does not contain %item + %list doesn't have %item, %list does not have %item + ..: + for %key = %value in %list + if (%key is %item): return (no) + return (yes) + +immediately: + # Note: it's important to have the space after "[" to prevent confusion if %index is a string + compile [%list has key %index, %list has index %index] to {..} + expr: ".." + ((\(%list as lua expr))[ \(%index as lua expr)] ~= nil) + + # Note: it's important to have the space after "[" to prevent confusion if %index is a string + compile [..] + %list doesn't have key %index, %list does not have key %index + %list doesn't have index %index, %list does not have index %index + ..to {expr:"((\(%list as lua expr))[ \(%index as lua expr)] == nil)"} + + compile [length of %list, size of %list, size %list, number of %list, len %list] to: + {expr:"utils.size(\(%list as lua expr))"} + + compile [append %item to %list, add %item to %list] to: + {statements:"table.insert(\(%list as lua expr), \(%item as lua expr))"} + + compile [pop from %list, remove last from %list] to: + {statements:"table.remove(\(%list as lua expr))"} + + compile [remove index %index from %list] to: + {statements:"table.remove(\(%list as lua expr), \(%index as lua expr))"} + +# List Comprehension +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 {..} + 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 %index from %start to %stop via %step + %expression for %index from %start to %stop by %step + ..to: + assume ((%index's "type") is "Var") or barf ".." + List comprehension has the wrong type for the loop variable. Expected Var, but got: \(%index's "type") + return {..} + expr:".." + (function() + local comprehension = {}; + for \(%index as lua expr)=\(%start as lua expr),\(%stop as lua expr),\(%step as lua expr) do + comprehension[\(%index as lua expr)] = \(%expression as lua expr); + end + return comprehension; + end)() + parse [%expression for all ] as: %expression for % in %iterable + parse [%expression for %var from %start to %stop] as: %expression for %var from %start to %stop via 1 + parse [..] + %expression for all %start to %stop by %step + %expression for all %start to %stop via %step + ..as: %expression for % from %start to %stop via %step + parse [%expression for all %start to %stop] as: %expression for all %start to %stop via 1 + + compile [%expression for %key = %value in %iterable] to: + assume ((%key's "type") is "Var") or barf ".." + 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 {..} + expr: ".." + (function() + local comprehension = {}; + for \(%key as lua expr), \(%value as lua expr) in pairs(\(%iterable as lua expr)) do + table.insert(comprehension, \(%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") + # 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: + assume ((%src_key's "type") is "Var") or barf ".." + 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") + # 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)() + +immediately: + action [%lists flattened]: + %flat <- [] + for %list in %lists: + for %item in %list: + add %item to %flat + return %flat + + parse [entries in %dict] as: {key:%k, value:%v} for %k = %v in %dict + parse [keys in %dict] as: %k for %k = %v in %dict + parse [values in %dict] as: %v for %k = %v in %dict + +# Sorting: +immediately: + compile [sort %items] to {statements:"table.sort(\(%items as lua expr))"} + compile [sort %items by %key_expr] to {..} + statements: ".." + utils.sort(\(%items as lua expr), function(\(\% as lua expr)) + return \(%key_expr as lua expr); + end) + +immediately: + action [%items sorted, sorted %items]: + %copy <- (% for all %items) + sort %copy + return %copy + action [%items sorted by %key]: + %copy <- (% for all %items) + sort %copy by %key + return %copy + + action [unique %items]: + %unique <- [] + %seen <- {} + for all %items: + unless: % in %seen + add % to %unique + (% in %seen) <- (yes) + return %unique + +immediately: + # Metatable stuff + compile [set %dict's metatable to %metatable] to {..} + statements: "setmetatable(\(%dict as lua expr), \(%metatable as lua expr));" + + compile [new counter] to {expr:"setmetatable({}, {__index=function() return 0; end})"} + + compile [new 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/core/control_flow.nom b/core/control_flow.nom new file mode 100644 index 0000000..315c4ef --- /dev/null +++ b/core/control_flow.nom @@ -0,0 +1,467 @@ +#.. + This file contains compile-time actions that define basic control flow structures + like "if" statements and loops. + +use "core/metaprogramming.nom" +use "core/text.nom" +use "core/operators.nom" + +# No-Op +immediately: + compile [do nothing] to {statements:""} + +# Conditionals +immediately: + 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: + %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)" + because that breaks if %if_expr is falsey, e.g. "x < 5 and false or 99" +immediately + compile [..] + %when_true_expr if %condition else %when_false_expr + %when_true_expr if %condition otherwise %when_false_expr + %when_false_expr unless %condition else %when_true_expr + %when_false_expr unless %condition then %when_true_expr + ..to: + #.. 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 {..} + 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 {..} + 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 ---, *** %label ***] to {..} + statements:"::label_\(%label as lua identifier)::;" + compile [go to %label] to {..} + statements:"goto label_\(%label as lua identifier);" + +# Basic loop control +immediately: + compile [do next] to {statements:"continue;"} + compile [stop] to {statements:"break;"} + +# Helper function +immediately: + 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 + +# While loops +immediately: + compile [do next repeat] to {statements:"goto continue_repeat;"} + compile [stop repeating] to {statements:"goto stop_repeat;"} + compile [repeat while %condition %body] to + %body_lua <- (%body as lua) + %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");") + if %body has subtree % where: + ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next repeat") + ..: %body_statments +<- "\n::continue_repeat::;" + %code <- ".." + while \(%condition as lua expr) do + \%body_statements + end --while-loop + 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 + 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: + %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 repeat") + ..: %body_statements +<- "\n::continue_repeat::;" + %code <- ".." + for i=1,\(%n as lua expr) do + \%body_statements + end --numeric for-loop + 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 + return {statements:%code, locals:%body_lua's "locals"} + +# For loop control flow: +immediately: + compile [stop %var] to {..} + statements:"goto stop_\(%var as lua identifier);" + compile [do next %var] to {..} + statements:"goto continue_\(%var as lua identifier);" + +# 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: + %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 %") and + ((3rd in (%'s "value"))'s "value") is (%var's "value") + ..: %body_statements +<- "\n::continue_\(%var as lua identifier)::;" + + # This uses Lua's approach of only allowing loop-scoped variables in a loop + assume ((%var's "type") is "Var") or barf "Loop expected variable, not: \(%var's source code)" + %code <- ".." + for \(%var as lua expr)=\(%start as lua expr),\(%stop as lua expr),\(%step as lua expr) do + \%body_statements + end --numeric for-loop + + if %body has subtree % where: + ((%'s "type") = "FunctionCall") and: + ((%'s "stub") is "stop %") and: + ((2nd in (%'s "value"))'s "value") is (%var's "value") + ..: + %code <- ".." + do -- scope for stopping for-loop + \%code + ::stop_\(%var as lua identifier)::; + end -- end of scope for stopping for-loop + + 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 + parse [..] + for all %start to %stop by %step %body + for all %start to %stop via %step %body + ..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 (lua's "ipairs()") +immediately: + 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") is "do next %") and + ((3rd in (%'s "value"))'s "value") is (%var's "value") + ..: %body_statements +<- "\n::continue_\(%var as lua identifier)::;" + # This uses Lua's approach of only allowing loop-scoped variables in a loop + assume ((%var's "type") is "Var") or barf "Loop expected variable, not: \(%var's source code)" + %code <- ".." + for i,\(%var as lua expr) in ipairs(\(%iterable as lua expr)) do + \%body_statements + end --foreach-loop + if %body has subtree % where: + ((%'s "type") = "FunctionCall") and + ((%'s "stub") is "stop %") and + ((2nd in (%'s "value"))'s "value") is (%var's "value") + ..: + %code <- ".." + do -- scope for stopping for-loop + \%code + ::stop_\(%var as lua identifier)::; + end -- end of scope for stopping for-loop + 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: + %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 %") and + ((3rd in (%'s "value"))'s "value") is (%key's "value") + ..: %body_statements +<- "\n::continue_\(%key as lua identifier)::;" + + if %body has subtree % where: + ((%'s "type") = "FunctionCall") and + ((%'s "stub") is "do next %") and + ((3rd in (%'s "value"))'s "value") is (%value's "value") + ..: %body_statements +<- "\n::continue_\(%value as lua identifier)::;" + + # This uses Lua's approach of only allowing loop-scoped variables in a loop + assume ((%key's "type") is "Var") or barf "Loop expected variable, not: \(%key's source code)" + assume ((%value's "type") is "Var") or barf "Loop expected variable, not: \(%value's source code)" + %code <- ".." + for \(%key as lua expr),\(%value as lua expr) in pairs(\(%iterable as lua expr)) do + \%body_statements + end --foreach-loop + + %stop_labels <- "" + if %body has subtree % where: + ((%'s "type") = "FunctionCall") and + ((%'s "stub") is "stop %") and + ((2nd in (%'s "value"))'s "value") is (%key's "value") + ..: %stop_labels +<- "\n::stop_\(%key as lua identifier)::;" + + if %body has subtree % where: + ((%'s "type") = "FunctionCall") and + ((%'s "stub") is "stop %") and + ((2nd in (%'s "value"))'s "value") is (%value's "value") + ..: %stop_labels +<- "\n::stop_\(%value as lua identifier)::;" + + if: %stop_labels is not "" + %code <- ".." + do -- scope for stopping for % = % loop + \%code\%stop_labels + end + return {statements:%code, locals:%body_lua's "locals"} + +# Switch statement/multi-branch if +immediately: + 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. + with [..] + %tokens <- (%func_call's "value") + %star <- (1st in %tokens) + %condition <- (2nd in %tokens) + %action <- (3rd in %tokens) + ..: + assume (=lua "\%star and \%star.type == 'Word' and \%star.value == '*'") or barf ".." + Invalid format for 'when' statement. Lines must begin with '*' + assume %condition or barf ".." + Invalid format for 'when' statement. Lines must begin with '*' and have a condition or the word "else" + if: %action is (nil) + lua> "table.insert(\%fallthroughs, \(%condition as lua expr));" + 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" + %code +<- ".." + + else + \%action_statements + %seen_else <- (yes) + ..else: + assume (not %seen_else) or barf "'else' clause needs to be last in 'when' block" + lua> "table.insert(\%fallthroughs, \(%condition as lua expr));" + %condition_code <- (%fallthroughs joined with " or ") + %code +<- ".." + + \("if" if %is_first else "elseif") \%condition_code then + \%action_statements + + %fallthroughs <- [] + %is_first <- (no) + + 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 <- "" + %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. + %tokens <- (%func_call's "value") + with [%star<-(1st in %tokens), %condition<-(2nd in %tokens), %action<-(3rd in %tokens)]: + assume (=lua "\%star and \%star.type == 'Word' and \%star.value == '*'") or barf ".." + Invalid format for 'when' statement. Lines must begin with '*' + assume %condition or barf ".." + Invalid format for 'when' statement. Lines must begin with '*' and have a condition or the word "else" + if: %action is (nil) + lua> "table.insert(\%fallthroughs, \(%condition as lua expr))" + 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" + %code +<- ".." + + else + \%action_statements + end + %seen_else <- (yes) + ..else: + assume (not %seen_else) or barf "'else' clause needs to be last in 'when % = ?' block" + lua> "table.insert(\%fallthroughs, \(%condition as lua expr));" + for %i = % in %fallthroughs + if: ((%'s "type") is "Text") or ((%'s "type") is "Number") + (%i'th in %fallthroughs) <- "branch_value == \%" + ..else + (%i'th in %fallthroughs) <- "utils.equivalent(branch_value, \%)" + %clause <- (%fallthroughs joined with " or ") + %code +<- ".." + + \("if" if %is_first else "elseif") \%clause then + \%action_statements + + %fallthroughs <- [] + %is_first <- (no) + + assume (%fallthroughs = []) or barf "Unfinished fallthrough conditions in 'when' block" + assume (%code is not "") or barf "No body for 'when % = ?' block!" + unless %seen_else + %code +<- "\nend" + %code <- ".." + do --when % = ? + local branch_value = \(%branch_value as lua expr);\ + ..\%code + end --when % = ? + lua> "utils.deduplicate(\%locals);" + return {statements:%code, locals:%locals} + +# Try/except +immediately: + compile [..] + try %action and if it succeeds %success or if it barfs %fallback + try %action and if it barfs %fallback or if it succeeds %success + ..to: + %locals <- [] + %action_lua <- (%action as lua) + %success_lua <- (%success as lua) + %fallback_lua <- (%fallback as lua) + %fallback <- (%fallback as lua) + for %block in [%action_lua, %success_lua, %fallback_lua]: + for %local in ((%block's "locals") or []): + lua> "table.insert(\%locals, \%local);" + lua> "utils.deduplicate(\%locals);" + return {..} + locals: %locals + statements: ".." + do + local fell_through = false; + local ok, ret = pcall(function() + \((%action_lua's "statements") or "\(%action_lua's "expr");") + fell_through = true; + end); + if ok then + \((%success_lua's "statements") or "\(%success_lua's "expr");") + end + if not ok then + \((%fallback_lua's "statements") or "\(%fallback_lua's "expr");") + elseif not fell_through then + return ret; + end + end + parse [try %action] as: + try %action and if it succeeds: do nothing + ..or if it barfs: do nothing + parse [try %action and if it barfs %fallback] as: + try %action and if it succeeds: do nothing + ..or if it barfs %fallback + parse [try %action and if it succeeds %success] as: + try %action and if it succeeds %success or if it barfs: do nothing + +# Do/finally: +immediately: + 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: + %action <- (%action as lua) + %final_action <- (%final_action as lua) + %locals <- [] + for %sub_locals in [%action's "locals", %final_action's "locals"]: + for %local in %sub_locals: + 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/core/math.nom b/core/math.nom new file mode 100644 index 0000000..da8d931 --- /dev/null +++ b/core/math.nom @@ -0,0 +1,95 @@ +#.. + This file defines some common math literals and functions + +use "core/metaprogramming.nom" +use "core/text.nom" +use "core/operators.nom" +use "core/control_flow.nom" + +# Literals: +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.exp(1)"} + +# Functions: +compile [% as a number] to {expr:"tonumber(\(% as lua expr))"} +compile [absolute value %, | % |, abs %] to {expr:"math.abs(\(% as lua expr))"} +compile [square root %, √%, sqrt %] to {expr:"math.sqrt(\(% as lua expr))"} +compile [sine %, sin %] to {expr:"math.sin(\(% as lua expr))"} +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: + unless: (%items' "type") is "List" + return {expr:"utils.all(\(%items as lua expr))"} + %clauses <- [] + for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" + return {expr:"(\(%clauses joined with " and "))"} +parse [not all of %items, not all %items] as: not (all of %items) +compile [any of %items, any %items] to: + unless: (%items' "type") is "List" + return {expr:"utils.any(\(%items as lua expr))"} + %clauses <- [] + for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" + return {expr:"(\(%clauses joined with " or "))"} +parse [none of %items, none %items] as: not (any of %items) +compile [sum of %items, sum %items] to: + unless: (%items' "type") is "List" + return {expr:"utils.sum(\(%items as lua expr))"} + %clauses <- [] + for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" + return {expr:"(\(%clauses joined with " + "))"} +compile [product of %items, product %items] to: + unless: (%items' "type") is "List" + return {expr:"utils.product(\(%items as lua expr))"} + %clauses <- [] + for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" + return {expr:"(\(%clauses joined with " * "))"} +action [avg of %items, average of %items]: + =lua "(utils.sum(\%items)/#\%items)" +compile [min of %items, smallest of %items, lowest of %items] to {..} + 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 %]: + lua> ".." + 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 {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 expr), \(%high as lua expr))" +action [random choice from %elements, random choice %elements, random %elements]: + =lua "\%elements[math.random(#\%elements)]" diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom new file mode 100644 index 0000000..544ae59 --- /dev/null +++ b/core/metaprogramming.nom @@ -0,0 +1,200 @@ +#.. + This File contains actions for making actions and compile-time actions and some helper + functions to make that easier. + +# Compile-time action to make compile-time actions: +immediately: + lua> ".." + 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 + 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 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_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: + 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 + 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:get_src())); + end + local replacements = {}; + for i, a in ipairs(stub_args[1]) do replacements[i] = a.."="..a; end + replacements = "{"..table.concat(replacements, ", ").."}"; + 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> ".." + local fn = ACTION[\%stub]; + local metadata = ACTION_METADATA[fn]; + for i=#metadata.aliases,1,-1 do + metadata.arg_orders[metadata.aliases[i]] = nil; + table.remove(metadata.aliases, i); + end + ACTION[\%stub] = nil; + +immediately: + action [%tree as lua]: + =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); + 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)" + +immediately: + compile [%tree's source code, %tree' source code] to {expr:"\(%tree as lua expr):get_src()"} + + compile [repr %obj] to {expr:"repr(\(%obj as lua expr))"} + compile [type of %obj] to {expr:"type(\(%obj as lua expr))"} + +immediately: + compile [nomsu] to {expr:"nomsu"} + compile [%var as lua identifier] to {expr:"nomsu:var_to_lua_identifier(\(%var as lua expr))"} + +action [action %names metadata]: + =lua "ACTION_METADATA[ACTION[\%names]]" + +# Get the source code for a function +action [help %action]: + lua> ".." + local metadata = \(action %action metadata); + if not metadata then + print("Action not found: "..repr(\%action)); + else + print(metadata.src or ""); + end + +# Compiler tools +immediately: + compile [run %code] to {expr: "nomsu:run(\(%code as lua expr), '\(!! code location !!)')"} + parse [enable debugging] as: lua> "nomsu.debug = true;" + parse [disable debugging] as: lua> "nomsu.debug = false;" + +immediately: + compile [say %message] to: + lua> ".." + if \%message.type == "Text" then + return {statements="print("..\(%message as lua expr)..");"}; + else + return {statements="print(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 +immediately: + compile [barf] to {statements:"error(nil, 0);"} + compile [barf %msg] to {statements:"error(\(%msg as lua expr), 0);"} + compile [assume %condition] to: + lua> "local \%assumption = 'Assumption failed: '..\%condition:get_src();" + return {..} + statements:".." + if not \(%condition as lua expr) then + error(\(repr %assumption), 0); + end + compile [assume %condition or barf %msg] to {..} + statements:".." + if not \(%condition as lua expr) then + error(\(%msg as lua expr), 0); + end + +# Literals +immediately: + compile [yes] to {expr:"true"} + compile [no] to {expr:"false"} + compile [nothing, nil, null] to {expr:"nil"} + diff --git a/core/operators.nom b/core/operators.nom new file mode 100644 index 0000000..d2929bf --- /dev/null +++ b/core/operators.nom @@ -0,0 +1,209 @@ +#.. + This file contains definitions of operators like "+" and "and". + +use "core/metaprogramming.nom" + +# Indexing: +immediately: + #.. NOTE!!! It's critical that there are spaces around %key if it's a string, + otherwise, Lua will get confused and interpret %obj[[[foo]]] as %obj("[foo]") + instead of %obj[ "foo" ]. + It's also critical to have parens around %obj, otherwise Lua is too dumb to + realize that {x=1}["x"] is the same as ({x=1})["x"] or that + {x=1}.x is the same as ({x=1}).x + compile [..] + %obj' %key, %obj's %key, %key in %obj, %key'th in %obj, %key of %obj, + %key st in %obj, %key nd in %obj, %key rd in %obj, %key th in %obj, + ..to: + 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 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 {expr=obj_lua.."."..key_attr}; + elseif key_lua:sub(1,1) == "[" then + key_lua = " "..key_lua.." "; + end + return {expr=obj_lua.."["..key_lua.."]"}; + +# Comparison Operators +immediately: + compile [%x < %y] to {expr:"(\(%x as lua expr) < \(%y as lua expr))"} + compile [%x > %y] to {expr:"(\(%x as lua expr) > \(%y as lua expr))"} + compile [%x <= %y] to {expr:"(\(%x as lua expr) <= \(%y as lua expr))"} + compile [%x >= %y] to {expr:"(\(%x as lua expr) >= \(%y as lua expr))"} + # TODO: optimize case of [%x,%y] = [1,2] + compile [%a is %b, %a = %b, %a == %b] to: + lua> ".." + local safe = {Text=true, Number=true}; + 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 {expr="("..a_lua.." == "..b_lua..")"}; + else + 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 {expr="("..a_lua.." ~= "..b_lua..")"}; + else + 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 {expr:"nomsu.ids[\(% as lua expr)]"} + +# Variable assignment operator +immediately: + 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)" + +immediately: + # Simultaneous mutli-assignments like: x,y,z = 1,x,3; + compile [<- %assignments] to: + %locals <- [] + %targets <- [] + %values <- [] + assume ((%assignments' "type") is "Dict") or barf ".." + Expected a Dict for the assignments part of '<- %' statement, not \(%assignments' source code) + lua> ".." + for i, item in ipairs(\%assignments.value) do + local target, value = item.dict_key, item.dict_value; + local target_lua = nomsu:tree_to_lua(target); + if not target_lua.expr then error("Invalid target for assignment: "..target:get_src()); end + local value_lua = nomsu:tree_to_lua(value); + if not value_lua.expr then error("Invalid value for assignment: "..value:get_src()); end + if target.type == "Var" then + table.insert(\%locals, target_lua.expr); + end + table.insert(\%targets, target_lua.expr); + table.insert(\%values, value_lua.expr); + end + utils.deduplicate(\%locals); + return {locals=\%locals, statements=(table.concat(\%targets, ", ").." = "..table.concat(\%values, ", ")..";")}; + +immediately: + compile [export %var <- %value] to: + %var_lua <- (%var as lua) + 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");"} + + compile [exporting %exported %body] to: + %body_lua <- (%body as lua) + %leftover_locals <- (=lua "{unpack(\%body_lua.locals or {})}") + assume ((%exported's "type") = "List") or barf ".." + Expected a List for the export part of 'exporting' statement, not \(%exported's source code) + lua> ".." + for i, item in ipairs(\%exported.value) do + if item.type ~= "Var" then + error("'exporting' statement expects Vars, not: "..item:get_src()); + end + local var = nomsu:tree_to_lua(item).expr; + utils.remove_from_list(\%leftover_locals, var); + end + return {locals:%leftover_locals, statements:=lua "\%body_lua.statements or (\%body_lua.expr..';')"} + + compile [with %assignments %body] to: + %body_lua <- (%body as lua) + %locals <- [] + %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; + utils.remove_from_list(\%leftover_locals, var); + table.insert(\%locals, var); + else + 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 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..";"))}; + +immediately: + # Math Operators + 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) + parse [%x < %y < %z] as: =lua "(function(x,y,z) return x < y and y < z; end)(\%x,\%y,\%z)" + parse [%x <= %y < %z] as: =lua "(function(x,y,z) return x <= y and y < z; end)(\%x,\%y,\%z)" + parse [%x < %y <= %z] as: =lua "(function(x,y,z) return x < y and y <= z; end)(\%x,\%y,\%z)" + parse [%x <= %y <= %z] as: =lua "(function(x,y,z) return x <= y and y <= z; end)(\%x,\%y,\%z)" + parse [%x > %y > %z] as: =lua "(function(x,y,z) return x > y and y > z; end)(\%x,\%y,\%z)" + parse [%x >= %y > %z] as: =lua "(function(x,y,z) return x >= y and y > z; end)(\%x,\%y,\%z)" + parse [%x > %y >= %z] as: =lua "(function(x,y,z) return x > y and y >= z; end)(\%x,\%y,\%z)" + parse [%x >= %y >= %z] as: =lua "(function(x,y,z) return x >= y and y >= z; end)(\%x,\%y,\%z)" + # TODO: optimize for common case where x,y,z are all either variables or number literals + + # Boolean Operators + 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 {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 {expr:"(- \(% as lua expr))"} + compile [not %] to {expr:"(not \(% as lua expr))"} + +# Update operators +immediately: + parse [%var + <- %, %var +<- %] as: %var <- (%var + %) + parse [%var - <- %, %var -<- %] as: %var <- (%var - %) + parse [%var * <- %, %var *<- %] as: %var <- (%var * %) + parse [%var / <- %, %var /<- %] as: %var <- (%var / %) + parse [%var ^ <- %, %var ^<- %] as: %var <- (%var ^ %) + parse [%var and <- %] as: %var <- (%var and %) + parse [%var or <- %] as: %var <- (%var or %) + parse [wrap %var around %] as: %var <- (%var wrapped around %) diff --git a/core/text.nom b/core/text.nom new file mode 100644 index 0000000..8213988 --- /dev/null +++ b/core/text.nom @@ -0,0 +1,56 @@ +#.. + This file contains some definitions of text escape sequences, including ANSI console + color codes. + +use "core/metaprogramming.nom" + +# Text functions +action [%texts joined with %glue]: + lua> ".." + local text_bits = {} + for i,bit in ipairs(\%texts) do text_bits[i] = stringify(bit) end + return table.concat(text_bits, \%glue) +parse [joined %texts, %texts joined] as: %texts joined with "" + +compile [capitalized %text, %text capitalized] to {..} + expr:"((\(%text as lua expr)):gsub('%l', string.upper, 1))" + +compile [%text with %sub instead of %patt, %text s/%patt/%sub] to {..} + expr:"((\(%text as lua expr)):gsub(\(%patt as lua expr), \(%sub as lua expr)))" + +# TODO: figure out whether indent/dedent should affect first line +compile [indented %text, %text indented] to {expr:"((\%text):gsub('\\n','\\n'..(' ')))"} +compile [dedented %obj, %obj dedented] to {expr:"nomsu:dedent(\(%obj as lua expr))"} +compile [%text indented %n times] to {expr:"((\%text):gsub('\\n','\\n'..(' '):rep(\%n)))"} + +# 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", + }; + for name, e in pairs(escapes) do + local lua = "'"..e.."'"; + nomsu:define_compile_action(name, \(!! code location !!), function() return {expr=lua}; end); + end + local colors = { + ["reset color"]="\\\\27[0m", bright="\\\\27[1m", dim="\\\\27[2m", underscore="\\\\27[4m", + 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", + + ["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/lib/collections.nom b/lib/collections.nom deleted file mode 100644 index a8cb20f..0000000 --- a/lib/collections.nom +++ /dev/null @@ -1,204 +0,0 @@ -#.. - This file contains code that supports manipulating and using collections like lists - and dictionaries. - -use "lib/metaprogramming.nom" -use "lib/control_flow.nom" -use "lib/operators.nom" - -# List/dict functions: - -# Indexing -immediately: - compile [..] - %index st to last in %list, %index nd to last in %list, %index rd to last in %list - %index th to last in %list - ..to {expr:"utils.nth_to_last(\(%list as lua expr), \(%index as lua expr))"} - -immediately: - parse [first in %list, first %list] as: 1 st in %list - parse [last in %list, last %list] as: 1 st to last in %list - -# Membership testing -immediately: - action [%item is in %list, %list contains %item, %list has %item] - for %key = %value in %list - if (%key is %item): return (yes) - return (no) - - action [..] - %item isn't in %list, %item is not in %list - %list doesn't contain %item, %list does not contain %item - %list doesn't have %item, %list does not have %item - ..: - for %key = %value in %list - if (%key is %item): return (no) - return (yes) - -immediately: - # Note: it's important to have the space after "[" to prevent confusion if %index is a string - compile [%list has key %index, %list has index %index] to {..} - expr: ".." - ((\(%list as lua expr))[ \(%index as lua expr)] ~= nil) - - # Note: it's important to have the space after "[" to prevent confusion if %index is a string - compile [..] - %list doesn't have key %index, %list does not have key %index - %list doesn't have index %index, %list does not have index %index - ..to {expr:"((\(%list as lua expr))[ \(%index as lua expr)] == nil)"} - - compile [length of %list, size of %list, size %list, number of %list, len %list] to: - {expr:"utils.size(\(%list as lua expr))"} - - compile [append %item to %list, add %item to %list] to: - {statements:"table.insert(\(%list as lua expr), \(%item as lua expr))"} - - compile [pop from %list, remove last from %list] to: - {statements:"table.remove(\(%list as lua expr))"} - - compile [remove index %index from %list] to: - {statements:"table.remove(\(%list as lua expr), \(%index as lua expr))"} - -# List Comprehension -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 {..} - 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 %index from %start to %stop via %step - %expression for %index from %start to %stop by %step - ..to: - assume ((%index's "type") is "Var") or barf ".." - List comprehension has the wrong type for the loop variable. Expected Var, but got: \(%index's "type") - return {..} - expr:".." - (function() - local comprehension = {}; - for \(%index as lua expr)=\(%start as lua expr),\(%stop as lua expr),\(%step as lua expr) do - comprehension[\(%index as lua expr)] = \(%expression as lua expr); - end - return comprehension; - end)() - parse [%expression for all ] as: %expression for % in %iterable - parse [%expression for %var from %start to %stop] as: %expression for %var from %start to %stop via 1 - parse [..] - %expression for all %start to %stop by %step - %expression for all %start to %stop via %step - ..as: %expression for % from %start to %stop via %step - parse [%expression for all %start to %stop] as: %expression for all %start to %stop via 1 - - compile [%expression for %key = %value in %iterable] to: - assume ((%key's "type") is "Var") or barf ".." - 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 {..} - expr: ".." - (function() - local comprehension = {}; - for \(%key as lua expr), \(%value as lua expr) in pairs(\(%iterable as lua expr)) do - table.insert(comprehension, \(%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") - # 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: - assume ((%src_key's "type") is "Var") or barf ".." - 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") - # 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)() - -immediately: - action [%lists flattened]: - %flat <- [] - for %list in %lists: - for %item in %list: - add %item to %flat - return %flat - - parse [entries in %dict] as: {key:%k, value:%v} for %k = %v in %dict - parse [keys in %dict] as: %k for %k = %v in %dict - parse [values in %dict] as: %v for %k = %v in %dict - -# Sorting: -immediately: - compile [sort %items] to {statements:"table.sort(\(%items as lua expr))"} - compile [sort %items by %key_expr] to {..} - statements: ".." - utils.sort(\(%items as lua expr), function(\(\% as lua expr)) - return \(%key_expr as lua expr); - end) - -immediately: - action [%items sorted, sorted %items]: - %copy <- (% for all %items) - sort %copy - return %copy - action [%items sorted by %key]: - %copy <- (% for all %items) - sort %copy by %key - return %copy - - action [unique %items]: - %unique <- [] - %seen <- {} - for all %items: - unless: % in %seen - add % to %unique - (% in %seen) <- (yes) - return %unique - -immediately: - # Metatable stuff - compile [set %dict's metatable to %metatable] to {..} - statements: "setmetatable(\(%dict as lua expr), \(%metatable as lua expr));" - - compile [new counter] to {expr:"setmetatable({}, {__index=function() return 0; end})"} - - compile [new 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 deleted file mode 100644 index 75cf6fd..0000000 --- a/lib/control_flow.nom +++ /dev/null @@ -1,467 +0,0 @@ -#.. - This file contains compile-time actions that define basic control flow structures - like "if" statements and loops. - -use "lib/metaprogramming.nom" -use "lib/text.nom" -use "lib/operators.nom" - -# No-Op -immediately: - compile [do nothing] to {statements:""} - -# Conditionals -immediately: - 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: - %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)" - because that breaks if %if_expr is falsey, e.g. "x < 5 and false or 99" -immediately - compile [..] - %when_true_expr if %condition else %when_false_expr - %when_true_expr if %condition otherwise %when_false_expr - %when_false_expr unless %condition else %when_true_expr - %when_false_expr unless %condition then %when_true_expr - ..to: - #.. 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 {..} - 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 {..} - 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 ---, *** %label ***] to {..} - statements:"::label_\(%label as lua identifier)::;" - compile [go to %label] to {..} - statements:"goto label_\(%label as lua identifier);" - -# Basic loop control -immediately: - compile [do next] to {statements:"continue;"} - compile [stop] to {statements:"break;"} - -# Helper function -immediately: - 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 - -# While loops -immediately: - compile [do next repeat] to {statements:"goto continue_repeat;"} - compile [stop repeating] to {statements:"goto stop_repeat;"} - compile [repeat while %condition %body] to - %body_lua <- (%body as lua) - %body_statements <- ((%body_lua's "statements") or "\(%body_lua's "expr");") - if %body has subtree % where: - ((%'s "type") = "FunctionCall") and ((%'s "stub") is "do next repeat") - ..: %body_statments +<- "\n::continue_repeat::;" - %code <- ".." - while \(%condition as lua expr) do - \%body_statements - end --while-loop - 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 - 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: - %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 repeat") - ..: %body_statements +<- "\n::continue_repeat::;" - %code <- ".." - for i=1,\(%n as lua expr) do - \%body_statements - end --numeric for-loop - 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 - return {statements:%code, locals:%body_lua's "locals"} - -# For loop control flow: -immediately: - compile [stop %var] to {..} - statements:"goto stop_\(%var as lua identifier);" - compile [do next %var] to {..} - statements:"goto continue_\(%var as lua identifier);" - -# 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: - %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 %") and - ((3rd in (%'s "value"))'s "value") is (%var's "value") - ..: %body_statements +<- "\n::continue_\(%var as lua identifier)::;" - - # This uses Lua's approach of only allowing loop-scoped variables in a loop - assume ((%var's "type") is "Var") or barf "Loop expected variable, not: \(%var's source code)" - %code <- ".." - for \(%var as lua expr)=\(%start as lua expr),\(%stop as lua expr),\(%step as lua expr) do - \%body_statements - end --numeric for-loop - - if %body has subtree % where: - ((%'s "type") = "FunctionCall") and: - ((%'s "stub") is "stop %") and: - ((2nd in (%'s "value"))'s "value") is (%var's "value") - ..: - %code <- ".." - do -- scope for stopping for-loop - \%code - ::stop_\(%var as lua identifier)::; - end -- end of scope for stopping for-loop - - 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 - parse [..] - for all %start to %stop by %step %body - for all %start to %stop via %step %body - ..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 (lua's "ipairs()") -immediately: - 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") is "do next %") and - ((3rd in (%'s "value"))'s "value") is (%var's "value") - ..: %body_statements +<- "\n::continue_\(%var as lua identifier)::;" - # This uses Lua's approach of only allowing loop-scoped variables in a loop - assume ((%var's "type") is "Var") or barf "Loop expected variable, not: \(%var's source code)" - %code <- ".." - for i,\(%var as lua expr) in ipairs(\(%iterable as lua expr)) do - \%body_statements - end --foreach-loop - if %body has subtree % where: - ((%'s "type") = "FunctionCall") and - ((%'s "stub") is "stop %") and - ((2nd in (%'s "value"))'s "value") is (%var's "value") - ..: - %code <- ".." - do -- scope for stopping for-loop - \%code - ::stop_\(%var as lua identifier)::; - end -- end of scope for stopping for-loop - 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: - %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 %") and - ((3rd in (%'s "value"))'s "value") is (%key's "value") - ..: %body_statements +<- "\n::continue_\(%key as lua identifier)::;" - - if %body has subtree % where: - ((%'s "type") = "FunctionCall") and - ((%'s "stub") is "do next %") and - ((3rd in (%'s "value"))'s "value") is (%value's "value") - ..: %body_statements +<- "\n::continue_\(%value as lua identifier)::;" - - # This uses Lua's approach of only allowing loop-scoped variables in a loop - assume ((%key's "type") is "Var") or barf "Loop expected variable, not: \(%key's source code)" - assume ((%value's "type") is "Var") or barf "Loop expected variable, not: \(%value's source code)" - %code <- ".." - for \(%key as lua expr),\(%value as lua expr) in pairs(\(%iterable as lua expr)) do - \%body_statements - end --foreach-loop - - %stop_labels <- "" - if %body has subtree % where: - ((%'s "type") = "FunctionCall") and - ((%'s "stub") is "stop %") and - ((2nd in (%'s "value"))'s "value") is (%key's "value") - ..: %stop_labels +<- "\n::stop_\(%key as lua identifier)::;" - - if %body has subtree % where: - ((%'s "type") = "FunctionCall") and - ((%'s "stub") is "stop %") and - ((2nd in (%'s "value"))'s "value") is (%value's "value") - ..: %stop_labels +<- "\n::stop_\(%value as lua identifier)::;" - - if: %stop_labels is not "" - %code <- ".." - do -- scope for stopping for % = % loop - \%code\%stop_labels - end - return {statements:%code, locals:%body_lua's "locals"} - -# Switch statement/multi-branch if -immediately: - 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. - with [..] - %tokens <- (%func_call's "value") - %star <- (1st in %tokens) - %condition <- (2nd in %tokens) - %action <- (3rd in %tokens) - ..: - assume (=lua "\%star and \%star.type == 'Word' and \%star.value == '*'") or barf ".." - Invalid format for 'when' statement. Lines must begin with '*' - assume %condition or barf ".." - Invalid format for 'when' statement. Lines must begin with '*' and have a condition or the word "else" - if: %action is (nil) - lua> "table.insert(\%fallthroughs, \(%condition as lua expr));" - 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" - %code +<- ".." - - else - \%action_statements - %seen_else <- (yes) - ..else: - assume (not %seen_else) or barf "'else' clause needs to be last in 'when' block" - lua> "table.insert(\%fallthroughs, \(%condition as lua expr));" - %condition_code <- (%fallthroughs joined with " or ") - %code +<- ".." - - \("if" if %is_first else "elseif") \%condition_code then - \%action_statements - - %fallthroughs <- [] - %is_first <- (no) - - 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 <- "" - %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. - %tokens <- (%func_call's "value") - with [%star<-(1st in %tokens), %condition<-(2nd in %tokens), %action<-(3rd in %tokens)]: - assume (=lua "\%star and \%star.type == 'Word' and \%star.value == '*'") or barf ".." - Invalid format for 'when' statement. Lines must begin with '*' - assume %condition or barf ".." - Invalid format for 'when' statement. Lines must begin with '*' and have a condition or the word "else" - if: %action is (nil) - lua> "table.insert(\%fallthroughs, \(%condition as lua expr))" - 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" - %code +<- ".." - - else - \%action_statements - end - %seen_else <- (yes) - ..else: - assume (not %seen_else) or barf "'else' clause needs to be last in 'when % = ?' block" - lua> "table.insert(\%fallthroughs, \(%condition as lua expr));" - for %i = % in %fallthroughs - if: ((%'s "type") is "Text") or ((%'s "type") is "Number") - (%i'th in %fallthroughs) <- "branch_value == \%" - ..else - (%i'th in %fallthroughs) <- "utils.equivalent(branch_value, \%)" - %clause <- (%fallthroughs joined with " or ") - %code +<- ".." - - \("if" if %is_first else "elseif") \%clause then - \%action_statements - - %fallthroughs <- [] - %is_first <- (no) - - assume (%fallthroughs = []) or barf "Unfinished fallthrough conditions in 'when' block" - assume (%code is not "") or barf "No body for 'when % = ?' block!" - unless %seen_else - %code +<- "\nend" - %code <- ".." - do --when % = ? - local branch_value = \(%branch_value as lua expr);\ - ..\%code - end --when % = ? - lua> "utils.deduplicate(\%locals);" - return {statements:%code, locals:%locals} - -# Try/except -immediately: - compile [..] - try %action and if it succeeds %success or if it barfs %fallback - try %action and if it barfs %fallback or if it succeeds %success - ..to: - %locals <- [] - %action_lua <- (%action as lua) - %success_lua <- (%success as lua) - %fallback_lua <- (%fallback as lua) - %fallback <- (%fallback as lua) - for %block in [%action_lua, %success_lua, %fallback_lua]: - for %local in ((%block's "locals") or []): - lua> "table.insert(\%locals, \%local);" - lua> "utils.deduplicate(\%locals);" - return {..} - locals: %locals - statements: ".." - do - local fell_through = false; - local ok, ret = pcall(function() - \((%action_lua's "statements") or "\(%action_lua's "expr");") - fell_through = true; - end); - if ok then - \((%success_lua's "statements") or "\(%success_lua's "expr");") - end - if not ok then - \((%fallback_lua's "statements") or "\(%fallback_lua's "expr");") - elseif not fell_through then - return ret; - end - end - parse [try %action] as: - try %action and if it succeeds: do nothing - ..or if it barfs: do nothing - parse [try %action and if it barfs %fallback] as: - try %action and if it succeeds: do nothing - ..or if it barfs %fallback - parse [try %action and if it succeeds %success] as: - try %action and if it succeeds %success or if it barfs: do nothing - -# Do/finally: -immediately: - 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: - %action <- (%action as lua) - %final_action <- (%final_action as lua) - %locals <- [] - for %sub_locals in [%action's "locals", %final_action's "locals"]: - for %local in %sub_locals: - 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/core.nom b/lib/core.nom deleted file mode 100644 index 8843104..0000000 --- a/lib/core.nom +++ /dev/null @@ -1,10 +0,0 @@ -#.. - This file imports all the commonly used library files, which can be convenient for - avoiding retyping the whole list. - -use "lib/metaprogramming.nom" -use "lib/text.nom" -use "lib/operators.nom" -use "lib/control_flow.nom" -use "lib/math.nom" -use "lib/collections.nom" diff --git a/lib/file_hash.nom b/lib/file_hash.nom new file mode 100644 index 0000000..4d99e39 --- /dev/null +++ b/lib/file_hash.nom @@ -0,0 +1,46 @@ +use "core" + +%hash_to_filename <- {} + +lua> ".." + local Hash = require("openssl.digest"); + local function sha1(x) + local hash = Hash.new("sha1"):final(x); + local hex = hash:gsub('.', function(c) return string.format('%02x', string.byte(c)) end) + return hex + end + local lfs = require('lfs'); + local function attrdir(path) + for filename in lfs.dir(path) do + if filename ~= "." and filename ~= ".." and filename:sub(1,1) ~= "." then + local filename = path..'/'..filename + local attr = lfs.attributes(filename); + if attr.mode == "directory" then + attrdir(filename); + elseif filename:match(".*%.nom") then + local file = io.open(filename); + local hash = sha1(file:read("*a")); + file:close(); + \%hash_to_filename[hash] = filename + end + end + end + end + attrdir("."); + +action [sha1 %]: + lua> "return sha1(\%);" + +action [file with hash %hash]: + %file <- (%hash in %hash_to_filename) + assume %file or barf "File with SHA1 hash \%hash not found!" + return %file + +action [hash of file %filename]: + lua> ".." + local f = io.open(\%filename); + local hash = sha1(f:read("*a")); + f:close(); + return hash; + +parse [use file with hash %hash] as: use (file with hash %hash) diff --git a/lib/habit_breaker.nom b/lib/habit_breaker.nom new file mode 100644 index 0000000..cc423e7 --- /dev/null +++ b/lib/habit_breaker.nom @@ -0,0 +1,54 @@ +use "core" + +immediately: + compile [correct %wrong to %right] to: + lua> ".." + local signature = {}; + for i, action in ipairs(\%wrong.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 \%right.type == "Block" then + local lines = {}; + for i, line in ipairs(\%right.value) do lines[i] = nomsu:dedent(line:get_src()); end + template = repr(table.concat(lines, "\\n")); + else + template = repr(nomsu:dedent(\%right:get_src())); + end + local replacements = {}; + for i, a in ipairs(stub_args[1]) do replacements[i] = a.."="..a; end + replacements = "{"..table.concat(replacements, ", ").."}"; + 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..[[); + error("Did you mean '"..nomsu:tree_to_nomsu(replacement).."'?"); + end); + ]]}; + +correct [%a == %b] to: %a = %b +correct [%a = %b] to: %a <- %b +correct [%a == %b] to: %a is %b +correct [%a ~= %b, %a != %b, %a <> %b] to: %a is not %b +correct [%a === %b] to: (%a's id) is (%b's id) +correct [%a !== %b] to: (%a's id) is not (%b's id) +correct [%a mod %b] to: %a wrapped around %b +correct [function %names %body, def %names %body] to: action %names %body +correct [switch %branch_value %body] to: when %branch_value = ? %body +correct [None, Null] to: nil +correct [True, true] to: yes +correct [False, false] to: no +correct [pass] to: do nothing +correct [%a || %b] to: %a or %b +correct [%a && %b] to: %a and %b +correct [continue] to: do next +correct [break] to: stop +correct [let %thing = %value in %action] to: with [%thing <- %value] %action +correct [print %] to: say % +correct [error!, panic!, fail!, abort!] to: barf! +correct [error %, panic %, fail %, abort %] to: barf % +correct [assert %condition %message] to: assume %condition or barf %message +correct [%cond ? %if_true %if_false] to: %if_true if %cond else %if_false diff --git a/lib/math.nom b/lib/math.nom deleted file mode 100644 index 70e7de4..0000000 --- a/lib/math.nom +++ /dev/null @@ -1,95 +0,0 @@ -#.. - This file defines some common math literals and functions - -use "lib/metaprogramming.nom" -use "lib/text.nom" -use "lib/operators.nom" -use "lib/control_flow.nom" - -# Literals: -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.exp(1)"} - -# Functions: -compile [% as a number] to {expr:"tonumber(\(% as lua expr))"} -compile [absolute value %, | % |, abs %] to {expr:"math.abs(\(% as lua expr))"} -compile [square root %, √%, sqrt %] to {expr:"math.sqrt(\(% as lua expr))"} -compile [sine %, sin %] to {expr:"math.sin(\(% as lua expr))"} -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: - unless: (%items' "type") is "List" - return {expr:"utils.all(\(%items as lua expr))"} - %clauses <- [] - for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" - return {expr:"(\(%clauses joined with " and "))"} -parse [not all of %items, not all %items] as: not (all of %items) -compile [any of %items, any %items] to: - unless: (%items' "type") is "List" - return {expr:"utils.any(\(%items as lua expr))"} - %clauses <- [] - for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" - return {expr:"(\(%clauses joined with " or "))"} -parse [none of %items, none %items] as: not (any of %items) -compile [sum of %items, sum %items] to: - unless: (%items' "type") is "List" - return {expr:"utils.sum(\(%items as lua expr))"} - %clauses <- [] - for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" - return {expr:"(\(%clauses joined with " + "))"} -compile [product of %items, product %items] to: - unless: (%items' "type") is "List" - return {expr:"utils.product(\(%items as lua expr))"} - %clauses <- [] - for all (%items' "value"): lua> "table.insert(\%clauses, \(% as lua expr));" - return {expr:"(\(%clauses joined with " * "))"} -action [avg of %items, average of %items]: - =lua "(utils.sum(\%items)/#\%items)" -compile [min of %items, smallest of %items, lowest of %items] to {..} - 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 %]: - lua> ".." - 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 {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 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 deleted file mode 100644 index 544ae59..0000000 --- a/lib/metaprogramming.nom +++ /dev/null @@ -1,200 +0,0 @@ -#.. - This File contains actions for making actions and compile-time actions and some helper - functions to make that easier. - -# Compile-time action to make compile-time actions: -immediately: - lua> ".." - 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 - 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 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_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: - 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 - 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:get_src())); - end - local replacements = {}; - for i, a in ipairs(stub_args[1]) do replacements[i] = a.."="..a; end - replacements = "{"..table.concat(replacements, ", ").."}"; - 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> ".." - local fn = ACTION[\%stub]; - local metadata = ACTION_METADATA[fn]; - for i=#metadata.aliases,1,-1 do - metadata.arg_orders[metadata.aliases[i]] = nil; - table.remove(metadata.aliases, i); - end - ACTION[\%stub] = nil; - -immediately: - action [%tree as lua]: - =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); - 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)" - -immediately: - compile [%tree's source code, %tree' source code] to {expr:"\(%tree as lua expr):get_src()"} - - compile [repr %obj] to {expr:"repr(\(%obj as lua expr))"} - compile [type of %obj] to {expr:"type(\(%obj as lua expr))"} - -immediately: - compile [nomsu] to {expr:"nomsu"} - compile [%var as lua identifier] to {expr:"nomsu:var_to_lua_identifier(\(%var as lua expr))"} - -action [action %names metadata]: - =lua "ACTION_METADATA[ACTION[\%names]]" - -# Get the source code for a function -action [help %action]: - lua> ".." - local metadata = \(action %action metadata); - if not metadata then - print("Action not found: "..repr(\%action)); - else - print(metadata.src or ""); - end - -# Compiler tools -immediately: - compile [run %code] to {expr: "nomsu:run(\(%code as lua expr), '\(!! code location !!)')"} - parse [enable debugging] as: lua> "nomsu.debug = true;" - parse [disable debugging] as: lua> "nomsu.debug = false;" - -immediately: - compile [say %message] to: - lua> ".." - if \%message.type == "Text" then - return {statements="print("..\(%message as lua expr)..");"}; - else - return {statements="print(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 -immediately: - compile [barf] to {statements:"error(nil, 0);"} - compile [barf %msg] to {statements:"error(\(%msg as lua expr), 0);"} - compile [assume %condition] to: - lua> "local \%assumption = 'Assumption failed: '..\%condition:get_src();" - return {..} - statements:".." - if not \(%condition as lua expr) then - error(\(repr %assumption), 0); - end - compile [assume %condition or barf %msg] to {..} - statements:".." - if not \(%condition as lua expr) then - error(\(%msg as lua expr), 0); - end - -# Literals -immediately: - compile [yes] to {expr:"true"} - compile [no] to {expr:"false"} - compile [nothing, nil, null] to {expr:"nil"} - diff --git a/lib/object.nom b/lib/object.nom index 4d8932a..2f2f3ff 100644 --- a/lib/object.nom +++ b/lib/object.nom @@ -1,4 +1,4 @@ -use "lib/core.nom" +use "core" compile [@%var] to: lua> ".." diff --git a/lib/object2.nom b/lib/object2.nom new file mode 100644 index 0000000..5e77a05 --- /dev/null +++ b/lib/object2.nom @@ -0,0 +1,135 @@ +use "core" + +compile [@] to {expr:"self"} + +compile [@%var] to: + lua> ".." + local key_lua = repr(\%var.value); + 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 {expr="self."..key_attr}; + elseif key_lua:sub(1,1) == "[" then + key_lua = " "..key_lua.." "; + end + return {expr="self["..key_lua.."]"}; + +compile [@%var <- %val] to: + lua> ".." + local val_lua = \(%val as lua expr); + local key_lua = repr(\%var.value); + 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 {statements="self."..key_attr.." = "..val_lua..";"}; + elseif key_lua:sub(1,1) == "[" then + key_lua = " "..key_lua.." "; + end + return {statements="self["..key_lua.."] = "..val_lua..";"}; + +compile [as %instance %body] to: + %body_lua <- (%body as lua) + return {..} + statements: ".." + do + local self = \(%instance as lua expr); + local global_actions = ACTION; + local ACTION = setmetatable({}, {__index=function(_,key) + local method = self[key]; + if method then return (function(...) return method(self, ...); end); end + return global_actions[key]; + end}); + \((%body_lua's "statements") or "\(%body_lua's "expr");") + end + locals: %body_lua's "locals" + +compile [define object %classname %class_body] to: + %class_identifier <- (=lua "nomsu:var_to_lua_identifier(\(%classname as value)):sub(2,-1)") + if: %class_identifier is "" + %class_identifier <- "class" + %methods <- [] + for %line in (%class_body's "value"): + if: (%line's "type") is "Comment" + do next %line + assume (((%line's "type") == "FunctionCall") and ((%line's "stub") == "action % %")) + ..or barf "Only action definitions are supported inside 'define object % %', not \(%line's "src")" + %actions <- (2nd in (%line's "value")) + %body <- (3rd in (%line's "value")) + 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({"self", unpack(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); + + local compiled_args = {}; + for i, arg in ipairs(stub_args[1]) do + compiled_args[i] = "nomsu:tree_to_lua("..arg..").expr"; + end + compiled_args = table.concat(compiled_args, "..', '.."); + table.insert(\%methods, ([==[ + %s[ %s] = function(%s) + %s + end + ]==]):format( + \%class_identifier, repr(stubs[1]), lua_fn_args, + body_code)); + + return {..} + statements:".." + do -- \%class_identifier + -- Create the class object: + local \%class_identifier = setmetatable({ + name=\(%classname as lua expr), instances=setmetatable({}, {__mode="k"}), + }, { + __tostring=function(c) return c.name; end, + __call=function(cls, inst) + inst = inst or {}; + inst.id = tostring(inst):match('table: (.*)'); + setmetatable(inst, cls.instance_metatable); + cls.instances[inst] = true; + if inst['set % up'] then + inst['set % up'](inst); + end + return inst; + end, + }); + \%class_identifier.class = \%class_identifier; + + -- Define the methods: + \(%methods joined with "\n") + + -- Define class methods for instantiating and accessing instances: + \%class_identifier.instance_metatable = { + __index=\%class_identifier, + __tostring=\%class_identifier['% as text'] or function(inst) + return "<"..inst.class.name..": "..inst.id..">"; + end, + }; + nomsu:define_action("instances of "..\%class_identifier.name, "lib/class.nom", function() + return utils.keys(\%class_identifier.instances); + end, ""); + nomsu:define_action("new "..\%class_identifier.name.." %instance", "lib/class.nom", function(_instance) + return \%class_identifier(_instance); + end, ""); + nomsu:define_action("new "..\%class_identifier.name, "lib/class.nom", function() + return \%class_identifier({}); + end, ""); + end -- End of definition of \%class_identifier + \("\n\n") + diff --git a/lib/operators.nom b/lib/operators.nom deleted file mode 100644 index 5d5a7a8..0000000 --- a/lib/operators.nom +++ /dev/null @@ -1,209 +0,0 @@ -#.. - This file contains definitions of operators like "+" and "and". - -use "lib/metaprogramming.nom" - -# Indexing: -immediately: - #.. NOTE!!! It's critical that there are spaces around %key if it's a string, - otherwise, Lua will get confused and interpret %obj[[[foo]]] as %obj("[foo]") - instead of %obj[ "foo" ]. - It's also critical to have parens around %obj, otherwise Lua is too dumb to - realize that {x=1}["x"] is the same as ({x=1})["x"] or that - {x=1}.x is the same as ({x=1}).x - compile [..] - %obj' %key, %obj's %key, %key in %obj, %key'th in %obj, %key of %obj, - %key st in %obj, %key nd in %obj, %key rd in %obj, %key th in %obj, - ..to: - 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 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 {expr=obj_lua.."."..key_attr}; - elseif key_lua:sub(1,1) == "[" then - key_lua = " "..key_lua.." "; - end - return {expr=obj_lua.."["..key_lua.."]"}; - -# Comparison Operators -immediately: - compile [%x < %y] to {expr:"(\(%x as lua expr) < \(%y as lua expr))"} - compile [%x > %y] to {expr:"(\(%x as lua expr) > \(%y as lua expr))"} - compile [%x <= %y] to {expr:"(\(%x as lua expr) <= \(%y as lua expr))"} - compile [%x >= %y] to {expr:"(\(%x as lua expr) >= \(%y as lua expr))"} - # TODO: optimize case of [%x,%y] = [1,2] - compile [%a is %b, %a = %b, %a == %b] to: - lua> ".." - local safe = {Text=true, Number=true}; - 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 {expr="("..a_lua.." == "..b_lua..")"}; - else - 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 {expr="("..a_lua.." ~= "..b_lua..")"}; - else - 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 {expr:"nomsu.ids[\(% as lua expr)]"} - -# Variable assignment operator -immediately: - 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)" - -immediately: - # Simultaneous mutli-assignments like: x,y,z = 1,x,3; - compile [<- %assignments] to: - %locals <- [] - %targets <- [] - %values <- [] - assume ((%assignments' "type") is "Dict") or barf ".." - Expected a Dict for the assignments part of '<- %' statement, not \(%assignments' source code) - lua> ".." - for i, item in ipairs(\%assignments.value) do - local target, value = item.dict_key, item.dict_value; - local target_lua = nomsu:tree_to_lua(target); - if not target_lua.expr then error("Invalid target for assignment: "..target:get_src()); end - local value_lua = nomsu:tree_to_lua(value); - if not value_lua.expr then error("Invalid value for assignment: "..value:get_src()); end - if target.type == "Var" then - table.insert(\%locals, target_lua.expr); - end - table.insert(\%targets, target_lua.expr); - table.insert(\%values, value_lua.expr); - end - utils.deduplicate(\%locals); - return {locals=\%locals, statements=(table.concat(\%targets, ", ").." = "..table.concat(\%values, ", ")..";")}; - -immediately: - compile [export %var <- %value] to: - %var_lua <- (%var as lua) - 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");"} - - compile [exporting %exported %body] to: - %body_lua <- (%body as lua) - %leftover_locals <- (=lua "{unpack(\%body_lua.locals or {})}") - assume ((%exported's "type") = "List") or barf ".." - Expected a List for the export part of 'exporting' statement, not \(%exported's source code) - lua> ".." - for i, item in ipairs(\%exported.value) do - if item.type ~= "Var" then - error("'exporting' statement expects Vars, not: "..item:get_src()); - end - local var = nomsu:tree_to_lua(item).expr; - utils.remove_from_list(\%leftover_locals, var); - end - return {locals:%leftover_locals, statements:=lua "\%body_lua.statements or (\%body_lua.expr..';')"} - - compile [with %assignments %body] to: - %body_lua <- (%body as lua) - %locals <- [] - %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; - utils.remove_from_list(\%leftover_locals, var); - table.insert(\%locals, var); - else - 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 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..";"))}; - -immediately: - # Math Operators - 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) - parse [%x < %y < %z] as: =lua "(function(x,y,z) return x < y and y < z; end)(\%x,\%y,\%z)" - parse [%x <= %y < %z] as: =lua "(function(x,y,z) return x <= y and y < z; end)(\%x,\%y,\%z)" - parse [%x < %y <= %z] as: =lua "(function(x,y,z) return x < y and y <= z; end)(\%x,\%y,\%z)" - parse [%x <= %y <= %z] as: =lua "(function(x,y,z) return x <= y and y <= z; end)(\%x,\%y,\%z)" - parse [%x > %y > %z] as: =lua "(function(x,y,z) return x > y and y > z; end)(\%x,\%y,\%z)" - parse [%x >= %y > %z] as: =lua "(function(x,y,z) return x >= y and y > z; end)(\%x,\%y,\%z)" - parse [%x > %y >= %z] as: =lua "(function(x,y,z) return x > y and y >= z; end)(\%x,\%y,\%z)" - parse [%x >= %y >= %z] as: =lua "(function(x,y,z) return x >= y and y >= z; end)(\%x,\%y,\%z)" - # TODO: optimize for common case where x,y,z are all either variables or number literals - - # Boolean Operators - 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 {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 {expr:"(- \(% as lua expr))"} - compile [not %] to {expr:"(not \(% as lua expr))"} - -# Update operators -immediately: - parse [%var + <- %, %var +<- %] as: %var <- (%var + %) - parse [%var - <- %, %var -<- %] as: %var <- (%var - %) - parse [%var * <- %, %var *<- %] as: %var <- (%var * %) - parse [%var / <- %, %var /<- %] as: %var <- (%var / %) - parse [%var ^ <- %, %var ^<- %] as: %var <- (%var ^ %) - parse [%var and <- %] as: %var <- (%var and %) - parse [%var or <- %] as: %var <- (%var or %) - parse [wrap %var around %] as: %var <- (%var wrapped around %) diff --git a/lib/text.nom b/lib/text.nom deleted file mode 100644 index 2a111fe..0000000 --- a/lib/text.nom +++ /dev/null @@ -1,56 +0,0 @@ -#.. - This file contains some definitions of text escape sequences, including ANSI console - color codes. - -use "lib/metaprogramming.nom" - -# Text functions -action [%texts joined with %glue]: - lua> ".." - local text_bits = {} - for i,bit in ipairs(\%texts) do text_bits[i] = stringify(bit) end - return table.concat(text_bits, \%glue) -parse [joined %texts, %texts joined] as: %texts joined with "" - -compile [capitalized %text, %text capitalized] to {..} - expr:"((\(%text as lua expr)):gsub('%l', string.upper, 1))" - -compile [%text with %sub instead of %patt, %text s/%patt/%sub] to {..} - expr:"((\(%text as lua expr)):gsub(\(%patt as lua expr), \(%sub as lua expr)))" - -# TODO: figure out whether indent/dedent should affect first line -compile [indented %text, %text indented] to {expr:"((\%text):gsub('\\n','\\n'..(' ')))"} -compile [dedented %obj, %obj dedented] to {expr:"nomsu:dedent(\(%obj as lua expr))"} -compile [%text indented %n times] to {expr:"((\%text):gsub('\\n','\\n'..(' '):rep(\%n)))"} - -# 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", - }; - for name, e in pairs(escapes) do - local lua = "'"..e.."'"; - nomsu:define_compile_action(name, \(!! code location !!), function() return {expr=lua}; end); - end - local colors = { - ["reset color"]="\\\\27[0m", bright="\\\\27[1m", dim="\\\\27[2m", underscore="\\\\27[4m", - 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", - - ["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/lib/training_wheels.nom b/lib/training_wheels.nom index 30c20fc..e5c02fb 100644 --- a/lib/training_wheels.nom +++ b/lib/training_wheels.nom @@ -2,7 +2,7 @@ This file contains a set of definitions that bring some familiar language features from other languages into nomsu (e.g. "==" and "continue") -use "lib/core.nom" +use "core" parse [%a = %b] as: %a <- %b parse [%a == %b] as: %a is %b diff --git a/nomsu.lua b/nomsu.lua index a6d75fe..b1f49d0 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -379,6 +379,17 @@ do return ret, lua_code end, run_file = function(self, filename) + local file_attributes = assert(lfs.attributes(filename), "File not found: " .. tostring(filename)) + if file_attributes.mode == "directory" then + for short_filename in lfs.dir(filename) do + local full_filename = filename .. '/' .. short_filename + local attr = lfs.attributes(full_filename) + if attr.mode ~= "directory" and short_filename:match(".*%.nom") then + self:run_file(full_filename) + end + end + return + end if filename:match(".*%.lua") then local file = io.open(filename) local contents = file:read("*a") @@ -407,23 +418,10 @@ do end, require_file = function(self, filename) local loaded = self.environment.LOADED - local file_attributes = lfs.attributes(filename) - if file_attributes.mode == "directory" then - for short_filename in lfs.dir(filename) do - local full_filename = filename .. '/' .. short_filename - local attr = lfs.attributes(full_filename) - if attr.mode ~= "directory" and short_filename:match(".*%.nom") then - if not loaded[full_filename] then - loaded[full_filename] = self:run_file(full_filename) or true - end - end - end - else - if not loaded[filename] then - loaded[filename] = self:run_file(filename) or true - end - return loaded[filename] + if not loaded[filename] then + loaded[filename] = self:run_file(filename) or true end + return loaded[filename] end, run_lua = function(self, lua_code) local run_lua_fn, err = load(lua_code, nil, nil, self.environment) @@ -1526,13 +1524,12 @@ if arg and debug.getinfo(2).func ~= require then if args.input:match(".*%.lua") then local retval = dofile(args.input)(nomsu, { }) else - local input + local retval, code if args.input == '-' then - input = io.read('*a') + retval, code = nomsu:run(io.read('*a'), 'stdin') else - input = io.open(args.input):read("*a") + retval, code = nomsu:run_file(args.input) end - local retval, code = nomsu:run(input, args.input) if compiled_output then compiled_output:write("local IMMEDIATE = true;\n") compiled_output:write(code) @@ -1543,7 +1540,7 @@ if arg and debug.getinfo(2).func ~= require then end end if args.flags["-i"] then - nomsu:run('use "lib/core.nom"', "stdin") + nomsu:run('use "core"', "stdin") while true do io.write(colored.bright(colored.yellow(">> "))) local buff = "" diff --git a/nomsu.moon b/nomsu.moon index 20372bb..a214857 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -293,6 +293,15 @@ class NomsuCompiler return ret, lua_code run_file: (filename)=> + file_attributes = assert(lfs.attributes(filename), "File not found: #{filename}") + if file_attributes.mode == "directory" + for short_filename in lfs.dir(filename) + full_filename = filename..'/'..short_filename + attr = lfs.attributes(full_filename) + if attr.mode ~= "directory" and short_filename\match(".*%.nom") + @run_file full_filename + return + if filename\match(".*%.lua") file = io.open(filename) contents = file\read("*a") @@ -316,18 +325,9 @@ class NomsuCompiler require_file: (filename)=> loaded = @environment.LOADED - file_attributes = lfs.attributes(filename) - if file_attributes.mode == "directory" - for short_filename in lfs.dir(filename) - full_filename = filename..'/'..short_filename - attr = lfs.attributes(full_filename) - if attr.mode ~= "directory" and short_filename\match(".*%.nom") - if not loaded[full_filename] - loaded[full_filename] = @run_file(full_filename) or true - else - if not loaded[filename] - loaded[filename] = @run_file(filename) or true - return loaded[filename] + if not loaded[filename] + loaded[filename] = @run_file(filename) or true + return loaded[filename] run_lua: (lua_code)=> run_lua_fn, err = load(lua_code, nil, nil, @environment) @@ -974,10 +974,11 @@ if arg and debug.getinfo(2).func != require if args.input\match(".*%.lua") retval = dofile(args.input)(nomsu, {}) else - input = if args.input == '-' - io.read('*a') - else io.open(args.input)\read("*a") - retval, code = nomsu\run(input, args.input) + local retval, code + if args.input == '-' + retval, code = nomsu\run(io.read('*a'), 'stdin') + else + retval, code = nomsu\run_file(args.input) if compiled_output compiled_output\write("local IMMEDIATE = true;\n") compiled_output\write(code) @@ -987,7 +988,7 @@ if arg and debug.getinfo(2).func != require if args.flags["-i"] -- REPL - nomsu\run('use "lib/core.nom"', "stdin") + nomsu\run('use "core"', "stdin") while true io.write(colored.bright colored.yellow ">> ") buff = "" diff --git a/tests/all.nom b/tests/all.nom deleted file mode 100644 index 5b95786..0000000 --- a/tests/all.nom +++ /dev/null @@ -1,28 +0,0 @@ -use "lib/core.nom" - -# Prerequisites: -%var <- 99 -assume (%var = 99) or barf "Var assignment doesn't work." - -try: barf "barf" -..and if it succeeds: barf "try % and if it succeeds failed." - -try: do nothing -..and if it barfs: barf "try % and if it barfs failed." - -immediately - assume (%ONCE is (nil)) or barf "immediately is executing twice" - export %ONCE <- "ONCE" - %foo <- "immediately local" -assume (%ONCE = "ONCE") or barf "Globals don't work." -assume (%foo is (nil)) or barf "Immediately is leaking locals." - -# Run per-file test suites -run file "tests/metaprogramming.nom" -run file "tests/text.nom" -run file "tests/operators.nom" -run file "tests/control_flow.nom" -run file "tests/math.nom" -run file "tests/collections.nom" - -say "All tests passed!" diff --git a/tests/collections.nom b/tests/collections.nom index ec79653..1c82487 100644 --- a/tests/collections.nom +++ b/tests/collections.nom @@ -1,7 +1,7 @@ #.. Tests for the stuff defined in lib/control_flow.nom -use "lib/core.nom" +use "core" assume ((2nd to last in [1,2,3,4,5]) = 4) assume ((first in [1,2]) = 1) @@ -41,3 +41,5 @@ assume ((unique [1,2,1,3,2,3]) = [1,2,3]) for all ["x","y","x","x","y"] (% in %c) +<- 1 assume (%c = {x:3,y:2}) + +say "Collections test passed." diff --git a/tests/control_flow.nom b/tests/control_flow.nom index 71d5f85..4ac3d8a 100644 --- a/tests/control_flow.nom +++ b/tests/control_flow.nom @@ -1,7 +1,8 @@ #.. Tests for the stuff defined in lib/control_flow.nom -use "lib/core.nom" +use "core" + do nothing action [test conditionals] @@ -189,3 +190,5 @@ try %x <- 1 ..and if it barfs: do nothing assume (%x = 1) or barf "do/then always failed" + +say "Control flow test passed." diff --git a/tests/math.nom b/tests/math.nom index 245fe52..4232d81 100644 --- a/tests/math.nom +++ b/tests/math.nom @@ -1,7 +1,7 @@ #.. Tests for the stuff defined in lib/control_flow.nom -use "lib/core.nom" +use "core" assume (all of [inf, pi, tau, golden ratio, e]) or barf "math constants failed" %nan <- (NaN) @@ -15,3 +15,5 @@ assume ..or barf "math functions failed" assume ((463 to the nearest 100) = 500) or barf "rounding failed" assume ((2.6 to the nearest 0.25) = 2.5) or barf "rounding failed" + +say "Math test passed" diff --git a/tests/metaprogramming.nom b/tests/metaprogramming.nom index 37acd3e..360771e 100644 --- a/tests/metaprogramming.nom +++ b/tests/metaprogramming.nom @@ -1,7 +1,7 @@ #.. Tests for the stuff defined in lib/metaprogramming.nom -use "lib/core.nom" +use "core" immediately compile [five] to {expr:"5"} @@ -53,3 +53,5 @@ assume ((nomsu) = (=lua "nomsu")) or barf "nomsu failed" assume (("x" as lua identifier) = (\%x as lua identifier)) or barf "converting to identifier failed." assume ((run "return 99") = 99) or barf "run % failed." + +say "Metaprogramming test passed." diff --git a/tests/object.nom b/tests/object.nom index 7dbaca6..84524e5 100644 --- a/tests/object.nom +++ b/tests/object.nom @@ -1,5 +1,5 @@ -use "lib/core.nom" -use "lib/object.nom" +use "core" +use "lib/object2.nom" immediately: define object "Dog": @@ -20,3 +20,4 @@ as %d: assume ("\(%d's "class")" = "Dog") assume ((%d's "barks") = 3) +say "Object test passed." diff --git a/tests/operators.nom b/tests/operators.nom index e3bd0b9..1a138cb 100644 --- a/tests/operators.nom +++ b/tests/operators.nom @@ -1,7 +1,7 @@ #.. Tests for the stuff defined in lib/operators.nom -use "lib/core.nom" +use "core" assume (({x:5}'s "x") = 5) or barf "indexing doesn't work." try: % <- ({}'s "[[[\n]]]") @@ -74,3 +74,5 @@ assume (%x = 2) or barf "+<- failed" assume (%x = 4) or barf "*<- failed" wrap %x around 3 assume (%x = 1) or barf "wrap around failed" + +say "Operator test passed." diff --git a/tests/text.nom b/tests/text.nom index ea18d42..2e08e45 100644 --- a/tests/text.nom +++ b/tests/text.nom @@ -1,7 +1,7 @@ #.. Tests for the stuff defined in lib/text.nom -use "lib/core.nom" +use "core" assume ((["x","y"] joined with ",") = "x,y") or barf "joined with failed" assume ((["x","y"] joined) = "xy") or barf "joined failed" @@ -11,3 +11,5 @@ assume (("asdf" with "X" instead of "s") = "aXdf") or barf "substitution failed" assume ("\n" = (newline)) or barf "Text literals failed." %x <- "\(green)hello\(reset color)" assume (("x" + "y") = "xy") + +say "Text test passed." -- cgit v1.2.3