From d73cbf0aa5c9081d965e06822f4958aa5c1871e6 Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Wed, 20 Jun 2018 15:22:03 -0700 Subject: [PATCH] Cleanups. --- core/collections.nom | 4 +- core/control_flow.nom | 21 +++-- core/io.nom | 4 +- core/metaprogramming.nom | 4 +- core/operators.nom | 10 ++ error_handling.lua | 2 +- error_handling.moon | 3 +- examples/how_do_i.nom | 194 +++++++++++++++++++++++++++------------ nomsu_compiler.lua | 2 +- nomsu_compiler.moon | 2 +- 10 files changed, 170 insertions(+), 76 deletions(-) diff --git a/core/collections.nom b/core/collections.nom index 6ba806a..abd4a3a 100644 --- a/core/collections.nom +++ b/core/collections.nom @@ -50,10 +50,10 @@ compile [append %item to %list, add %item to %list, to %list add %item, to %list Lua "table.insert(\(%list as lua expr), \(%item as lua expr))" compile [pop from %list, remove last from %list] to - Lua "table.remove(\(%list as lua expr))" + Lua value "table.remove(\(%list as lua expr))" compile [remove index %index from %list] to - Lua "table.remove(\(%list as lua expr), \(%index as lua expr))" + Lua value "table.remove(\(%list as lua expr), \(%index as lua expr))" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/core/control_flow.nom b/core/control_flow.nom index 4a007df..9aae937 100644 --- a/core/control_flow.nom +++ b/core/control_flow.nom @@ -135,6 +135,11 @@ compile [stop %var] to compile [do next %var] to Lua "goto continue_\(%var as lua identifier)" +compile [=== stop %var ===, --- stop %var ---, *** stop %var ***] to + Lua "::stop_\(%var as lua identifier)::" +compile [=== next %var ===, --- next %var ---, *** next %var ***] to + Lua "::continue_\(%var as lua identifier)::" + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Numeric range for loops @@ -153,7 +158,7 @@ compile [..] (%.type = "Action") and (%.stub is "do next %") and %.(3).1 = %var.1 - ..: to %lua write "\n ::continue_\(%var as lua identifier)::" + ..: to %lua write "\n \(compile as: === next %var ===)" to %lua write "\nend --numeric for-loop" if @@ -166,7 +171,7 @@ compile [..] Lua ".." do -- scope for stopping for-loop \%lua - ::stop_\(%var as lua identifier):: + \(compile as: === stop %var ===) end -- end of scope for stopping for-loop return %lua @@ -188,7 +193,7 @@ compile [for %var in %iterable %body] to (%.type = "Action") and (%.stub is "do next %") and %.3.(1) = %var.(1) - ..: to %lua write (Lua "\n::continue_\(%var as lua identifier)::") + ..: to %lua write (Lua "\n\(compile as: === next %var ===)") to %lua write "\nend --foreach-loop" if %body has subtree % where @@ -200,7 +205,7 @@ compile [for %var in %iterable %body] to Lua ".." do -- scope for stopping for-loop \%lua - ::stop_\(%var as lua identifier):: + \(compile as: === stop %var ===) end -- end of scope for stopping for-loop return %lua @@ -221,14 +226,14 @@ compile [..] (%.type = "Action") and (%.stub is "do next %") and %.3.(1) = %key.(1) - ..: to %lua write (Lua "\n::continue_\(%key as lua identifier)::") + ..: to %lua write (Lua "\n\(compile as: === next %key ===)") if %body has subtree % where (%.type = "Action") and (%.stub is "do next %") and %.3.(1) = %value.(1) - ..: to %lua write (Lua "\n::continue_\(%value as lua identifier)::") + ..: to %lua write (Lua "\n\(compile as: === next %value ===)") to %lua write "\nend --foreach-loop" %stop_labels <- (Lua "") @@ -237,14 +242,14 @@ compile [..] (%.type = "Action") and (%.stub is "stop %") and %.2.(1) = %key.(1) - ..: to %stop_labels write "\n::stop_\(%key as lua identifier)::" + ..: to %stop_labels write "\n\(compile as: === stop %key ===)" if %body has subtree % where (%.type = "Action") and (%.stub is "stop %") and %.2.(1) = %value.(1) - ..: to %stop_labels write "\n::stop_\(%value as lua identifier)::" + ..: to %stop_labels write "\n\(compile as: === stop %value ===)" if: (length of "\%stop_labels") > 0 %lua <- diff --git a/core/io.nom b/core/io.nom index 751650d..48db71f 100644 --- a/core/io.nom +++ b/core/io.nom @@ -6,9 +6,9 @@ use "core/metaprogramming.nom" compile [say %message] to lua> ".." if \%message.type == "Text" then - return LuaCode(tree.source, "io.write(", \(%message as lua expr), ", '\\\\n');"); + return LuaCode(tree.source, "print(", \(%message as lua expr), ");"); else - return LuaCode(tree.source, "io.write(tostring(", \(%message as lua expr), "), '\\\\n');"); + return LuaCode(tree.source, "print(tostring(", \(%message as lua expr), "));"); end compile [ask %prompt] to diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom index 8911b59..1e167d0 100644 --- a/core/metaprogramming.nom +++ b/core/metaprogramming.nom @@ -4,7 +4,7 @@ lua> ".." nomsu.COMPILE_ACTIONS["% -> %"] = function(nomsu, tree, \%args, \%body) - local lua = LuaCode(tree.source, "function(") + local lua = LuaCode.Value(tree.source, "function(") if AST.is_syntax_tree(\%args, "Action") then \%args = \%args:get_args() end local lua_args = table.map(\%args, function(a) return AST.is_syntax_tree(a) and tostring(nomsu:compile(a)) or a end) lua:concat_append(lua_args, ", ") @@ -163,7 +163,7 @@ compile [declare locals %locals in %code] to compile [remove free vars %vars from %code] to Lua "\(%code as lua expr):remove_free_vars(\(%vars as lua expr));" -parse [%lua <-write %code, to %lua write %code] as: lua> "\%lua:append(\%code);" +compile [%lua <-write %code, to %lua write %code] to: Lua "\(%lua as lua expr):append(\(%code as lua expr));" compile [quote %s] to Lua value ".." diff --git a/core/operators.nom b/core/operators.nom index 0756751..c81760d 100644 --- a/core/operators.nom +++ b/core/operators.nom @@ -130,6 +130,16 @@ compile [with %assignments %body] to \%lua end -- 'with' block +compile [local %var_or_vars] to + %lua <- (Lua "") + lua> ".." + if \%var_or_vars.type == "List" then + \%lua:add_free_vars(table.map(\%var_or_vars, function(v) return tostring(nomsu:compile(v)) end)) + else + \%lua:add_free_vars({tostring(nomsu:compile(\%var_or_vars))}) + end + return %lua + # Math Operators compile [%x wrapped around %y, %x mod %y] to: Lua value "(\(%x as lua expr) % \(%y as lua expr))" diff --git a/error_handling.lua b/error_handling.lua index 3ead904..5e7d715 100644 --- a/error_handling.lua +++ b/error_handling.lua @@ -160,7 +160,7 @@ print_error = function(error_message, stack_offset) end end end - if file and calling_fn.short_src:match("%.moon$") and type(MOON_SOURCE_MAP[file]) == 'table' then + if file and (calling_fn.short_src:match("%.moon$") or file:match("^#![^\n]*moon\n")) and type(MOON_SOURCE_MAP[file]) == 'table' then local char = MOON_SOURCE_MAP[file][calling_fn.currentline] line_num = 1 for _ in file:sub(1, char):gmatch("\n") do diff --git a/error_handling.moon b/error_handling.moon index c95556b..8aa0a82 100644 --- a/error_handling.moon +++ b/error_handling.moon @@ -98,7 +98,8 @@ print_error = (error_message, stack_offset=3)-> name = "upvalue '#{varname}'" if not varname\match("%(") break - if file and calling_fn.short_src\match("%.moon$") and type(MOON_SOURCE_MAP[file]) == 'table' + + if file and (calling_fn.short_src\match("%.moon$") or file\match("^#![^\n]*moon\n")) and type(MOON_SOURCE_MAP[file]) == 'table' char = MOON_SOURCE_MAP[file][calling_fn.currentline] line_num = 1 for _ in file\sub(1,char)\gmatch("\n") do line_num += 1 diff --git a/examples/how_do_i.nom b/examples/how_do_i.nom index 107b987..867405b 100644 --- a/examples/how_do_i.nom +++ b/examples/how_do_i.nom @@ -273,24 +273,26 @@ action [square root of %n] =lua "math.sqrt(\%n)" say "The square root of 2 is \(square root of 2)" -# The "immediately %" macro forces the indented code below it to run before the rest of - the file finishes compiling, so it's often useful for writing your own macros. -immediately - # Macros can be defined to transform one bit of nomsu code into another using "parse % as %": - parse [if %condition is untrue %body] as - if (not %condition) %body +# Macros can be defined to transform one bit of nomsu code into another using "parse % as %": +parse [if %condition is untrue %body] as + if (not %condition) %body - # Or to transform nomsu code into custom lua code using "compile % to %" - compile [if %condition on opposite day %body] to - Lua ".." - if not \(%condition as lua expr) then - \(%body as lua statements) - end - - # Constants can be defined as macros - parse [TWENTY] as: 20 - # When they're invoked, they'll need parentheses just like a function call - parse [TWENTY ONE] as: 21 +# Or to transform nomsu code into custom lua code using "compile % to %" +compile [if %condition on opposite day %body] to + Lua ".." + if not \(%condition as lua expr) then + \(%body as lua statements) + end + +# Constants can be defined as macros +parse [TWENTY] as: 20 +# When they're invoked, they'll need parentheses just like a function call +parse [TWENTY ONE] as: 21 + +# If you need to use compile-time actions in the same file that they're defined in, you + can add a line of tildes (3 or more), and the file will be split into chunks, and each + chunk will run before the next one compiles. (note that each chunk has its own scope) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if (1 > (TWENTY)) is untrue say "Nomsu parsing macros work!" @@ -305,55 +307,131 @@ if (1 > (TWENTY)) on opposite day # Well... it's always *possible* to fall back to Lua behavior for something like this: action [best of %items according to %key-fn] <- {%best:nil, %best-key:nil} - for % in %items - %key <- (=lua "\%key-fn(\%)") + for %item in %items + %key <- (=lua "\%key-fn(\%item)") if: (%best is (nil)) or (%key > %best-key) - <- {%best:%, %best-key:%key} + <- {%best:%item, %best-key:%key} return %best -immediately - compile [function %var %body] to - Lua value ".." - (function(\(%var as lua expr)) - return \(%body as lua expr) - end) - -say: best of [2,-3,4,-8] according to (function %: % * %) +say: best of [2,-3,4,-8] according to ([%x] -> (%x * %x)) # But nomsu was mostly designed so that you don't *need* to. Instead of creating a one-off function to pass to another function and get called a bunch of times, you could use a macro to generate a single block of code that inlines the expression you want to use: -immediately - parse [best of %items according to %key-var in %key-expr] as - result of - <- {%best:nil, %best-key:nil} - for %key-var in %items - %key <- %key-expr - if: (%best is (nil)) or (%key > %best-key) - <- {%best:%, %best-key:%key} - return %best -# This results in generated code that is more efficient (no function calls in the - inner loop) -say: best of [2,-3,4,-8] according to % in (% * %) +parse [best of %items where %item-var has score %key-expr] as + result of + <- {%best:nil, %best-key:nil} + for %item-var in %items + %key <- %key-expr + if: (%best is (nil)) or (%key > %best-key) + <- {%best:%item-var, %best-key:%key} + return %best +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +say: best of [2,-3,4,-8] where %x has score (%x * %x) -# In a functional programming language, you might do something like: - doubled = map(list, function(x) return 2 * x end) - to get a new list with every entry multiplied by 2, but it's *much* more readable to - do something like: -%nums <- [1,2,3,4,5] -%double-nums <- ((2 * %num) for %num in %nums) +# The first example generates code that looks like: -# Nomsu comes with built-in list comprehensions, but the flexible macro system makes it - incredibly easy to make similar constructs. -immediately - parse [%expr for %key in %list BACKWARDS!] as - result of - %result <- [] - %N <- (length of %list) - for %i = %key in %list - %result.(%N - %i + 1) <- %expr - return %result + A_best_of_1_according_to_2 = function(items, key_fn) + local best, best_key = nil, nil + for _, item in ipairs(items) do + local key = key_fn(item) + if best == nil or key > best_key then + best, best_key = item, key + end + end + return best + end + print(A_best_of_1_according_to_2({2,-3,4,-8}, function(x) + return x * x + end)) -%double-nums <- ((2 * %num) for %num in %nums BACKWARDS!) -say %double-nums + But the second example produces something more like: + + print((function() + local best, best_key = nil, nil + for _, x in ipairs({1,-2,3,-4}) do + local key = x * x + if best == nil or key > best_key then + best, best_key = x, key + end + end + return best + end)()) + + Notice that the second example has inlined the key function, so that in the inner loop, + the code only has to do a multiplication, instead of calling a function that does the + multiplication. In addition to being more efficient, it's also more powerful and + flexible for the language to allow you to define syntax that manipulates expressions. + + For example, list comprehensions can (and are) easily defined in Nomsu as: + +parse [%expr for %key-var in %list] as + result of + %result <- [] + for %key-var in %list + add %expr to %result + return %result + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%double-nums <- ((2 * %num) for %num in [1,2,3,4,5]) + + +# Naturally, Nomsu's macros are hygienic, so even though "%result" is used as a local + variable in the macro, there won't be any namespace collisions or confusion if you + use a different variable named "%result" as any (or all) of the arguments to the macro. + +%result <- [1,2,3,4] +%result <- ((2*%result) for %result in %result) +assume: %result = [2,4,6,8] + +# You'll find that in a lot places where a functional programmer would think to use a + lambda function, what they actually want to do is some metaprogramming. If you embrace + the metaprogramming from the start, then you can do a lot more powerful stuff. + +# For example, you can define a loop that operates on recursive datastructures, but still + has all of the normal imperative control structures: + +compile [stack for %var] to + Lua value "stack\(%var as lua id)" + +parse [recurse on %sub as %super] as + add %sub to (stack for %super) + +parse [for %var in recursive %iterable %body] as + with {(stack for %var): [%iterable]} + repeat while: (length of (stack for %var)) > 0 + %var <- (remove index 1 from (stack for %var)) + with {%val: %expr} + if: %val != (nil) + add %val to %result + %body + === next %var === + === stop %var === + +~~~~~~~~~~~~~~~~ + +# This action iterates over a recursive structure, but it's written imperatively and it + can return early without traversing the whole tree. +action [tree %tree contains %val] + for %subtree in recursive %tree + if: %subtree = %val + return: yes + if: (type of %subtree) = "table" + for % in %subtree: recurse on % as %subtree + return: no + +%t <- [1,[2,[3,4,[]],5,[6]]] +say: tree %t contains 3 +say: tree %t contains 99 + + +action [linked list from %list] + %head <- (nil) + for %i in (length of %list) to 1 via -1 + %head <- {value:%list.%i, next:%head} + return %head + +for %node in recursive (linked list from [3,4,5,6,7]) + say "NODE: \(%node.value)" + recurse on %node.next as %node diff --git a/nomsu_compiler.lua b/nomsu_compiler.lua index 5c7fb91..d093ff5 100644 --- a/nomsu_compiler.lua +++ b/nomsu_compiler.lua @@ -501,7 +501,7 @@ do source = nil end local lua_string = tostring(lua) - local run_lua_fn, err = load(lua_string, tostring(source or lua.source), "t", self) + local run_lua_fn, err = load(lua_string, nil and tostring(source or lua.source), "t", self) if not run_lua_fn then local line_numbered_lua = concat((function() local _accum_0 = { } diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon index 4d7b4cf..8226db4 100644 --- a/nomsu_compiler.moon +++ b/nomsu_compiler.moon @@ -339,7 +339,7 @@ with NomsuCompiler .run_lua = (lua, source=nil)=> lua_string = tostring(lua) - run_lua_fn, err = load(lua_string, tostring(source or lua.source), "t", self) + run_lua_fn, err = load(lua_string, nil and tostring(source or lua.source), "t", self) if not run_lua_fn line_numbered_lua = concat( [format("%3d|%s",i,line) for i, line in ipairs get_lines\match(lua_string)],