diff --git a/Makefile b/Makefile index 867a726..e3db5a3 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ MOON_FILES= code_obj.moon error_handling.moon files.moon nomsu.moon nomsu_compil syntax_tree.moon containers.moon bitops.moon parser.moon pretty_errors.moon \ string2.moon LUA_FILES= code_obj.lua consolecolors.lua error_handling.lua files.lua nomsu.lua nomsu_compiler.lua \ - syntax_tree.lua containers.lua bitops.lua utils.lua parser.lua pretty_errors.lua \ + syntax_tree.lua containers.lua bitops.lua parser.lua pretty_errors.lua \ string2.lua CORE_NOM_FILES= $(wildcard core/*.nom) CORE_LUA_FILES= $(patsubst %.nom,%.lua,$(CORE_NOM_FILES)) diff --git a/README.md b/README.md index 1de0910..1af0f90 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,6 @@ All `.moon` files have been precompiled into corresponding `.lua` files, so you * [syntax\_tree.moon](syntax_tree.moon) - Datastructures used for Nomsu Abstract Syntax Trees. * [code\_obj.moon](code_obj.moon) - Datastructures used for incrementally building generated code, while preserving code origins. * [error\_handling.moon](error_handling.moon) - The logic for producing good error messages within Lua that reference the Nomsu source code that led to them. -* [utils.lua](utils.lua) - A set of utility actions used by nomsu.moon. * [consolecolors.lua](consolecolors.lua) - Lua module that defines ANSI color codes for colored console output (used internally in nomsu.moon). * [examples/how\_do\_i.nom](examples/how_do_i.nom) - A simple walkthrough of some of the features of Nomsu, written in Nomsu code. **This is a good place to start.** * [core/\*.nom](core) - Core language definitions of stuff like control flow, operators, and metaprogramming, broken down into different files. diff --git a/code_obj.lua b/code_obj.lua index b5213c9..7d335e0 100644 --- a/code_obj.lua +++ b/code_obj.lua @@ -409,7 +409,7 @@ do return { nomsu_filename = self.source.filename, lua_filename = tostring(self.source) .. ".lua", - lua_file = self:stringify(), + lua_file = self:text(), lua_to_nomsu = lua_to_nomsu, nomsu_to_lua = nomsu_to_lua } diff --git a/code_obj.moon b/code_obj.moon index 22969a4..cb8eae2 100644 --- a/code_obj.moon +++ b/code_obj.moon @@ -245,7 +245,7 @@ class LuaCode extends Code walk self, 1 return { nomsu_filename:@source.filename - lua_filename:tostring(@source)..".lua", lua_file:@stringify! + lua_filename:tostring(@source)..".lua", lua_file:@text! :lua_to_nomsu, :nomsu_to_lua } diff --git a/containers.lua b/containers.lua index 3148dcf..9f8fa46 100644 --- a/containers.lua +++ b/containers.lua @@ -1,16 +1,9 @@ +local List, Dict local insert, remove, concat do local _obj_0 = table insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat end -local equivalent, nth_to_last, size -do - local _obj_0 = require('utils') - equivalent, nth_to_last, size = _obj_0.equivalent, _obj_0.nth_to_last, _obj_0.size -end -local lpeg = require('lpeg') -local re = require('re') -local List, Dict local as_nomsu as_nomsu = function(self) if type(self) == 'number' then @@ -47,9 +40,23 @@ as_lua = function(self) end return tostring(self) end +local nth_to_last +nth_to_last = function(self, n) + return self[#self - n + 1] +end local _list_mt = { __type = "List", - __eq = equivalent, + __eq = function(self, other) + if not (type(other) == 'table' and getmetatable(other) == getmetatable(self) and #other == #self) then + return false + end + for i, x in ipairs(self) do + if not (x == other[i]) then + return false + end + end + return true + end, __tostring = function(self) return "[" .. concat((function() local _accum_0 = { } @@ -75,7 +82,7 @@ local _list_mt = { end)(), ", ") .. "]" end, as_lua = function(self) - return "_List{" .. concat((function() + return "List{" .. concat((function() local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #self do @@ -242,8 +249,29 @@ walk_items = function(self, i) end local _dict_mt = { __type = "Dict", - __eq = equivalent, - __len = size, + __eq = function(self, other) + if not (type(other) == 'table' and getmetatable(other) == getmetatable(self)) then + return false + end + for k, v in pairs(self) do + if not (v == other[k]) then + return false + end + end + for k, v in pairs(other) do + if not (v == self[k]) then + return false + end + end + return true + end, + __len = function(self) + local n = 0 + for _ in pairs(self) do + n = n + 1 + end + return n + end, __tostring = function(self) return "{" .. concat((function() local _accum_0 = { } @@ -267,7 +295,7 @@ local _dict_mt = { end)(), ", ") .. "}" end, as_lua = function(self) - return "_Dict{" .. concat((function() + return "Dict{" .. concat((function() local _accum_0 = { } local _len_0 = 1 for k, v in pairs(self) do diff --git a/containers.moon b/containers.moon index 4671f6b..9591fa2 100644 --- a/containers.moon +++ b/containers.moon @@ -1,11 +1,6 @@ --- This file contains container classes, i.e. Lists, Dicts, and Sets - -{:insert,:remove,:concat} = table -{:equivalent, :nth_to_last, :size} = require 'utils' -lpeg = require 'lpeg' -re = require 're' - +-- This file contains container classes, i.e. Lists and Dicts, plus some extended string functionality local List, Dict +{:insert,:remove,:concat} = table as_nomsu = => if type(@) == 'number' @@ -23,19 +18,26 @@ as_lua = => return _as_lua(@) return tostring(@) +nth_to_last = (n)=> @[#@-n+1] + -- List and Dict classes to provide basic equality/tostring functionality for the tables -- used in Nomsu. This way, they retain a notion of whether they were originally lists or dicts. _list_mt = __type: "List" - __eq:equivalent + __eq: (other)=> + unless type(other) == 'table' and getmetatable(other) == getmetatable(@) and #other == #@ + return false + for i,x in ipairs(@) + return false unless x == other[i] + return true -- Could consider adding a __newindex to enforce list-ness, but would hurt performance __tostring: => "["..concat([as_nomsu(b) for b in *@], ", ").."]" as_nomsu: => "["..concat([as_nomsu(b) for b in *@], ", ").."]" as_lua: => - "_List{"..concat([as_lua(b) for b in *@], ", ").."}" + "List{"..concat([as_lua(b) for b in *@], ", ").."}" __lt: (other)=> assert type(@) == 'table' and type(other) == 'table', "Incompatible types for comparison" for i=1,math.max(#@, #other) @@ -65,7 +67,6 @@ _list_mt = last: (=> @[#@]), first: (=> @[1]) _1_st_to_last:nth_to_last, _1_nd_to_last:nth_to_last _1_rd_to_last:nth_to_last, _1_th_to_last:nth_to_last - -- TODO: use stringify() to allow joining misc. objects? joined: => table.concat([tostring(x) for x in *@]), joined_with: (glue)=> table.concat([tostring(x) for x in *@], glue), has: (item)=> @@ -105,14 +106,24 @@ walk_items = (i)=> _dict_mt = __type: "Dict" - __eq:equivalent - __len:size + __eq: (other)=> + unless type(other) == 'table' and getmetatable(other) == getmetatable(@) + return false + for k,v in pairs(@) + return false unless v == other[k] + for k,v in pairs(other) + return false unless v == @[k] + return true + __len: => + n = 0 + for _ in pairs(@) do n += 1 + return n __tostring: => "{"..concat(["#{as_nomsu(k)}: #{as_nomsu(v)}" for k,v in pairs @], ", ").."}" as_nomsu: => "{"..concat(["#{as_nomsu(k)}: #{as_nomsu(v)}" for k,v in pairs @], ", ").."}" as_lua: => - "_Dict{"..concat(["[ #{as_lua(k)}]= #{as_lua(v)}" for k,v in pairs @], ", ").."}" + "Dict{"..concat(["[ #{as_lua(k)}]= #{as_lua(v)}" for k,v in pairs @], ", ").."}" __ipairs: => walk_items, {table:@, key:nil}, 0 __band: (other)=> Dict{k,v for k,v in pairs(@) when other[k] != nil} diff --git a/core/control_flow.nom b/core/control_flow.nom index bbc98f1..a692f95 100644 --- a/core/control_flow.nom +++ b/core/control_flow.nom @@ -560,7 +560,7 @@ test: %lua = (..) Lua "\ ..do - local \(mangle "stack \(%var.1)") = _List{\(%structure as lua expr)} + local \(mangle "stack \(%var.1)") = List{\(%structure as lua expr)} while #\(mangle "stack \(%var.1)") > 0 do \(%var as lua expr) = table.remove(\(mangle "stack \(%var.1)"), 1)" %lua::append "\n " diff --git a/core/math.nom b/core/math.nom index 66f5aba..3bad78a 100644 --- a/core/math.nom +++ b/core/math.nom @@ -76,27 +76,54 @@ test: externally (%n to the nearest %rounder) means (..) =lua "(\%rounder)*math.floor((\%n / \%rounder) + .5)" -# Any/all/none +# Any/all +externally [all of %items, all %items] all mean: + for % in %items: + unless %: return (no) + return (yes) [all of %items, all %items] all compile to: unless (%items.type is "List"): - return (Lua value "utils.all(\(%items as lua expr))") + return %tree %clauses = (((% as lua expr)::text) for % in %items) return (Lua value "(\(%clauses::joined with " and "))") - [not all of %items, not all %items] all parse as (not (all of %items)) + +externally [any of %items, any %items] all mean: + for % in %items: + if %: return (yes) + return (no) [any of %items, any %items] all compile to: unless (%items.type is "List"): - return (Lua value "utils.any(\(%items as lua expr))") + return %tree %clauses = (((% as lua expr)::text) for % in %items) return (Lua value "(\(%clauses::joined with " or "))") - [none of %items, none %items] all parse as (not (any of %items)) + +# Sum/product +externally [sum of %items, sum %items] all mean: + %total = 0 + for % in %items: %total += % + return %total [sum of %items, sum %items] all compile to: unless (%items.type is "List"): - return (Lua value "utils.sum(\(%items as lua expr))") + return %tree %clauses = (((% as lua expr)::text) for % in %items) return (Lua value "(\(%clauses::joined with " + "))") +externally [product of %items, product %items] all mean: + %prod = 1 + for % in %items: %prod *= % + return %prod +[product of %items, product %items] all compile to: + unless (%items.type is "List"): + return %tree + %clauses = (((% as lua expr)::text) for % in %items) + return (Lua value "(\(%clauses::joined with " * "))") + +externally [avg of %items, average of %items] all mean (..) + (sum of %items) / (size of %items) + +# Shorthand for control flow [if all of %items %body, if all of %items then %body] all parse as (..) if (all of %items) %body @@ -136,42 +163,44 @@ externally (%n to the nearest %rounder) means (..) unless none of %items %body else %else, unless none of %items then %body else %else ..all parse as (if (any of %items) %body else %else) -[product of %items, product %items] all compile to: - unless (%items.type is "List"): - return (Lua value "utils.product(\(%items as lua expr))") - %clauses = (((% as lua expr)::text) for % in %items) - return (Lua value "(\(%clauses::joined with " * "))") +# Min/max +externally [min of %items, smallest of %items, lowest of %items] all mean: + %best = (nil) + for % in %items: + if ((%best == (nil)) or (% < %best)): + %best = % + return %best -externally [avg of %items, average of %items] all mean (..) - =lua "(utils.sum(\%items)/#\%items)" - -[min of %items, smallest of %items, lowest of %items] all compile to (..) - Lua value "utils.min(\(%items as lua expr))" - -[max of %items, biggest of %items, largest of %items, highest of %items] all compile \ -..to (Lua value "utils.max(\(%items as lua expr))") +externally [max of %items, biggest of %items, largest of %items, highest of %items] all mean: + %best = (nil) + for % in %items: + if ((%best == (nil)) or (% > %best)): + %best = % + return %best test: assume ((min of [3, -4, 1, 2] by % = (% * %)) == 1) assume ((max of [3, -4, 1, 2] by % = (% * %)) == -4) (min of %items by %item = %value_expr) parses as (..) result of: - set {%best:nil, %best_key:nil} + %best = (nil) + %best_key = (nil) for %item in %items: %key = %value_expr if ((%best == (nil)) or (%key < %best_key)): - set {%best:%item, %best_key:%key} - + %best = %item + %best_key = %key return %best (max of %items by %item = %value_expr) parses as (..) result of: - set {%best:nil, %best_key:nil} + %best = (nil) + %best_key = (nil) for %item in %items: %key = %value_expr if ((%best == (nil)) or (%key > %best_key)): - set {%best:%item, %best_key:%key} - + %best = %item + %best_key = %key return %best # Random functions diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom index 61c877f..b104987 100644 --- a/core/metaprogramming.nom +++ b/core/metaprogramming.nom @@ -63,29 +63,33 @@ test: asdf assume (%tmp is (nil)) or barf "compile to is leaking variables" lua> "\ - ..COMPILE_ACTIONS["1 compiles to"] = function(nomsu, tree, \%actions, \%body) - if \%actions.type ~= "List" then \%actions = {\%actions, type="List"} end - local \%args = {"nomsu", "tree", unpack(table.map(\%actions[1]:get_args(), function(a) return nomsu:compile(a):text() end))} - local lua = LuaCode(tree.source, "COMPILE_ACTIONS[", \%actions[1].stub:as_lua(), + ..COMPILE_ACTIONS["1 compiles to"] = function(nomsu, tree, \%action, \%body) + local \%args = List{\(\%nomsu), \(\%tree), unpack(\%action:get_args())} + local lua = LuaCode(tree.source, "COMPILE_ACTIONS[", \%action.stub:as_lua(), "] = ", \(what (%args -> %body) compiles to)) + return lua + end" + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +(%actions all compile to %body) compiles to: + lua> "\ + ..if \%actions.type ~= "List" then + nomsu:compile_error(\%actions, "This should be a list of actions.") + end + local lua = LuaCode(tree.source, \(what (%actions.1 compiles to %body) compiles to)) + local \%args = List{\(\%nomsu), \(\%tree), unpack(\%actions[1]:get_args())} for i=2,#\%actions do local alias = \%actions[i] - local \%alias_args = {"nomsu", "tree", unpack(table.map(alias:get_args(), function(a) return nomsu:compile(a):text() \ - ..end))} + local \%alias_args = List{\(\%nomsu), \(\%tree), unpack(alias:get_args())} lua:append("\\nCOMPILE_ACTIONS[", alias.stub:as_lua(), "] = ") - if utils.equivalent(\%args, \%alias_args) then + if \%alias_args == \%args then lua:append("COMPILE_ACTIONS[", \%actions[1].stub:as_lua(), "]") else - lua:append("function(") - lua:concat_append(\%alias_args, ", ") - lua:append(")\\n return COMPILE_ACTIONS[", \%actions[1].stub:as_lua(), "](") - lua:concat_append(\%args, ", ") - lua:append(")\\nend") + lua:append(\(what (%alias_args -> \(what %actions.1 compiles to)) compiles to)) end end - return lua - end - COMPILE_ACTIONS["1 all compile to"] = COMPILE_ACTIONS["1 compiles to"]" + return lua" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -112,27 +116,29 @@ test: (baz %) parses as (foo %) assume ((foo 1) == "outer") -[%actions means %body, %actions all mean %body] all compile to: + +(%action means %body) compiles to: lua> "\ - ..if \%actions.type ~= "List" then \%actions = {\%actions, type="List"} end - local fn_name = \%actions[1].stub:as_lua_id() - local \%args = table.map(\%actions[1]:get_args(), function(a) return nomsu:compile(a):text() end) + ..local fn_name = \%action.stub:as_lua_id() + local \%args = \%action:get_args() local lua = LuaCode(tree.source, fn_name, " = ", \(what (%args -> %body) compiles to)) lua:add_free_vars({fn_name}) + return lua" +(%actions all mean %body) compiles to: + lua> "\ + ..local fn_name = \%actions[1].stub:as_lua_id() + local \%args = List(\%actions[1]:get_args()) + local lua = LuaCode(tree.source, \(what (%actions.1 means %body) compiles to)) for i=2,#\%actions do local alias = \%actions[i] local alias_name = alias.stub:as_lua_id() lua:add_free_vars({alias_name}) - local \%alias_args = table.map(alias:get_args(), function(a) return nomsu:compile(a):text() end) + local \%alias_args = List(alias:get_args()) lua:append("\\n", alias_name, " = ") - if utils.equivalent(\%args, \%alias_args) then + if \%args == \%alias_args then lua:append(fn_name) else - lua:append("function(") - lua:concat_append(\%alias_args, ", ") - lua:append(")\\n return ", fn_name, "(") - lua:concat_append(\%args, ", ") - lua:append(")\\nend") + lua:append(\(what (%alias_args -> %actions.1) compiles to)) end end return lua" @@ -143,10 +149,14 @@ test: test: assume ((baz1) == "baz1") assume ((baz2) == "baz2") -[externally %actions means %body, externally %actions all mean %body] all compile to: +(externally %action means %body) compiles to: lua> "\ - ..local lua = \(what (%actions means %body) compiles to) - if \%actions.type ~= "List" then \%actions = {\%actions, type="List"} end + ..local lua = \(what (%action means %body) compiles to) + lua:remove_free_vars({\%action.stub:as_lua_id()}) + return lua" +(externally %actions all mean %body) compiles to: + lua> "\ + ..local lua = \(what (%actions all mean %body) compiles to) lua:remove_free_vars(table.map(\%actions, function(a) return a.stub:as_lua_id() end)) return lua" @@ -169,10 +179,12 @@ test: swap %tmp and %tmp2 assume ((%tmp == 2) and (%tmp2 == 1)) or barf "\ ..'parse % as %' variable mangling failed." -[%actions parses as %body, %actions all parse as %body] all compile to: +(%actions all parse as %body) compiles to: lua> "\ ..local replacements = {} - if \%actions.type ~= "List" then \%actions = {\%actions, type="List"} end + if \%actions.type ~= "List" then + nomsu:compile_error(\%actions, "This should be a list.") + end for i,arg in ipairs(\%actions[1]:get_args()) do replacements[arg[1]] = nomsu:compile(arg):text() end @@ -208,10 +220,12 @@ test: local \%new_body = LuaCode(\%body.source, "local mangle = mangler()", "\\nreturn ", make_tree(\%body)) - local ret = \(what (%actions compiles to %new_body) compiles to) + local ret = \(what (%actions all compile to %new_body) compiles to) return ret" -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +[%action parses as %body] all parse as ([%action] all parse as %body) # TODO: add check for .is_value (%tree as lua expr) compiles to (..) @@ -287,10 +301,10 @@ externally (%tree with vars %replacements) means (..) externally (match %tree with %patt) means: lua> "\ - ..if \%patt.type == "Var" then return _Dict{[\%patt[1]]=\%tree} end + ..if \%patt.type == "Var" then return Dict{[\%patt[1]]=\%tree} end if \%patt.type == "Action" and \%patt.stub ~= \%tree.stub then return nil end if #\%patt ~= #\%tree then return nil end - local matches = _Dict{} + local matches = Dict{} for \%i=1,#\%patt do if SyntaxTree:is_instance(\%tree[\%i]) then local submatch = \(match %tree.%i with %patt.%i) @@ -341,11 +355,11 @@ externally (type of %) means: lua> "\ ..local lua_type = \(lua type of %) if lua_type == 'string' then return 'Text' - elseif lua_type == 'table' then + elseif lua_type == 'table' or lua_type == 'userdata' then local mt = getmetatable(\%) if mt and mt.__type then return mt.__type end - return 'Lua table' - else return lua_type end" + end + return lua_type" [% is a %type, % is an %type] all parse as ((type of %) == %type) [% isn't a %type, % isn't an %type, % is not a %type, % is not an %type] all parse as (..) diff --git a/core/text.nom b/core/text.nom index 05ec409..40eb895 100644 --- a/core/text.nom +++ b/core/text.nom @@ -22,10 +22,10 @@ test: assume ("asdf"::uppercase) == "ASDF" assume ("asdf"::with "s" -> "X") == "aXdf" assume ("one\ntwo\n"::lines) == ["one", "two", ""] - (アクション %spec %body) parses as (externally %spec means %body) + (%spec とは %body) parses as (%spec means %body) test: %こんにちは = "こんにちは" - アクション [% と言う] "\(%)世界" + (% と言う) とは "\(%)世界" assume (%こんにちは と言う) == "こんにちは世界" (%expr for %match in %text matching %patt) compiles to (..) Lua value "\ diff --git a/examples/how_do_i.nom b/examples/how_do_i.nom index ec75596..91d1ae3 100644 --- a/examples/how_do_i.nom +++ b/examples/how_do_i.nom @@ -16,6 +16,7 @@ use "lib" say "Hello world!" # How do I set a variable? +# Variables have "%" prefix: %foobar = 1 %text = "Hello world" @@ -31,7 +32,7 @@ say %one_two %foobar += 1 # How do I define a mutli-line string? -# In Nomsu, "strings" are called "text", and multi-line text looks like: +# In Nomsu, the name "text" is used, rather than "string", and multi-line text looks like: %mutli_text = "\ ..Start with a quote mark and a backslash and an indented "..", then put indented lines below it. The indented lines will not include the indentation, except when @@ -47,7 +48,7 @@ say "\ ..Text can contain a backslash followed by a variable, list, dict, or parenthesized expression. This escaped value will be converted to readable text, like so: The value of %foobar is \%foobar, isn't that nice? - These are some numbers: \[1 + 1, 2 + 1, 3 + 1] + These are some numbers: \[1, 2, 3] The sum of 2 and 4 is \(2 + 4). A backslash not followed by any of these, and not at the end of a line @@ -66,16 +67,16 @@ say "\ say "Single-line text can contain escape sequences like \", \\, \000, and \n" # How do I define a list? -%my_list = [1, 2, "hello"] +%my_list = ["first", "second", "third", 4] # Really long lists can use [..] followed by a bunch of indented values delimited by commas and/or newlines -%my_really_long_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +%my_really_long_list = [..] + 10000, 20000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, 100000, 110000 + 120000, 130000, 140000, 150000, 160000, 170000 # How do I use a list? -%my_list = ["first item", "second item", "third item"] - -# Lists are 1-indexed because they're implemented as Lua tables, so this prints "first item" +# Lists are 1-indexed because they're implemented as Lua tables, so this prints "first" say %my_list.1 # List entries can be modified like this: @@ -86,12 +87,15 @@ say %my_list.1 %my_list::pop # How do I define a dictionary/hash map? -%my_dict = {x:99, y:101} -%my_dict = {x:101, y:2, "99 bottles":99, 653:292} + One-word text keys don't need quotes, otherwise the key is an expression. + If the expression is more complex than a literal, it needs parentheses: +%my_dict = {x:101, y:2, "how many bottles":99, 653:292, (5 + 6):11} # How do I use a dict? # Dicts are also implemented as Lua tables, so they're accessed and modified the same way as lists say %my_dict.x +say %my_dict."how many bottles" +say %my_dict.653 %my_dict.x = 9999 # How do I do conditional branching? @@ -104,21 +108,21 @@ if (1 > 10): say "this will print" # There's no "elseif", so for longer conditionals, a "when" branch is the best option -if: - (3 > 6) (3 > 5) (3 > 4): - say "this won't print" +when: (3 > 3): say "this won't print" + (3 > 6) (3 > 5) (3 > 4): + say "this won't print because none of the conditions on the line above are true" (3 > 2): say "this will print" else: say "this is the default case" # How do I do a switch statement? -if 3 is: - 0 1 2: +if (1 + 2) is: + 0 1: say "this won't print" - 3: + 2 3: say "this will print" else: say "this won't print" @@ -159,20 +163,22 @@ repeat: # How do I do a 'goto'? do: %x = 1 - === %again === + === (my loop) === say "GOTO loop #\%x" %x += 1 - if (%x <= 3): go to %again + if (%x <= 3): go to (my loop) say "finished going to" # How do I define a function/method/procedure? # In nomsu, they're called "action"s, and they can be declared like this: (say both %first and also %second) means: say %first - - # Function arguments are accessed just like variables say %second +# Actions can have parts of the action's name spread throughout. + Everything that's not a literal value is treated as part of the action's name +say both "Hello" and also "world!" + # Actions can use "return" to return a value early (first fibonacci above %n) means: %f1 = 0 @@ -196,10 +202,6 @@ say (first fibonacci above 10) I like "dogs" more than "cats" I think "chihuahuas" are worse than "corgis" -# Actions can have parts of the action's name spread throughout. - Everything that's not a literal value is treated as part of the action's name -say both "Hello" and also "again!" - # Actions can even start with a parameter (%what_she_said is what she said) means: say %what_she_said @@ -207,14 +209,14 @@ say both "Hello" and also "again!" "Howdy pardner" is what she said -# The language only reserves []{}().,:;% as special characters, so actions +# The language only reserves []{}().,:;%#\ as special characters, so actions can have really funky names! -(>> %foo_bar $$$^ --> % @&_~-^-~_~-^ %1 !) means: +(>> %foo_bar $@&' --> % @&_~-^-~_~-^ %1 !) means: say %foo_bar say % say %1 ->> "wow" $$$^ --> "so flexible!" @&_~-^-~_~-^ "even numbers can be variables!" ! +>> "wow" $@&' --> "so flexible!" @&_~-^-~_~-^ "even numbers can be variables!" ! # There's also full unicode support %こんにちは = "こんにちは" @@ -251,9 +253,9 @@ say both (my favorite number) and also "foo" lua> "io.write(\"The OS time is: \", os.time(), \"\\n\");" say the time -say "Math expression result is: \(=lua "(1 + 2*3 + 3*4)^2")" +say "Math expression result is: \(=lua "(1 + 2*3 + 3*4)^2 % 5")" -# Variables can be accessed via \%varname +# Variables can be accessed via \%var (square root of %n) means (=lua "math.sqrt(\%n)") say "The square root of 2 is \(square root of 2)" @@ -261,11 +263,13 @@ say "The square root of 2 is \(square root of 2)" (if %condition is untrue %body) parses as (if (not %condition) %body) # Or to transform nomsu code into custom lua code using "compile % to %" -(if %condition on opposite day %body) compiles to (..) - Lua "\ - ..if not \(%condition as lua expr) then - \(%body as lua statements) - end" +(debug only %body) compiles to: + if %DEBUG_ENABLED: + return (Lua "-- Debug code:\n\(%body as lua statements)") + ..else: + return (Lua "-- (debug code removed for production)") + +%DEBUG_ENABLED = (yes) # Constants can be defined as macros (TWENTY) parses as 20 @@ -282,37 +286,44 @@ if (1 > (TWENTY)) is untrue: say "Nomsu parsing macros work!" say "It looks like a keyword, but there's no magic here!" -if (1 > (TWENTY)) on opposite day: +debug only: say "Lua compiling macros work!" - say "It looks like a keyword, but there's no magic here!" # How do I use an action as a value? # Well... it's always *possible* to fall back to Lua behavior for something like this: (best of %items according to %key_fn) means: - set {%best:nil, %best_key:nil} + %best = (nil) + %best_key = (nil) for %item in %items: - %key = (=lua "\%key_fn(\%item)") + %key = (%key_fn %item) if ((%best is (nil)) or (%key > %best_key)): - set {%best:%item, %best_key:%key} + %best = %item + %best_key = %key return %best +# Function literals look like: [%x] -> (%x * %x) 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: -(best of %items where %item_var has score %key_expr) parses as (..) +# Or, you can use ((foo %)'s meaning) to access the function that gets called by (foo %) +(%x squared) means (%x * %x) +say (best of [2, -3, 4, -8] according to ((% squared)'s meaning)) + +# But nomsu was designed with flexible alternatives that are often better than passing functions. + For example, instead of calling a key function on every item, you could instead define a macro + that gives you a value based on an inlined expression: +(best of %items where %item has score %key_expr) parses as (..) result of: - set {%best:nil, %best_key:nil} - for %item_var in %items: + %best = (nil) + %best_key = (nil) + for %item in %items: %key = %key_expr if ((%best is (nil)) or (%key > %best_key)): - set {%best:%item_var, %best_key:%key} + %best = %item + %best_key = %key return %best ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -say (best of [2, -3, 4, -8] where %x has score (%x * %x)) + say (best of [2, -3, 4, -8] where %x has score (%x * %x)) diff --git a/lib/object.nom b/lib/object.nom index d5555df..6f6fc20 100644 --- a/lib/object.nom +++ b/lib/object.nom @@ -54,25 +54,21 @@ test: (my action %actions %body) compiles to: lua> "\ ..local fn_name = \%actions[1].stub:as_lua_id() - local \%args = table.map(\%actions[1]:get_args(), function(a) return tostring(nomsu:compile(a)) end) - table.insert(\%args, 1, \(\%me as lua id)) + local \%args = List(\%actions[1]:get_args()) + table.insert(\%args, 1, \(\%me)) local lua = LuaCode(tree.source, "class.", fn_name, " = ", \(..) what (%args -> %body) compiles to ..) for i=2,#\%actions do local alias = \%actions[i] local alias_name = alias.stub:as_lua_id() - local \%alias_args = table.map(alias:get_args(), function(a) return tostring(nomsu:compile(a)) end) - table.insert(\%alias_args, 1, \(\%me as lua id)) + local \%alias_args = List(alias:get_args()) + table.insert(\%alias_args, 1, \(\%me)) lua:append("\\nclass.", alias_name, " = ") - if utils.equivalent(\%args, \%alias_args) then + if \%args == \%alias_args then lua:append("class.", fn_name) else - lua:append("function(") - lua:concat_append(\%alias_args, ", ") - lua:append(")\\n return class.", fn_name, "(") - lua:concat_append(\%args, ", ") - lua:append(")\\nend") + lua:append(\(what (%alias_args -> %actions.1) compiles to)) end end return lua" diff --git a/lib/os.nom b/lib/os.nom index 2bd91ad..00fe81f 100644 --- a/lib/os.nom +++ b/lib/os.nom @@ -32,7 +32,7 @@ test: (%expr for file %f in %path) compiles to (..) Lua value "\ ..(function() - local ret = _List{} + local ret = List{} for i,\(%f as lua expr) in Files.walk(\(%path as lua expr)) do ret[#ret+1] = \(%expr as lua statements) end diff --git a/nomsu.lua b/nomsu.lua index bd1ee85..e7f8fdd 100644 --- a/nomsu.lua +++ b/nomsu.lua @@ -131,7 +131,7 @@ if not args or args.help then os.exit(EXIT_FAILURE) end local nomsu = NomsuCompiler -nomsu.environment.arg = NomsuCompiler.environment._List(args.nomsu_args) +nomsu.environment.arg = NomsuCompiler.environment.List(args.nomsu_args) if args.version then nomsu:run([[(: use "core"; say (Nomsu version))]]) os.exit(EXIT_SUCCESS) diff --git a/nomsu.moon b/nomsu.moon index 2c4d21f..9637358 100755 --- a/nomsu.moon +++ b/nomsu.moon @@ -86,7 +86,7 @@ if not args or args.help os.exit(EXIT_FAILURE) nomsu = NomsuCompiler -nomsu.environment.arg = NomsuCompiler.environment._List(args.nomsu_args) +nomsu.environment.arg = NomsuCompiler.environment.List(args.nomsu_args) if args.version nomsu\run [[(: use "core"; say (Nomsu version))]] diff --git a/nomsu_compiler.lua b/nomsu_compiler.lua index 67e9621..c7fa456 100644 --- a/nomsu_compiler.lua +++ b/nomsu_compiler.lua @@ -2,10 +2,7 @@ local lpeg = require('lpeg') local R, P, S R, P, S = lpeg.R, lpeg.P, lpeg.S local re = require('re') -local utils = require('utils') local Files = require('files') -local stringify, equivalent -stringify, equivalent = utils.stringify, utils.equivalent local List, Dict, Text do local _obj_0 = require('containers') @@ -187,10 +184,8 @@ do load = load, pairs = pairs, ipairs = ipairs, - _List = List, - _Dict = Dict, - stringify = stringify, - utils = utils, + List = List, + Dict = Dict, lpeg = lpeg, re = re, Files = Files, @@ -431,6 +426,22 @@ do end NomsuCompiler.environment.COMPILE_ACTIONS = setmetatable({ __imported = Dict({ }), + [""] = function(self, tree, fn, ...) + local lua = LuaCode.Value(tree.source) + lua:append(self:compile(fn, compile_actions)) + if not (lua:text():is_lua_id()) then + lua:parenthesize() + end + lua:append("(") + for i = 1, select('#', ...) do + if i > 1 then + lua:append(", ") + end + lua:append(self:compile(select(i, ...), compile_actions)) + end + lua:append(")") + return lua + end, ["Lua"] = function(self, tree, code) return add_lua_string_bits(self, 'statements', code) end, @@ -710,15 +721,12 @@ do local filename = Source:from_string(info.source).filename self:compile_error(tree, "The compile-time action here (" .. tostring(stub) .. ") failed to return any value.", "Look at the implementation of (" .. tostring(stub) .. ") in " .. tostring(filename) .. ":" .. tostring(info.linedefined) .. " and make sure it's returning something.") end - if SyntaxTree:is_instance(ret) then - if ret == tree then - local info = debug.getinfo(compile_action, "S") - local filename = Source:from_string(info.source).filename - self:compile_error(tree, "The compile-time action here (" .. tostring(stub) .. ") is producing an endless loop.", "Look at the implementation of (" .. tostring(stub) .. ") in " .. tostring(filename) .. ":" .. tostring(info.linedefined) .. " and make sure it's not just returning the original tree.") - end + if not (SyntaxTree:is_instance(ret)) then + return ret + end + if ret ~= tree then return self:compile(ret, compile_actions) end - return ret end local lua = LuaCode.Value(tree.source) if tree.target then @@ -871,7 +879,7 @@ do lua:append("..") end if bit.type ~= "Text" then - bit_lua = LuaCode.Value(bit.source, "stringify(", bit_lua, ")") + bit_lua = LuaCode.Value(bit.source, "tostring(", bit_lua, ")") end lua:append(bit_lua) _continue_0 = true @@ -891,7 +899,7 @@ do end return lua elseif "List" == _exp_0 then - local lua = LuaCode.Value(tree.source, "_List{") + local lua = LuaCode.Value(tree.source, "List{") lua:concat_append((function() local _accum_0 = { } local _len_0 = 1 @@ -905,7 +913,7 @@ do lua:append("}") return lua elseif "Dict" == _exp_0 then - local lua = LuaCode.Value(tree.source, "_Dict{") + local lua = LuaCode.Value(tree.source, "Dict{") lua:concat_append((function() local _accum_0 = { } local _len_0 = 1 diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon index a1194d9..6fe6501 100644 --- a/nomsu_compiler.moon +++ b/nomsu_compiler.moon @@ -12,9 +12,7 @@ lpeg = require 'lpeg' {:R,:P,:S} = lpeg re = require 're' -utils = require 'utils' Files = require 'files' -{:stringify, :equivalent} = utils {:List, :Dict, :Text} = require 'containers' export colors, colored colors = require 'consolecolors' @@ -101,9 +99,9 @@ with NomsuCompiler :table, :assert, :dofile, :loadstring, lua_type_of:type, :select, :math, :io, :load, :pairs, :ipairs, -- Nomsu types: - _List:List, _Dict:Dict, + List:List, Dict:Dict, -- Utilities and misc. - stringify:stringify, utils:utils, lpeg:lpeg, re:re, Files:Files, + lpeg:lpeg, re:re, Files:Files, :SyntaxTree, TESTS: Dict({}), globals: Dict({}), :LuaCode, :NomsuCode, :Source nomsu:NomsuCompiler @@ -242,6 +240,17 @@ with NomsuCompiler return lua .environment.COMPILE_ACTIONS = setmetatable({ __imported: Dict{} + [""]: (tree, fn, ...)=> + lua = LuaCode.Value(tree.source) + lua\append @compile(fn, compile_actions) + lua\parenthesize! unless lua\text!\is_lua_id! + lua\append "(" + for i=1,select('#',...) + lua\append(", ") if i > 1 + lua\append @compile(select(i, ...), compile_actions) + lua\append ")" + return lua + ["Lua"]: (tree, code)=> return add_lua_string_bits(@, 'statements', code) @@ -415,15 +424,10 @@ with NomsuCompiler @compile_error tree, "The compile-time action here (#{stub}) failed to return any value.", "Look at the implementation of (#{stub}) in #{filename}:#{info.linedefined} and make sure it's returning something." - if SyntaxTree\is_instance(ret) - if ret == tree - info = debug.getinfo(compile_action, "S") - filename = Source\from_string(info.source).filename - @compile_error tree, - "The compile-time action here (#{stub}) is producing an endless loop.", - "Look at the implementation of (#{stub}) in #{filename}:#{info.linedefined} and make sure it's not just returning the original tree." + unless SyntaxTree\is_instance(ret) + return ret + if ret != tree return @compile(ret, compile_actions) - return ret lua = LuaCode.Value(tree.source) if tree.target -- Method call @@ -524,7 +528,7 @@ with NomsuCompiler "Can't this as a string interpolation value, since it's not an expression." if #lua.bits > 0 then lua\append ".." if bit.type != "Text" - bit_lua = LuaCode.Value(bit.source, "stringify(",bit_lua,")") + bit_lua = LuaCode.Value(bit.source, "tostring(",bit_lua,")") lua\append bit_lua if string_buffer ~= "" or #lua.bits == 0 @@ -536,13 +540,13 @@ with NomsuCompiler return lua when "List" - lua = LuaCode.Value tree.source, "_List{" + lua = LuaCode.Value tree.source, "List{" lua\concat_append([@compile(e, compile_actions) for e in *tree], ", ", ",\n ") lua\append "}" return lua when "Dict" - lua = LuaCode.Value tree.source, "_Dict{" + lua = LuaCode.Value tree.source, "Dict{" lua\concat_append([@compile(e, compile_actions) for e in *tree], ", ", ",\n ") lua\append "}" return lua diff --git a/utils.lua b/utils.lua deleted file mode 100644 index d45dd2d..0000000 --- a/utils.lua +++ /dev/null @@ -1,334 +0,0 @@ --- A collection of helper utility functions --- -local match, gmatch, gsub = string.match, string.gmatch, string.gsub -local function is_list(t) - if type(t) ~= 'table' then - return false - end - local i = 1 - for _ in pairs(t) do - if t[i] == nil then - return false - end - i = i + 1 - end - return true -end - -local function size(t) - local n = 0 - for _ in pairs(t) do - n = n + 1 - end - return n -end - -local repr_behavior = function(x) - local mt = getmetatable(x) - if mt then - local fn = rawget(mt, "__repr") - if fn then return fn(x) end - end -end -local function repr(x, mt_behavior) - -- Create a string representation of the object that is close to the lua code that will - -- reproduce the object (similar to Python's "repr" function) - mt_behavior = mt_behavior or repr_behavior - local x_type = type(x) - if x_type == 'table' then - local ret = mt_behavior(x) - if ret then return ret end - local ret = {} - local i = 1 - for k, v in pairs(x) do - if k == i then - ret[#ret+1] = repr(v, mt_behavior) - i = i + 1 - elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*") then - ret[#ret+1] = k.."= "..repr(v, mt_behavior) - else - ret[#ret+1] = "["..repr(k, mt_behavior).."]= "..repr(v, mt_behavior) - end - end - return "{"..table.concat(ret, ", ").."}" - elseif x_type == 'string' then - local escaped = gsub(x, "\\", "\\\\") - escaped = gsub(escaped, "\n", "\\n") - escaped = gsub(escaped, '"', '\\"') - escaped = gsub(escaped, "[%c%z]", function(c) return ("\\%03d"):format(c:byte()) end) - return '"'..escaped..'"' - else - return tostring(x) - end -end - -local stringify_behavior = function(x) - local mt = getmetatable(x) - if mt then - local fn = rawget(mt, "__tostring") - if fn then return fn(x) end - end -end -local function stringify(x) - if type(x) == 'string' then - return x - else - return repr(x, stringify_behavior) - end -end - -local function split(str, sep) - if sep == nil then - sep = "%s" - end - local ret = {} - for chunk in gmatch(str, "[^"..sep.."]+") do - ret[#ret+1] = chunk - end - return ret -end - -local function remove_from_list(list, item) - local deleted, N = 0, #list - for i=1,N do - if list[i] == item then - deleted = deleted + 1 - else - list[i-deleted] = list[i] - end - end - for i=N-deleted+1,N do list[i] = nil end -end - -local function accumulate(co) - local bits = {} - for bit in coroutine.wrap(co) do - bits[#bits+1] = bit - end - return bits -end - -local function nth_to_last(list, n) - return list[#list - n + 1] -end - -local function keys(t) - local ret = {} - for k in pairs(t) do - ret[#ret+1] = k - end - return ret -end - -local function values(t) - local ret = {} - for _,v in pairs(t) do - ret[#ret+1] = v - end - return ret -end - -local function set(list) - local ret = {} - for i=1,#list do - ret[list[i]] = true - end - return ret -end - -local function deduplicate(list) - local seen, deleted = {}, 0 - for i, item in ipairs(list) do - if seen[item] then - deleted = deleted + 1 - else - seen[item] = true - list[i-deleted] = list[i] - end - end -end - -local function sum(t) - local tot = 0 - for i=1,#t do - tot = tot + t[i] - end - return tot -end - -local function product(t) - if #t > 5 and 0 < t[1] and t[1] < 1 then - local log, log_prod = math.log, 0 - for i=1,#t do - log_prod = log_prod + log(t[i]) - end - return math.exp(log_prod) - else - local prod = 1 - for i=1,#t do - prod = prod * t[i] - end - return prod - end -end - -local function all(t) - for i=1,#t do - if not t[i] then return false end - end - return true -end - -local function any(t) - for i=1,#t do - if t[i] then return true end - end - return false -end - -local function min(list, keyFn) - if keyFn == nil then - keyFn = (function(x) - return x - end) - end - if type(keyFn) == 'table' then - local keyTable = keyFn - keyFn = function(k) - return keyTable[k] - end - end - local best = list[1] - local bestKey = keyFn(best) - for i = 2, #list do - local key = keyFn(list[i]) - if key < bestKey then - best, bestKey = list[i], key - end - end - return best -end - -local function max(list, keyFn) - if keyFn == nil then - keyFn = (function(x) - return x - end) - end - if type(keyFn) == 'table' then - local keyTable = keyFn - keyFn = function(k) - return keyTable[k] - end - end - local best = list[1] - local bestKey = keyFn(best) - for i = 2, #list do - local key = keyFn(list[i]) - if key > bestKey then - best, bestKey = list[i], key - end - end - return best -end - -local function sort(list, keyFn, reverse) - if keyFn == nil then - keyFn = (function(x) - return x - end) - end - if reverse == nil then - reverse = false - end - if type(keyFn) == 'table' then - local keyTable = keyFn - keyFn = function(k) - return keyTable[k] - end - end - table.sort(list, reverse - and (function(x,y) return keyFn(x) > keyFn(y) end) - or (function(x,y) return keyFn(x) < keyFn(y) end)) - return list -end - -local function equivalent(x, y, depth) - depth = depth or 0 - if rawequal(x, y) then - return true - end - if type(x) ~= type(y) then - return false - end - if type(x) ~= 'table' then return false end - if getmetatable(x) ~= getmetatable(y) then - return false - end - if depth >= 99 then - error("Exceeded maximum comparison depth") - end - local checked = {} - for k, v in pairs(x) do - if not equivalent(y[k], v, depth + 1) then - return false - end - checked[k] = true - end - for k, v in pairs(y) do - if not checked[k] and not equivalent(x[k], v, depth + 1) then - return false - end - end - return true -end - -local function key_for(t, value) - for k, v in pairs(t) do - if v == value then - return k - end - end - return nil -end - -local function clamp(x, min, max) - if x < min then - return min - elseif x > max then - return max - else - return x - end -end - -local function mix(min, max, amount) - return (1 - amount) * min + amount * max -end - -local function sign(x) - if x == 0 then - return 0 - elseif x < 0 then - return -1 - else - return 1 - end -end - -local function round(x, increment) - if increment == nil then - increment = 1 - end - if x >= 0 then - return math.floor(x / increment + .5) * increment - else - return math.ceil(x / increment - .5) * increment - end -end - -return {is_list=is_list, size=size, repr=repr, stringify=stringify, split=split, - remove_from_list=remove_from_list, accumulate=accumulate, nth_to_last=nth_to_last, - keys=keys, values=values, set=set, deduplicate=deduplicate, sum=sum, product=product, - all=all, any=any, min=min, max=max, sort=sort, equivalent=equivalent, key_for=key_for, - clamp=clamp, mix=mix, round=round}