diff options
| author | Bruce Hill <bruce@bruce-hill.com> | 2018-11-02 15:17:48 -0700 |
|---|---|---|
| committer | Bruce Hill <bruce@bruce-hill.com> | 2018-11-02 15:17:49 -0700 |
| commit | 0f17c5eb9ac4660f2f969bd1e67af42713e45eac (patch) | |
| tree | 279ca7da2de0efe2f363684f3c84a540635f11a8 | |
| parent | acd9c2acd4688f2301b091daad910c04e402bd6a (diff) | |
| parent | dc41f30c73c9686685e3a4183c1213fb4ba55c90 (diff) | |
Merge branch 'master' into working
63 files changed, 2945 insertions, 1182 deletions
@@ -25,12 +25,12 @@ say "Hello" for %num in %my_nums: say "\%num is one of my nums" -action [sing %n bottles of beer]: +(sing %n bottles of beer) means: for %i in %n to 1 by -1: - say ".." - \%i bottle\("s" if (%i > 1) else "") of beer on the wall, + say "\ + ..\%i bottle\("s" if (%i > 1) else "") of beer on the wall, \%i bottle\("s" if (%i > 1) else "") of beer! - Take one down, pass it around... + Take one down, pass it around..." say "No bottles of beer on the wall. Go to the store, buy some more..." sing 99 bottles of beer diff --git a/code_obj.lua b/code_obj.lua index d51d63f..b5213c9 100644 --- a/code_obj.lua +++ b/code_obj.lua @@ -3,8 +3,6 @@ do local _obj_0 = table insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat end -local repr -repr = require('utils').repr local unpack = unpack or table.unpack local LuaCode, NomsuCode, Source do @@ -13,8 +11,8 @@ do __tostring = function(self) return "@" .. tostring(self.filename) .. "[" .. tostring(self.start) .. tostring(self.stop and ':' .. self.stop or '') .. "]" end, - __repr = function(self) - return "Source(" .. tostring(repr(self.filename)) .. ", " .. tostring(self.start) .. tostring(self.stop and ', ' .. self.stop or '') .. ")" + as_lua = function(self) + return "Source(" .. tostring(self.filename:as_lua()) .. ", " .. tostring(self.start) .. tostring(self.stop and ', ' .. self.stop or '') .. ")" end, __eq = function(self, other) return getmetatable(self) == getmetatable(other) and self.filename == other.filename and self.start == other.start and self.stop == other.stop @@ -80,7 +78,7 @@ do local _class_0 local _base_0 = { is_code = true, - __tostring = function(self) + text = function(self) if self.__str == nil then local buff, indent = { }, 0 local match, gsub, rep @@ -97,7 +95,7 @@ do end end else - b = tostring(b) + b = b:text() if indent > 0 then b = gsub(b, "\n", "\n" .. rep(" ", indent)) end @@ -108,16 +106,19 @@ do end return self.__str end, - __repr = function(self) + __tostring = function(self) + return self:text() + end, + as_lua = function(self) return tostring(self.__class.__name) .. "(" .. tostring(concat({ - repr(tostring(self.source)), + tostring(self.source):as_lua(), unpack((function() local _accum_0 = { } local _len_0 = 1 local _list_0 = self.bits for _index_0 = 1, #_list_0 do local b = _list_0[_index_0] - _accum_0[_len_0] = repr(b) + _accum_0[_len_0] = b:as_lua() _len_0 = _len_0 + 1 end return _accum_0 @@ -125,13 +126,13 @@ do }, ", ")) .. ")" end, __len = function(self) - return #tostring(self) + return #self:text() end, match = function(self, ...) - return tostring(self):match(...) + return self:text():match(...) end, gmatch = function(self, ...) - return tostring(self):gmatch(...) + return self:text():gmatch(...) end, dirty = function(self) self.__str = nil @@ -157,9 +158,6 @@ do if b.is_code then b.dirty = error end - if type(b) ~= 'string' and not (type(b) == 'table' and b.is_code) then - b = repr(b) - end bits[#bits + 1] = b _continue_0 = true until true @@ -171,7 +169,7 @@ do end, trailing_line_len = function(self) if self._trailing_line_len == nil then - self._trailing_line_len = #tostring(self):match("[^\n]*$") + self._trailing_line_len = #self:text():match("[^\n]*$") end return self._trailing_line_len end, @@ -214,7 +212,9 @@ do if b.is_code then b.dirty = error end - b = tostring(b) + if not (type(b) == 'string') then + b = b:text() + end local line = match(b, "\n([^\n]*)$") if line then line_len = #line @@ -235,9 +235,6 @@ do if b.is_code then b.dirty = error end - if type(b) ~= 'string' and not (type(b) == 'table' and b.is_code) then - b = repr(b) - end bits[i] = b end return self:dirty() @@ -255,7 +252,6 @@ do if type(self.source) == 'string' then self.source = Source:from_string(self.source) end - assert(self.source and Source:is_instance(self.source), "Source has the wrong type") return self:append(...) end, __base = _base_0, @@ -276,7 +272,7 @@ do local _parent_0 = Code local _base_0 = { __tostring = Code.__tostring, - __repr = Code.__repr, + as_lua = Code.as_lua, __len = Code.__len, add_free_vars = function(self, vars) if not (#vars > 0) then @@ -357,7 +353,7 @@ do local _list_1 = self.bits for _index_0 = 1, #_list_1 do local bit = _list_1[_index_0] - if bit.__class == LuaCode then + if not (type(bit) == 'string') then gather_from(bit) end end @@ -404,8 +400,9 @@ do end else walk(b, pos) + b = b:text() end - pos = pos + #tostring(b) + pos = pos + #b end end walk(self, 1) @@ -469,7 +466,7 @@ do local _parent_0 = Code local _base_0 = { __tostring = Code.__tostring, - __repr = Code.__repr, + as_lua = Code.as_lua, __len = Code.__len } _base_0.__index = _base_0 @@ -505,12 +502,7 @@ do end NomsuCode = _class_0 end -Code.__base.append_1 = assert(Code.__base.append) -Code.__base.append_1_joined_by_2 = assert(Code.__base.concat_append) -Code.__base.prepend_1 = assert(Code.__base.prepend) -LuaCode.__base.declare_locals_1 = assert(LuaCode.__base.declare_locals) -LuaCode.__base.remove_free_vars_1 = assert(LuaCode.__base.remove_free_vars) -LuaCode.__base.add_free_vars_1 = assert(LuaCode.__base.add_free_vars) +Code.__base.add_1_joined_with = assert(Code.__base.concat_append) return { Code = Code, NomsuCode = NomsuCode, diff --git a/code_obj.moon b/code_obj.moon index bd53c5b..22969a4 100644 --- a/code_obj.moon +++ b/code_obj.moon @@ -2,7 +2,6 @@ -- build up generated code, while keeping track of where it came from, and managing -- indentation levels. {:insert, :remove, :concat} = table -{:repr} = require 'utils' unpack or= table.unpack local LuaCode, NomsuCode, Source @@ -19,7 +18,7 @@ class Source __tostring: => "@#{@filename}[#{@start}#{@stop and ':'..@stop or ''}]" - __repr: => "Source(#{repr @filename}, #{@start}#{@stop and ', '..@stop or ''})" + as_lua: => "Source(#{@filename\as_lua!}, #{@start}#{@stop and ', '..@stop or ''})" __eq: (other)=> getmetatable(@) == getmetatable(other) and @filename == other.filename and @start == other.start and @stop == other.stop @@ -48,10 +47,10 @@ class Code @bits = {} if type(@source) == 'string' @source = Source\from_string(@source) - assert(@source and Source\is_instance(@source), "Source has the wrong type") + --assert(@source and Source\is_instance(@source), "Source has the wrong type") @append(...) - __tostring: => + text: => if @__str == nil buff, indent = {}, 0 {:match, :gsub, :rep} = string @@ -60,21 +59,23 @@ class Code if spaces = match(b, "\n([ ]*)[^\n]*$") indent = #spaces else - b = tostring(b) + b = b\text! if indent > 0 b = gsub(b, "\n", "\n"..rep(" ", indent)) buff[#buff+1] = b @__str = concat(buff, "") return @__str - __repr: => - "#{@__class.__name}(#{concat {repr(tostring(@source)), unpack([repr(b) for b in *@bits])}, ", "})" + __tostring: => @text! - __len: => #tostring(@) + as_lua: => + "#{@__class.__name}(#{concat {tostring(@source)\as_lua!, unpack([b\as_lua! for b in *@bits])}, ", "})" + + __len: => #@text! - match: (...)=> tostring(@)\match(...) + match: (...)=> @text!\match(...) - gmatch: (...)=> tostring(@)\gmatch(...) + gmatch: (...)=> @text!\gmatch(...) dirty: => @__str = nil @@ -92,14 +93,14 @@ class Code assert(not Source\is_instance(b), "code bit is a Source") if b == '' then continue b.dirty = error if b.is_code - if type(b) != 'string' and not (type(b) == 'table' and b.is_code) - b = repr(b) + --if type(b) != 'string' and not (type(b) == 'table' and b.is_code) + -- b = b\as_lua! bits[#bits+1] = b @dirty! trailing_line_len: => if @_trailing_line_len == nil - @_trailing_line_len = #tostring(@)\match("[^\n]*$") + @_trailing_line_len = #@text!\match("[^\n]*$") return @_trailing_line_len is_multiline: => @@ -131,7 +132,8 @@ class Code bits[#bits+1] = joiner bits[#bits+1] = b b.dirty = error if b.is_code - b = tostring(b) + unless type(b) == 'string' + b = b\text! line = match(b, "\n([^\n]*)$") if line line_len = #line @@ -147,8 +149,8 @@ class Code for i=1,n b = select(i, ...) b.dirty = error if b.is_code - if type(b) != 'string' and not (type(b) == 'table' and b.is_code) - b = repr(b) + --if type(b) != 'string' and not (type(b) == 'table' and b.is_code) + -- b = b\as_lua! bits[i] = b @dirty! @@ -158,7 +160,7 @@ class Code class LuaCode extends Code __tostring: Code.__tostring - __repr: Code.__repr + as_lua: Code.as_lua __len: Code.__len new: (...)=> super ... @@ -208,7 +210,7 @@ class LuaCode extends Code seen[var] = true to_declare[#to_declare+1] = var for bit in *@bits - if bit.__class == LuaCode + unless type(bit) == 'string' gather_from bit gather_from self if #to_declare > 0 @@ -238,7 +240,8 @@ class LuaCode extends Code nomsu_to_lua[lua.source.start] = pos else walk b, pos - pos += #tostring(b) + b = b\text! + pos += #b walk self, 1 return { nomsu_filename:@source.filename @@ -253,14 +256,9 @@ class LuaCode extends Code class NomsuCode extends Code __tostring: Code.__tostring - __repr: Code.__repr + as_lua: Code.as_lua __len: Code.__len -Code.__base.append_1 = assert Code.__base.append -Code.__base.append_1_joined_by_2 = assert Code.__base.concat_append -Code.__base.prepend_1 = assert Code.__base.prepend -LuaCode.__base.declare_locals_1 = assert LuaCode.__base.declare_locals -LuaCode.__base.remove_free_vars_1 = assert LuaCode.__base.remove_free_vars -LuaCode.__base.add_free_vars_1 = assert LuaCode.__base.add_free_vars +Code.__base.add_1_joined_with = assert Code.__base.concat_append return {:Code, :NomsuCode, :LuaCode, :Source} diff --git a/compatibility/2.3.nom b/compatibility/2.3.nom index fb137b8..7de353e 100644 --- a/compatibility/2.3.nom +++ b/compatibility/2.3.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines upgrades from Nomsu <2.3 to Nomsu 2.3 diff --git a/compatibility/2.4.nom b/compatibility/2.4.nom index b770840..1a7c9c3 100644 --- a/compatibility/2.4.nom +++ b/compatibility/2.4.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines upgrades from Nomsu <2.4 to Nomsu 2.4 diff --git a/compatibility/2.5.5.5.nom b/compatibility/2.5.5.5.nom index a3f8b27..efed78d 100644 --- a/compatibility/2.5.5.5.nom +++ b/compatibility/2.5.5.5.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines upgrades from Nomsu <2.5.5.5 to Nomsu 2.5.5.5 diff --git a/compatibility/2.5.nom b/compatibility/2.5.nom index fe17dfb..b1e01b0 100644 --- a/compatibility/2.5.nom +++ b/compatibility/2.5.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines upgrades from Nomsu <2.5 to Nomsu 2.5 diff --git a/compatibility/2.nom b/compatibility/2.nom index 2364f56..d7a1489 100644 --- a/compatibility/2.nom +++ b/compatibility/2.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines upgrades from Nomsu 1 to Nomsu 2 diff --git a/compatibility/3.5.5.6.nom b/compatibility/3.5.5.6.nom index 068e65d..89fbc8a 100644 --- a/compatibility/3.5.5.6.nom +++ b/compatibility/3.5.5.6.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines upgrades from Nomsu <3.5.5.6 to Nomsu 3.5.5.6 diff --git a/compatibility/3.6.nom b/compatibility/3.6.nom index eec97eb..7cd5c64 100644 --- a/compatibility/3.6.nom +++ b/compatibility/3.6.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines upgrades from Nomsu <3.6 to 3.6 diff --git a/compatibility/3.7.nom b/compatibility/3.7.nom index 5b458de..599e0d5 100644 --- a/compatibility/3.7.nom +++ b/compatibility/3.7.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines upgrades from Nomsu <3.7 to 3.7 diff --git a/compatibility/3.8.nom b/compatibility/3.8.nom index 9538770..0709688 100644 --- a/compatibility/3.8.nom +++ b/compatibility/3.8.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines upgrades from Nomsu <3.8 to 3.8 (Text method changes) diff --git a/compatibility/3.nom b/compatibility/3.nom index 36df17d..5156aa8 100644 --- a/compatibility/3.nom +++ b/compatibility/3.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines upgrades from Nomsu <=2 to Nomsu 3 diff --git a/compatibility/4.8.10.nom b/compatibility/4.8.10.nom new file mode 100644 index 0000000..66152b5 --- /dev/null +++ b/compatibility/4.8.10.nom @@ -0,0 +1,60 @@ +#!/usr/bin/env nomsu -V4.8.10.6 +# + This file defines upgrades from Nomsu <4.8.10 to 4.8.10 (renaming "action" -> "means") +use "compatibility/compatibility.nom" + +upgrade action "local action 1 2" to "4.8.10" via (..) + [%tree, %end_version] ->: + %spec = %tree.3 + %body = %tree.4 + if %spec.type is: + "List": + if ((size of %spec) == 1): + return \(%spec.1 means %body) + ..else: + return \(%spec all mean %body) + else: + return \(%spec means %body) + +upgrade action "action 1 2" to "4.8.10" via (..) + [%tree, %end_version] ->: + %spec = %tree.2 + %body = %tree.3 + if %spec.type is: + "List": + if ((size of %spec) == 1): + return \(externally %spec.1 means %body) + ..else: + return \(externally %spec all mean %body) + else: + return \(externally %spec means %body) + +upgrade action "compile 1 to 2" to "4.8.10" via (..) + [%tree, %end_version] ->: + %spec = %tree.2 + %body = %tree.4 + if %spec.type is: + "List": + if ((size of %spec) == 1): + return \(%spec.1 compiles to %body) + ..else: + return \(%spec all compile to %body) + else: + return \(%spec compiles to %body) + +upgrade action "parse 1 as 2" to "4.8.10" via (..) + [%tree, %end_version] ->: + %spec = %tree.2 + %body = %tree.4 + if %spec.type is: + "List": + if ((size of %spec) == 1): + return \(%spec.1 parses as %body) + ..else: + return \(%spec all parse as %body) + else: + return \(%spec parse as %body) + +upgrade action (compile as %) to "4.8.10" as (what % compiles to) +upgrade action (action %) to "4.8.10" as (%'s meaning) +upgrade action (remove action %) to "4.8.10" as ((%'s meaning) = (nil)) diff --git a/compatibility/compatibility.nom b/compatibility/compatibility.nom index acd1d11..1153ab4 100644 --- a/compatibility/compatibility.nom +++ b/compatibility/compatibility.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file contains code for defining ways to upgrade code between different versions of Nomsu. @@ -6,17 +6,17 @@ use "lib/os.nom" %UPGRADES = {} -action [upgrade to %version via %upgrade_fn]: +externally (upgrade to %version via %upgrade_fn) means: %UPGRADES.%version = %upgrade_fn %ACTION_UPGRADES = ({} with fallback % -> {}) -action [upgrade action %stub to %version via %upgrade_fn]: +externally (upgrade action %stub to %version via %upgrade_fn) means: %ACTION_UPGRADES.%version.%stub = %upgrade_fn -parse [upgrade %tree to %version as %body] as (..) - upgrade to %version via ([%] -> (% with %tree -> %body)) +(upgrade %tree to %version as %body) parses as (..) + upgrade to %version via ([%, %end_version] -> (% with %tree -> %body)) -compile [upgrade action %actions to %version as %body] to: +(upgrade action %actions to %version as %body) compiles to: if (%actions is "Action" syntax tree): %actions = \[%actions] %lua = (Lua "") @@ -27,7 +27,7 @@ compile [upgrade action %actions to %version as %body] to: %replacements.(%action.%i.1) = "\(\%tree as lua id)[\%i]" define mangler - local action [make tree %t]: + (make tree %t) means: when: (%t is "Var" syntax tree): if %replacements.(%t.1): @@ -52,7 +52,7 @@ compile [upgrade action %actions to %version as %body] to: %retval = (make tree %body) %lua::append (..) Lua "\ - ..upgrade_action_1_to_2_via_3(\(quote %action.stub), \(%version as lua expr), function(\(..) + ..upgrade_action_1_to_2_via(\(quote %action.stub), \(%version as lua expr), function(\(..) \%tree as lua id ..) return \%retval @@ -60,11 +60,12 @@ compile [upgrade action %actions to %version as %body] to: return %lua -action [..] +externally [..] %tree upgraded from %start_version to %end_version %tree upgraded to %end_version from %start_version -..: - local action [%ver as list] ((% as number) for % in %ver matching "[0-9]+") +..all mean: + unless (%tree is syntax tree): return %tree + (%ver as list) means ((% as number) for % in %ver matching "[0-9]+") %versions = {} for %v = % in %UPGRADES: %versions.%v = (yes) @@ -78,25 +79,30 @@ action [..] %tree = (..) %tree with % -> (..) if ((% is "Action" syntax tree) and %ACTION_UPGRADES.%ver.(%.stub)): - return (call %ACTION_UPGRADES.%ver.(%.stub) with [%]) + %with_upgraded_args = (..) + %k = (%v upgraded from %start_version to %end_version) for %k = %v in % + set %with_upgraded_args 's metatable to (% 's metatable) + return (..) + call %ACTION_UPGRADES.%ver.(%.stub) with [%with_upgraded_args, %end_version] if %UPGRADES.%ver: - %tree = (call %UPGRADES.%ver with [%tree]) + %with_upgraded_args = (..) + %k = (%v upgraded from %start_version to %end_version) for %k = %v in %tree + set %with_upgraded_args 's metatable to (%tree 's metatable) + %tree = (call %UPGRADES.%ver with [%with_upgraded_args, %end_version]) return %tree -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -parse [%tree upgraded from %start_version] as (..) +externally (%tree upgraded from %start_version) means (..) %tree upgraded from %start_version to (Nomsu version) -parse [%tree upgraded to %end_version] as (..) +externally (%tree upgraded to %end_version) means (..) %tree upgraded from (%tree.version or (Nomsu version)) to %end_version -parse [%tree upgraded] as (..) +externally (%tree upgraded) means (..) %tree upgraded from (%tree.version or (Nomsu version)) to (Nomsu version) -action [use %path from version %version]: +externally (use %path from version %version) means: for file %filename in %path: if (=lua "LOADED[\%filename]"): do next %filename %file = (read file %filename) diff --git a/containers.lua b/containers.lua index 1b3fd94..3148dcf 100644 --- a/containers.lua +++ b/containers.lua @@ -3,15 +3,52 @@ do local _obj_0 = table insert, remove, concat = _obj_0.insert, _obj_0.remove, _obj_0.concat end -local repr, stringify, equivalent, nth_to_last, size +local equivalent, nth_to_last, size do local _obj_0 = require('utils') - repr, stringify, equivalent, nth_to_last, size = _obj_0.repr, _obj_0.stringify, _obj_0.equivalent, _obj_0.nth_to_last, _obj_0.size + 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 + return tostring(self) + end + do + local mt = getmetatable(self) + if mt then + do + local _as_nomsu = mt.as_nomsu + if _as_nomsu then + return _as_nomsu(self) + end + end + end + end + return tostring(self) +end +local as_lua +as_lua = function(self) + if type(self) == 'number' then + return tostring(self) + end + do + local mt = getmetatable(self) + if mt then + do + local _as_lua = mt.as_lua + if _as_lua then + return _as_lua(self) + end + end + end + end + return tostring(self) +end local _list_mt = { + __type = "List", __eq = equivalent, __tostring = function(self) return "[" .. concat((function() @@ -19,12 +56,36 @@ local _list_mt = { local _len_0 = 1 for _index_0 = 1, #self do local b = self[_index_0] - _accum_0[_len_0] = repr(b) + _accum_0[_len_0] = as_nomsu(b) _len_0 = _len_0 + 1 end return _accum_0 end)(), ", ") .. "]" end, + as_nomsu = function(self) + return "[" .. concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #self do + local b = self[_index_0] + _accum_0[_len_0] = as_nomsu(b) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") .. "]" + end, + as_lua = function(self) + return "_List{" .. concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #self do + local b = self[_index_0] + _accum_0[_len_0] = as_lua(b) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") .. "}" + end, __lt = function(self, other) assert(type(self) == 'table' and type(other) == 'table', "Incompatible types for comparison") for i = 1, math.max(#self, #other) do @@ -73,15 +134,15 @@ local _list_mt = { return ret end, __index = { - add_1 = insert, - append_1 = insert, - add_1_at_index_2 = function(t, x, i) + add = insert, + append = insert, + add_1_at_index = function(t, x, i) return insert(t, i, x) end, - at_index_1_add_2 = insert, + at_index_1_add = insert, pop = remove, remove_last = remove, - remove_index_1 = remove, + remove_index = remove, last = (function(self) return self[#self] end), @@ -104,7 +165,7 @@ local _list_mt = { return _accum_0 end)()) end, - joined_with_1 = function(self, glue) + joined_with = function(self, glue) return table.concat((function() local _accum_0 = { } local _len_0 = 1 @@ -116,7 +177,7 @@ local _list_mt = { return _accum_0 end)(), glue) end, - has_1 = function(self, item) + has = function(self, item) for _index_0 = 1, #self do local x = self[_index_0] if x == item then @@ -125,13 +186,36 @@ local _list_mt = { end return false end, - index_of_1 = function(self, item) + remove = function(self, item) + for i, x in ipairs(self) do + if x == item then + remove(self, i) + end + end + end, + index_of = function(self, item) for i, x in ipairs(self) do if x == item then return i end end return nil + end, + from_1_to = function(self, start, stop) + local n = #self + if n < 0 then + start = (n + 1 - start) + end + if n < 0 then + stop = (n + 1 - stop) + end + local _accum_0 = { } + local _len_0 = 1 + for i = start, stop do + _accum_0[_len_0] = self[i] + _len_0 = _len_0 + 1 + end + return _accum_0 end }, __newindex = function(self, k, v) @@ -139,6 +223,8 @@ local _list_mt = { return rawset(self, k, v) end } +_list_mt.__index.as_lua = _list_mt.as_lua +_list_mt.__index.as_nomsu = _list_mt.as_nomsu List = function(t) return setmetatable(t, _list_mt) end @@ -155,6 +241,7 @@ walk_items = function(self, i) end end local _dict_mt = { + __type = "Dict", __eq = equivalent, __len = size, __tostring = function(self) @@ -162,7 +249,29 @@ local _dict_mt = { local _accum_0 = { } local _len_0 = 1 for k, v in pairs(self) do - _accum_0[_len_0] = tostring(repr(k)) .. ": " .. tostring(repr(v)) + _accum_0[_len_0] = tostring(as_nomsu(k)) .. ": " .. tostring(as_nomsu(v)) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") .. "}" + end, + as_nomsu = function(self) + return "{" .. concat((function() + local _accum_0 = { } + local _len_0 = 1 + for k, v in pairs(self) do + _accum_0[_len_0] = tostring(as_nomsu(k)) .. ": " .. tostring(as_nomsu(v)) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ") .. "}" + end, + as_lua = function(self) + return "_Dict{" .. concat((function() + local _accum_0 = { } + local _len_0 = 1 + for k, v in pairs(self) do + _accum_0[_len_0] = "[ " .. tostring(as_lua(k)) .. "]= " .. tostring(as_lua(v)) _len_0 = _len_0 + 1 end return _accum_0 @@ -271,19 +380,23 @@ do reverse, upper, lower, find, byte, match, gmatch, gsub, sub, format, rep = _obj_0.reverse, _obj_0.upper, _obj_0.lower, _obj_0.find, _obj_0.byte, _obj_0.match, _obj_0.gmatch, _obj_0.gsub, _obj_0.sub, _obj_0.format, _obj_0.rep end local string2 = require('string2') - local lines, line, line_at, as_lua_id - lines, line, line_at, as_lua_id = string2.lines, string2.line, string2.line_at, string2.as_lua_id + local lines, line, line_at, as_lua_id, is_lua_id + lines, line, line_at, as_lua_id, is_lua_id = string2.lines, string2.line, string2.line_at, string2.as_lua_id, string2.is_lua_id local text_methods = { - formatted_with_1 = format, - byte_1 = byte, - position_of_1 = find, - position_of_1_after_2 = find, - bytes_1_to_2 = function(self, start, stop) + formatted_with = format, + byte = byte, + position_of = find, + position_of_1_after = find, + as_a_lua_identifier = as_lua_id, + is_a_lua_identifier = is_lua_id, + as_a_lua_id = as_lua_id, + is_a_lua_id = is_lua_id, + bytes_1_to = function(self, start, stop) return List({ byte(tostring(self), start, stop) }) end, - [as_lua_id("with 1 -> 2")] = gsub, + [as_lua_id("with 1 ->")] = gsub, bytes = function(self) return List({ byte(tostring(self), 1, -1) @@ -292,8 +405,8 @@ do lines = function(self) return List(lines(self)) end, - line_1 = line, - wrap_to_1 = function(self, maxlen) + line = line, + wrapped_to = function(self, maxlen) local _lines = { } local _list_0 = self:lines() for _index_0 = 1, #_list_0 do @@ -309,22 +422,30 @@ do end return table.concat(_lines, "\n") end, - line_at_1 = function(self, i) + line_at = function(self, i) return (line_at(self, i)) end, - line_number_of_1 = function(self, i) + line_number_at = function(self, i) return select(2, line_at(self, i)) end, - line_position_of_1 = function(self, i) + line_position_at = function(self, i) return select(3, line_at(self, i)) end, - matches_1 = function(self, patt) + matches = function(self, patt) return match(self, patt) and true or false end, + matching = function(self, patt) + return (match(self, patt)) + end, + matching_groups = function(self, patt) + return { + match(self, patt) + } + end, [as_lua_id("* 1")] = function(self, n) return rep(self, n) end, - matching_1 = function(self, patt) + all_matches_of = function(self, patt) local result = { } local stepper, x, i = gmatch(self, patt) while true do @@ -343,6 +464,7 @@ do setmetatable(text_methods, { __index = string2 }) + getmetatable("").__methods = text_methods getmetatable("").__index = function(self, i) if type(i) == 'number' then return sub(self, i, i) @@ -352,6 +474,9 @@ do return text_methods[i] end end + getmetatable("").__add = function(self, x) + return tostring(self) .. tostring(x) + end end return { List = List, diff --git a/containers.moon b/containers.moon index eaf007a..4671f6b 100644 --- a/containers.moon +++ b/containers.moon @@ -1,19 +1,41 @@ -- This file contains container classes, i.e. Lists, Dicts, and Sets {:insert,:remove,:concat} = table -{:repr, :stringify, :equivalent, :nth_to_last, :size} = require 'utils' +{:equivalent, :nth_to_last, :size} = require 'utils' lpeg = require 'lpeg' re = require 're' local List, Dict +as_nomsu = => + if type(@) == 'number' + return tostring(@) + if mt = getmetatable(@) + if _as_nomsu = mt.as_nomsu + return _as_nomsu(@) + return tostring(@) + +as_lua = => + if type(@) == 'number' + return tostring(@) + if mt = getmetatable(@) + if _as_lua = mt.as_lua + return _as_lua(@) + return tostring(@) + -- 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 -- Could consider adding a __newindex to enforce list-ness, but would hurt performance __tostring: => - "["..concat([repr(b) for b in *@], ", ").."]" + "["..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 *@], ", ").."}" __lt: (other)=> assert type(@) == 'table' and type(other) == 'table', "Incompatible types for comparison" for i=1,math.max(#@, #other) @@ -36,30 +58,41 @@ _list_mt = insert(ret, x) return ret __index: - add_1: insert, append_1: insert - add_1_at_index_2: (t,x,i)-> insert(t,i,x) - at_index_1_add_2: insert - pop: remove, remove_last: remove, remove_index_1: remove + add: insert, append: insert + add_1_at_index: (t,x,i)-> insert(t,i,x) + at_index_1_add: insert + pop: remove, remove_last: remove, remove_index: remove 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_1: (glue)=> table.concat([tostring(x) for x in *@], glue), - has_1: (item)=> + joined_with: (glue)=> table.concat([tostring(x) for x in *@], glue), + has: (item)=> for x in *@ if x == item return true return false - index_of_1: (item)=> + remove: (item)=> + for i,x in ipairs @ + if x == item + remove(@, i) + index_of: (item)=> for i,x in ipairs @ if x == item return i return nil + from_1_to: (start, stop)=> + n = #@ + start = (n+1-start) if n < 0 + stop = (n+1-stop) if n < 0 + return [@[i] for i=start,stop] -- TODO: remove this safety check to get better performance? __newindex: (k,v)=> assert type(k) == 'number', "List indices must be numbers" rawset(@, k, v) +_list_mt.__index.as_lua = _list_mt.as_lua +_list_mt.__index.as_nomsu = _list_mt.as_nomsu List = (t)-> setmetatable(t, _list_mt) @@ -71,10 +104,15 @@ walk_items = (i)=> return i, Dict{key:k, value:v} _dict_mt = + __type: "Dict" __eq:equivalent __len:size __tostring: => - "{"..concat(["#{repr(k)}: #{repr(v)}" for k,v in pairs @], ", ").."}" + "{"..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 @], ", ").."}" __ipairs: => walk_items, {table:@, key:nil}, 0 __band: (other)=> Dict{k,v for k,v in pairs(@) when other[k] != nil} @@ -109,15 +147,17 @@ for i,entry in ipairs(Dict({x:99})) do {:reverse, :upper, :lower, :find, :byte, :match, :gmatch, :gsub, :sub, :format, :rep} = string string2 = require 'string2' - {:lines, :line, :line_at, :as_lua_id} = string2 + {:lines, :line, :line_at, :as_lua_id, :is_lua_id} = string2 text_methods = - formatted_with_1:format, byte_1:byte, position_of_1:find, position_of_1_after_2:find, - bytes_1_to_2: (start, stop)=> List{byte(tostring(@), start, stop)} - [as_lua_id "with 1 -> 2"]: gsub + formatted_with:format, byte:byte, position_of:find, position_of_1_after:find, + as_a_lua_identifier: as_lua_id, is_a_lua_identifier: is_lua_id, + as_a_lua_id: as_lua_id, is_a_lua_id: is_lua_id, + bytes_1_to: (start, stop)=> List{byte(tostring(@), start, stop)} + [as_lua_id "with 1 ->"]: gsub bytes: => List{byte(tostring(@), 1, -1)}, lines: => List(lines(@)) - line_1: line - wrap_to_1: (maxlen)=> + line: line + wrapped_to: (maxlen)=> _lines = {} for line in *@lines! while #line > maxlen @@ -129,12 +169,14 @@ do _lines[#_lines+1] = line return table.concat(_lines, "\n") - line_at_1: (i)=> (line_at(@, i)) - line_number_of_1: (i)=> select(2, line_at(@, i)) - line_position_of_1: (i)=> select(3, line_at(@, i)) - matches_1: (patt)=> match(@, patt) and true or false + line_at: (i)=> (line_at(@, i)) + line_number_at: (i)=> select(2, line_at(@, i)) + line_position_at: (i)=> select(3, line_at(@, i)) + matches: (patt)=> match(@, patt) and true or false + matching: (patt)=> (match(@, patt)) + matching_groups: (patt)=> {match(@, patt)} [as_lua_id "* 1"]: (n)=> rep(@, n) - matching_1: (patt)=> + all_matches_of: (patt)=> result = {} stepper,x,i = gmatch(@, patt) while true @@ -146,10 +188,13 @@ do setmetatable(text_methods, {__index:string2}) + getmetatable("").__methods = text_methods getmetatable("").__index = (i)=> -- Use [] for accessing text characters, or s[{3,4}] for s:sub(3,4) if type(i) == 'number' then return sub(@, i, i) elseif type(i) == 'table' then return sub(@, i[1], i[2]) else return text_methods[i] + getmetatable("").__add = (x)=> tostring(@)..tostring(x) + return {:List, :Dict} diff --git a/core/collections.nom b/core/collections.nom index 8dbc79d..ae7f5f1 100644 --- a/core/collections.nom +++ b/core/collections.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file contains code that supports manipulating and using collections like lists and dictionaries. @@ -45,17 +45,17 @@ test: # List Comprehension test: assume (((% * %) for % in [1, 2, 3]) == [1, 4, 9]) -parse [%expression for %item in %iterable] as (..) +(%expression for %item in %iterable) parses as (..) result of: %comprehension = [] for %item in %iterable: %comprehension::add %expression return %comprehension -parse [..] +[..] %expression for %index in %start to %stop via %step %expression for %index in %start to %stop by %step -..as (..) +..all parse as (..) result of: %comprehension = [] for %index in %start to %stop via %step: @@ -66,15 +66,15 @@ parse [..] test: assume (((% * %) for % in 1 to 3) == [1, 4, 9]) -parse [%expression for %var in %start to %stop] as (..) +(%expression for %var in %start to %stop) parses as (..) %expression for %var in %start to %stop via 1 test: assume (("\%k,\%v" for %k = %v in {x:1}) == ["x,1"]) -parse [..] +[..] %expression for %key = %value in %iterable %expression for %key %value in %iterable -..as (..) +..all parse as (..) result of: %comprehension = [] for %key = %value in %iterable: @@ -84,7 +84,9 @@ parse [..] # Dict comprehensions test: assume (((% * %) = % for % in [1, 2, 3]) == {1:1, 4:2, 9:3}) -parse [%key = %value for %item in %iterable, %key %value for %item in %iterable] as (..) +[..] + %key = %value for %item in %iterable, %key %value for %item in %iterable +..all parse as (..) result of: %comprehension = {} for %item in %iterable: @@ -93,20 +95,20 @@ parse [%key = %value for %item in %iterable, %key %value for %item in %iterable] test: assume ((%k = (%v * %v) for %k = %v in {x:1, y:2, z:3}) == {x:1, y:4, z:9}) -parse [..] +[..] %key = %value for %src_key = %src_value in %iterable %key %value for %src_key %src_value in %iterable -..as (..) +..all parse as (..) result of: %comprehension = {} for %src_key = %src_value in %iterable: %comprehension.%key = %value return %comprehension -parse [..] +[..] %key = %value for %item in %start to %stop via %step %key %value for %item in %start to %stop via %step -..as (..) +..all parse as (..) result of: %comprehension = {} for %item in %start to %stop via %step: @@ -117,14 +119,14 @@ parse [..] test: assume (((% * %) = % for % in 1 to 3) == {1:1, 4:2, 9:3}) -parse [..] +[..] %key = %value for %item in %start to %stop %key %value for %item in %start to %stop -..as (%key = %value for %item in %start to %stop via 1) +..all parse as (%key = %value for %item in %start to %stop via 1) test: assume (([[1, 2], [3, 4]] flattened) == [1, 2, 3, 4]) -action [%lists flattened]: +externally (%lists flattened) means: %flat = [] for %list in %lists: for %item in %list: %flat::add %item @@ -132,27 +134,30 @@ action [%lists flattened]: test: assume ((entries in {x:1}) == [{key:"x", value:1}]) -parse [entries in %dict] as ({key:%k, value:%v} for %k = %v in %dict) +(entries in %dict) parses as ({key:%k, value:%v} for %k = %v in %dict) test: assume ((keys in {x:1}) == ["x"]) -parse [keys in %dict, keys of %dict] as (%k for %k = %v in %dict) +[keys in %dict, keys of %dict] all parse as (%k for %k = %v in %dict) test: assume ((values in {x:1}) == [1]) -parse [values in %dict, values of %dict] as (%v for %k = %v in %dict) +[values in %dict, values of %dict] all parse as (%v for %k = %v in %dict) # Metatable stuff test: %t = {} set %t 's metatable to {__tostring:[%] -> "XXX"} assume ("\%t" == "XXX") -compile [set %dict 's metatable to %metatable] to (..) +(set %dict 's metatable to %metatable) compiles to (..) Lua "setmetatable(\(%dict as lua expr), \(%metatable as lua expr));" +[% 's metatable, % 'metatable] all compile to (..) + Lua value "getmetatable(\(% as lua expr))" + test: assume (({} with fallback % -> (% + 1)).10 == 11) -compile [%dict with fallback %key -> %value] to (..) +(%dict with fallback %key -> %value) compiles to (..) Lua value "\ ..(function(d) local mt = {} @@ -175,8 +180,10 @@ test: %keys = {1:999, 2:0, 3:50} sort %x by % = %keys.% assume (%x == [2, 3, 1]) -compile [sort %items] to (Lua "table.sort(\(%items as lua expr));") -parse [sort %items by %item = %key_expr, sort %items by %item -> %key_expr] as (..) +(sort %items) compiles to (Lua "table.sort(\(%items as lua expr));") +[..] + sort %items by %item = %key_expr, sort %items by %item -> %key_expr +..all parse as (..) do: %keys = ({} with fallback %item -> %key_expr) lua> "table.sort(\%items, function(x,y) return \%keys[x] < \%keys[y] end)" @@ -185,12 +192,12 @@ parse [sort %items by %item = %key_expr, sort %items by %item -> %key_expr] as ( test: assume ((sorted [3, 1, 2]) == [1, 2, 3]) -action [%items sorted, sorted %items]: +externally [%items sorted, sorted %items] all mean: %copy = (% for % in %items) sort %copy return %copy -parse [%items sorted by %item = %key, %items sorted by %item -> %key] as (..) +[%items sorted by %item = %key, %items sorted by %item -> %key] all parse as (..) result of: %copy = (% for % in %items) sort %copy by %item = %key @@ -198,7 +205,7 @@ parse [%items sorted by %item = %key, %items sorted by %item -> %key] as (..) test: assume ((unique [1, 2, 1, 3, 2, 3]) == [1, 2, 3]) -action [unique %items]: +externally (unique %items) means: %unique = [] %seen = {} for % in %items: diff --git a/core/control_flow.nom b/core/control_flow.nom index a9e0ae0..bbc98f1 100644 --- a/core/control_flow.nom +++ b/core/control_flow.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file contains compile-time actions that define basic control flow structures like "if" statements and loops. @@ -10,31 +10,35 @@ use "core/errors.nom" # No-Op test: do nothing -compile [do nothing] to (Lua "") +(do nothing) compiles to (Lua "") # Conditionals test: if (no): barf "conditional fail" -compile [if %condition %if_body] to (..) - Lua "\ - ..if \(%condition as lua expr) then - \(%if_body as lua statements) - end" +(if %condition %if_body) compiles to: + %lua = (Lua "if ") + %lua::append (%condition as lua expr) + %lua::append " then\n " + %lua::append (%if_body as lua statements) + %lua::append "\nend" + return %lua test: unless (yes): barf "conditional fail" -parse [unless %condition %unless_body] as (if (not %condition) %unless_body) -compile [..] +(unless %condition %unless_body) parses as (if (not %condition) %unless_body) +[..] if %condition %if_body else %else_body, unless %condition %else_body else %if_body -..to (..) - Lua "\ - ..if \(%condition as lua expr) then - \(%if_body as lua statements) - else - \(%else_body as lua statements) - end" +..all compile to: + %lua = (Lua "if ") + %lua::append (%condition as lua expr) + %lua::append " then\n " + %lua::append (%if_body as lua statements) + %lua::append "\nelse\n " + %lua::append (%else_body as lua statements) + %lua::append "\nend" + return %lua ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -44,12 +48,12 @@ compile [..] test: assume ((1 if (yes) else 2) == 1) assume ((1 if (no) else 2) == 2) -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 (..) +..all compile 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 {Text:yes, List:yes, Dict:yes, Number:yes}.(%when_true_expr.type): @@ -72,6 +76,8 @@ compile [..] end end)())" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # GOTOs test: %i = 0 @@ -79,14 +85,37 @@ test: %i += 1 unless (%i == 10): go to %loop assume (%i == 10) -compile [=== %label ===, --- %label ---, *** %label ***] to (..) - Lua "::label_\(%label as lua identifier)::" + === (Loop) === + %i -= 1 + unless (%i == 0): go to (Loop) + assume (%i == 0) +[=== %label ===, --- %label ---, *** %label ***] all compile to (..) + Lua "\ + ..::label_\(..) + (%label.stub if (%label.type == "Action") else %label) as lua identifier + ..::" -compile [go to %label] to (Lua "goto label_\(%label as lua identifier)") +(go to %label) compiles to (..) + Lua "\ + ..goto label_\(..) + (%label.stub if (%label.type == "Action") else %label) as lua identifier + .." # Basic loop control -compile [do next] to (Lua "goto continue") -compile [stop] to (Lua "break") +(stop %var) compiles to: + if %var: + return (..) + Lua "goto stop_\((%var.stub if (%var.type == "action") else %var) as lua identifier)" + ..else: return (Lua "break") +(do next %var) compiles to: + if %var: + return (..) + Lua "goto continue_\((%var.stub if (%var.type == "action") else %var) as lua identifier)" + ..else: return (Lua "goto continue") +[===stop %var ===, ---stop %var ---, ***stop %var ***] all compile to (..) + Lua "::stop_\((%var.stub if (%var.type == "action") else %var) as lua identifier)::" +[===next %var ===, ---next %var ---, ***next %var ***] all compile to (..) + Lua "::continue_\((%var.stub if (%var.type == "action") else %var) as lua identifier)::" # While loops test: @@ -108,9 +137,9 @@ test: barf "Failed to 'do next repeat'" assume (%x == 30) -compile [do next repeat] to (Lua "goto continue_repeat") -compile [stop repeating] to (Lua "goto stop_repeat") -compile [repeat while %condition %body] to: +(do next repeat) compiles to (Lua "goto continue_repeat") +(stop repeating) compiles to (Lua "goto stop_repeat") +(repeat while %condition %body) compiles to: %lua = (..) Lua "\ ..while \(%condition as lua expr) do @@ -122,53 +151,44 @@ compile [repeat while %condition %body] to: %lua::append "\n ::continue_repeat::" %lua::append "\nend --while-loop" if (%body has subtree \(stop repeating)): - %lua = (..) - Lua "\ - ..do -- scope of "stop repeating" label - \%lua - ::stop_repeat:: - end -- end of "stop repeating" label scope" + %inner_lua = %lua + %lua = (Lua "do -- scope of 'stop repeating' label\n ") + %lua::append %inner_lua + %lua::append "\ + .. + ::stop_repeat:: + end -- end of 'stop repeating' label scope" return %lua -parse [repeat %body] as (repeat while (yes) %body) -parse [repeat until %condition %body] as (repeat while (not %condition) %body) +(repeat %body) parses as (repeat while (yes) %body) +(repeat until %condition %body) parses as (repeat while (not %condition) %body) test: %x = 0 repeat 10 times: %x += 1 assume (%x == 10) -compile [repeat %n times %body] to: +(repeat %n times %body) compiles to: define mangler %lua = (..) - Lua "\ - ..for \(mangle "i")=1,\(%n as lua expr) do - \(%body as lua statements)" - + Lua "for \(mangle "i")=1,\(%n as lua expr) do\n " + %lua::append (%body as lua statements) if (%body has subtree \(do next)): %lua::append "\n ::continue::" if (%body has subtree \(do next repeat)): %lua::append "\n ::continue_repeat::" %lua::append "\nend --numeric for-loop" if (%body has subtree \(stop repeating)): - %lua = (..) - Lua "\ - ..do -- scope of "stop repeating" label - \%lua - ::stop_repeat:: - end -- end of "stop repeating" label scope" + %inner_lua = %lua + %lua = (Lua "do -- scope of 'stop repeating' label\n ") + %lua::append %inner_lua + %lua::append "\ + .. + ::stop_repeat:: + end -- end of 'stop repeating' label scope" return %lua -# For loop control flow -compile [stop %var] to (Lua "goto stop_\(%var as lua identifier)") -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)::" - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test: @@ -191,38 +211,41 @@ test: assume (%nums == [1, -2, 3, -2, 3, 4, 3, 4, 5]) # Numeric range for loops -compile [..] +[..] for %var in %start to %stop by %step %body for %var in %start to %stop via %step %body -..to: +..all compile to: # This uses Lua's approach of only allowing loop-scoped variables in a loop unless (%var.type is "Var"): - compile error at %var "Expected a variable here, not a \(%var.type)." + compile error at %var "Expected a variable here, not a \(%var.type)" %lua = (..) Lua "\ ..for \(%var as lua expr)=\(%start as lua expr),\(%stop as lua expr),\(..) %step as lua expr - .. do - \(%body as lua statements)" + .. do" + %lua::append "\n " + %lua::append (%body as lua statements) if (%body has subtree \(do next)): %lua::append "\n ::continue::" if (%body has subtree \(do next %var)): - %lua::append "\n \(compile as (===next %var ===))" + %lua::append "\n " + %lua::append (what (===next %var ===) compiles to) + %lua::append "\nend --numeric for-loop" if (%body has subtree \(stop %var)): - %lua = (..) - Lua "\ - ..do -- scope for stopping for-loop - \%lua - \(compile as (===stop %var ===)) - end -- end of scope for stopping for-loop" + %inner_lua = %lua + %lua = (Lua "do -- scope for stopping for-loop\n ") + %lua::append %inner_lua + %lua::append "\n " + %lua::append (what (===stop %var ===) compiles to) + %lua::append "\nend -- end of scope for stopping for-loop" return %lua ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -parse [for %var in %start to %stop %body] as (..) +(for %var in %start to %stop %body) parses as (..) for %var in %start to %stop via 1 %body test: @@ -239,28 +262,49 @@ test: assume (%b == [20, 30, 40]) # For-each loop (lua's "ipairs()") -compile [for %var in %iterable %body] to: - # This uses Lua's approach of only allowing loop-scoped variables in a loop - unless (%var.type is "Var"): - compile error at %var "Expected a variable here, not a \(%var.type)." +(for %var in %iterable %body) compiles to: define mangler + # This uses Lua's approach of only allowing loop-scoped variables in a loop %lua = (..) - Lua "\ - ..for \(mangle "i"),\(%var as lua identifier) in ipairs(\(%iterable as lua expr)) do - \(%body as lua statements)" + Lua "for \(mangle "i"),\(%var as lua identifier) in ipairs(\(%iterable as lua expr)) do\n " + %lua::append (%body as lua statements) + if (%body has subtree \(do next)): + %lua::append "\n ::continue::" + if (%body has subtree \(do next %var)): + %lua::append "\n " + %lua::append (what (===next %var ===) compiles to) + %lua::append "\nend --foreach-loop" + if (%body has subtree \(stop %var)): + %inner_lua = %lua + %lua = (Lua "do -- scope for stopping for-loop\n ") + %lua::append %inner_lua + %lua::append "\n " + %lua::append (what (===stop %var ===) compiles to) + %lua::append "\nend -- end of scope for stopping for-loop" + + return %lua + +# TODO: reduce code duplication +(for %var in %iterable at %i %body) compiles to: + # This uses Lua's approach of only allowing loop-scoped variables in a loop + %lua = (..) + Lua "for \(%i as lua identifier),\(%var as lua identifier) in ipairs(\(%iterable as lua expr)) do\n " + %lua::append (%body as lua statements) if (%body has subtree \(do next)): %lua::append "\n ::continue::" if (%body has subtree \(do next %var)): - %lua::append (Lua "\n\(compile as (===next %var ===))") + %lua::append "\n " + %lua::append (what (===next %var ===) compiles to) + %lua::append "\nend --foreach-loop" if (%body has subtree \(stop %var)): - %lua = (..) - Lua "\ - ..do -- scope for stopping for-loop - \%lua - \(compile as (===stop %var ===)) - end -- end of scope for stopping for-loop" + %inner_lua = %lua + %lua = (Lua "do -- scope for stopping for-loop\n ") + %lua::append %inner_lua + %lua::append "\n " + %lua::append (what (===stop %var ===) compiles to) + %lua::append "\nend -- end of scope for stopping for-loop" return %lua @@ -275,46 +319,55 @@ test: assume ((%result sorted) == ["c = 30", "d = 40", "e = 50"]) # Dict iteration (lua's "pairs()") -compile [..] +[..] for %key = %value in %iterable %body, for %key %value in %iterable %body -..to: +..all compile to: # This uses Lua's approach of only allowing loop-scoped variables in a loop unless (%key.type is "Var"): - compile error at %key "Expected a variable here, not a \(%key.type)." + compile error at %key "Expected a variable here, not a \(%key.type)" unless (%value.type is "Var"): - compile error at %value "Expected a variable here, not a \(%value.type)." + compile error at %value "Expected a variable here, not a \(%value.type)" %lua = (..) Lua "\ ..for \(%key as lua identifier),\(%value as lua identifier) in pairs(\(..) %iterable as lua expr - ..) do - \(%body as lua statements)" + ..) do" + %lua::append "\n " + %lua::append (%body as lua statements) if (%body has subtree \(do next)): %lua::append "\n ::continue::" if (%body has subtree \(do next %key)): - %lua::append (Lua "\n\(compile as (===next %key ===))") + %lua::append "\n " + %lua::append (what (===next %key ===) compiles to) + if (%body has subtree \(do next %value)): - %lua::append (Lua "\n\(compile as (===next %value ===))") + %lua::append "\n " + %lua::append (what (===next %value ===) compiles to) + %lua::append "\nend --foreach-loop" %stop_labels = (Lua "") if (%body has subtree \(stop %key)): - %stop_labels::append "\n\(compile as (===stop %key ===))" + %stop_labels::append "\n" + %stop_labels::append (what (===stop %key ===) compiles to) + if (%body has subtree \(stop %value)): - %stop_labels::append "\n\(compile as (===stop %value ===))" + %stop_labels::append "\n" + %stop_labels::append (what (===stop %value ===) compiles to) + if ((size of "\%stop_labels") > 0): - %lua = (..) - Lua "\ - ..do -- scope for stopping for % = % loop - \%lua\%stop_labels - end" + %inner_lua = %lua + %lua = (Lua "do -- scope for stopping for % = % loop\n ") + %lua::append %inner_lua + %lua::append %stop_labels + %lua::append "\nend" return %lua ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test: - if: + when: (1 == 2) (100 < 0): barf "bad conditional" (1 == 0) (1 == 1) %not_a_variable.x: do nothing @@ -326,13 +379,14 @@ test: barf "bad conditional" # Multi-branch conditional (if..elseif..else) -compile [if %body, when %body] to: +(when %body) compiles to: %code = (Lua "") %clause = "if" %else_allowed = (yes) unless (%body.type is "Block"): compile error at %body "'if' expected a Block, but got a \(%body.type)." ..hint "Perhaps you forgot to put a ':' after 'if'?" + for %line in %body: unless (..) ((%line.type is "Action") and ((size of %line) >= 2)) and (..) @@ -347,6 +401,7 @@ compile [if %body, when %body] to: unless %else_allowed: compile error at %line "You can't have two 'else' blocks." ..hint "Merge all of the 'else' blocks together." + unless ((size of "\%code") > 0): compile error at %line "\ ..You can't have an 'else' block without a preceeding condition" @@ -354,23 +409,19 @@ compile [if %body, when %body] to: ..need a conditional block around it. Otherwise, make sure the 'else' \ ..block comes last." - %code::append "\ - .. - else - \(%action as lua statements)" - + %code::append "\nelse\n " + %code::append (%action as lua statements) %else_allowed = (no) ..else: - %code::append "\%clause " + %code::append %clause + %code::append " " for %i in 1 to ((size of %line) - 1): if (%i > 1): %code::append " or " %code::append (%line.%i as lua expr) - %code::append "\ - .. then - \(%action as lua statements)" - + %code::append " then\n " + %code::append (%action as lua statements) %clause = "\nelseif" if ((size of "\%code") == 0): @@ -390,7 +441,7 @@ test: barf "bad switch statement" # Switch statement -compile [if %branch_value is %body, when %branch_value is %body] to: +[if %branch_value is %body, when %branch_value is %body] all compile to: %code = (Lua "") %clause = "if" %else_allowed = (yes) @@ -419,42 +470,42 @@ compile [if %branch_value is %body, when %branch_value is %body] to: ..need a conditional block around it. Otherwise, make sure the 'else' \ ..block comes last." - %code::append "\ - .. - else - \(%action as lua statements)" - + %code::append "\nelse\n " + %code::append (%action as lua statements) %else_allowed = (no) ..else: - %code::append "\%clause " + %code::append %clause + %code::append " " for %i in 1 to ((size of %line) - 1): if (%i > 1): %code::append " or " - %code::append "\(mangle "branch value") == \(%line.%i as lua expr)" - - %code::append "\ - .. then - \(%action as lua statements)" + %code::append "\(mangle "branch value") == " + %code::append (%line.%i as lua expr) + %code::append " then\n " + %code::append (%action as lua statements) %clause = "\nelseif" if ((size of "\%code") == 0): compile error at %body "'if' block has an empty body." ..hint "This means nothing would happen, so the 'if' block should be deleted." %code::append "\nend --when" - return (..) + %lua = (..) Lua "\ - ..do --if % is - local \(mangle "branch value") = \(%branch_value as lua expr) - \%code - end --if % is" + ..do --if % is... + local \(mangle "branch value") = " + %lua::append (%branch_value as lua expr) + %lua::append "\n " + %lua::append %code + %lua::append "\nend --if % is..." + return %lua # Do/finally -compile [do %action] to (..) - Lua "\ - ..do - \(%action as lua statements) - end --do" +(do %action) compiles to: + %lua = (Lua "do\n ") + %lua::append (%action as lua statements) + %lua::append "\nend -- do" + return %lua test: %d = {} @@ -463,60 +514,63 @@ test: %d.x = "bad" barf ..then always: %d.x = "good" - ..and if it barfs: do nothing - assume (%d.x == "good") -compile [do %action then always %final_action] to: +(do %action then always %final_action) compiles to: define mangler - return (..) + %lua = (..) Lua "\ ..do local \(mangle "fell_through") = false - local \(mangle "ok"), \(mangle "ret") = pcall(function() - \(%action as lua statements) - \(mangle "fell_through") = true - end) - \(%final_action as lua statements) - if not \(mangle "ok") then error(ret, 0) end - if not \(mangle "fell_through") then return ret end - end" + local \(mangle "ok"), \(mangle "ret") = pcall(function()" + %lua::append "\n " + %lua::append (%action as lua statements) + %lua::append "\ + .. \(mangle "fell_through") = true + end)" + %lua::append "\n " + %lua::append (%final_action as lua statements) + %lua::append "\ + .. if not \(mangle "ok") then error(ret, 0) end + if not \(mangle "fell_through") then return ret end + end" + return %lua test: assume ((result of (: return 99)) == 99) # Inline thunk: -compile [result of %body] to (Lua value "\(compile as ([] -> %body))()") +(result of %body) compiles to (Lua value "\(what ([] -> %body) compiles to)()") test: %t = [1, [2, [[3], 4], 5, [[[6]]]]] %flat = [] for % in recursive %t: - if ((type of %) is "table"): + if ((lua type of %) is "table"): for %2 in %: recurse % on %2 ..else: %flat::add % - assume ((sorted %flat) == [1, 2, 3, 4, 5, 6]) + assume (sorted %flat) == [1, 2, 3, 4, 5, 6] # Recurion control flow -compile [for %var in recursive %structure %body] to (..) +(for %var in recursive %structure %body) compiles to: with local compile actions: define mangler - compile [recurse %v on %x] to (..) + (recurse %v on %x) compiles to (..) Lua "table.insert(\(mangle "stack \(%v.1)"), \(%x as lua expr))" %lua = (..) Lua "\ ..do 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) - \(%body as lua statements)" - + \(%var as lua expr) = table.remove(\(mangle "stack \(%var.1)"), 1)" + %lua::append "\n " + %lua::append (%body as lua statements) if (%body has subtree \(do next)): %lua::append "\n ::continue::" if (%body has subtree \(do next %var)): - %lua::append "\n \(compile as (===next %var ===))" + %lua::append "\n \(what (===next %var ===) compiles to)" %lua::append "\n end -- Recursive loop" if (%body has subtree \(stop %var)): - %lua::append "\n \(compile as (===stop %var ===))" + %lua::append "\n \(what (===stop %var ===) compiles to)" %lua::append "\nend -- Recursive scope" return %lua diff --git a/core/coroutines.nom b/core/coroutines.nom index e7ec41e..246a2ef 100644 --- a/core/coroutines.nom +++ b/core/coroutines.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines the code that creates and manipulates coroutines @@ -14,15 +14,15 @@ test: for % in coroutine %co: %nums::add % assume (%nums == [4, 5, 6, 6, 6]) or barf "Coroutine iteration failed" -compile [coroutine %body, generator %body] to (..) +[coroutine %body, generator %body] all compile to (..) Lua value "\ ..(function() \(%body as lua statements) end)" -compile [->] to (Lua value "coroutine.yield(true)") -compile [-> %] to (Lua value "coroutine.yield(true, \(% as lua expr))") -compile [for % in coroutine %co %body] to (..) +(->) compiles to (Lua value "coroutine.yield(true)") +(-> %) compiles to (Lua value "coroutine.yield(true, \(% as lua expr))") +(for % in coroutine %co %body) compiles to (..) Lua "\ ..for junk,\(% as lua expr) in coroutine.wrap(\(%co as lua expr)) do \(%body as lua statements) diff --git a/core/errors.nom b/core/errors.nom index 1157cb1..0b0a6a3 100644 --- a/core/errors.nom +++ b/core/errors.nom @@ -1,17 +1,16 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file contains basic error reporting code use "core/metaprogramming.nom" -compile [barf] to (Lua "error(nil, 0);") -compile [barf %msg] to (Lua "error(\(%msg as lua expr), 0);") -compile [compile error at %tree %msg] to (..) +(barf %msg) compiles to (Lua "error(\(=lua "\%msg and \(%msg as lua expr) or 'nil'"), 0);") +(compile error at %tree %msg) compiles to (..) Lua "nomsu:compile_error(\(%tree as lua expr), \(%msg as lua expr))" -compile [compile error at %tree %msg hint %hint] to (..) +(compile error at %tree %msg hint %hint) compiles to (..) Lua "nomsu:compile_error(\(%tree as lua expr), \(%msg as lua expr), \(%hint as lua expr))" -compile [assume %condition] to: +(assume %condition) compiles to: lua> "\ ..local \%assumption = 'Assumption failed: '..tostring(nomsu:tree_to_nomsu(\%condition))" return (..) @@ -20,7 +19,7 @@ compile [assume %condition] to: error(\(quote "\%assumption"), 0) end" -compile [assume %a == %b] to: +(assume %a == %b) compiles to: lua> "\ ..local \%assumption = 'Assumption failed: '..tostring(nomsu:tree_to_nomsu(\(\(%a == %b))))" define mangler @@ -35,7 +34,7 @@ compile [assume %a == %b] to: end end" -compile [assume %condition or barf %message] to (..) +(assume %condition or barf %message) compiles to (..) Lua "\ ..if not \(%condition as lua expr) then error(\(%message as lua expr), 0) @@ -55,10 +54,10 @@ test: assume (%x == 3) or barf "do/then always failed" # Try/except -compile [..] +[..] try %action and if it succeeds %success or if it barfs %msg %fallback try %action and if it barfs %msg %fallback or if it succeeds %success -..to (..) +..all compile to (..) Lua "\ ..do local fell_through = false @@ -66,9 +65,9 @@ compile [..] local ok, ret = xpcall(function() \(%action as lua statements) fell_through = true - end, function(\(%msg as lua expr)) + end, function(\(=lua "\%fallback and \(%msg as lua expr) or ''")) local ok, ret = pcall(function() - \(%fallback as lua statements) + \((=lua "\%fallback or \%msg") as lua statements) end) if not ok then err, erred = ret, true end end) @@ -84,21 +83,23 @@ compile [..] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -parse [..] - try %action and if it succeeds %success or if it barfs %fallback - try %action and if it barfs %fallback or if it succeeds %success -..as (try %action and if it succeeds %success or if it barfs (=lua "") %fallback) +# + [..] + try %action and if it succeeds %success or if it barfs %fallback + try %action and if it barfs %fallback or if it succeeds %success + ..all parse as (..) + try %action and if it succeeds %success or if it barfs (=lua "") %fallback -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -parse [try %action] as (..) +(try %action) parses 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 barfs %fallback) parses as (..) try %action and if it succeeds (do nothing) or if it barfs %fallback -parse [try %action and if it barfs %msg %fallback] as (..) +(try %action and if it barfs %msg %fallback) parses as (..) try %action and if it succeeds (do nothing) or if it barfs %msg %fallback -parse [try %action and if it succeeds %success] as (..) +(try %action and if it succeeds %success) parses as (..) try %action and if it succeeds %success or if it barfs (do nothing) diff --git a/core/id.nom b/core/id.nom index d3b2b71..a9231b9 100644 --- a/core/id.nom +++ b/core/id.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # A simple UUID function based on RFC 4122: http://www.ietf.org/rfc/rfc4122.txt @@ -27,7 +27,7 @@ set %id_by_obj 's metatable to {..} %obj_by_id.%id = %key return %id -action [uuid]: +externally (uuid) means: # Set all the other bits to randomly (or pseudo-randomly) chosen values. %bytes = [..] # time-low, time-mid, time-high-and-version @@ -56,4 +56,4 @@ test: seed random with 0 assume ((id of %x) != (id of [])) seed random -action [id of %, %'s id, %' id] %id_by_obj.% +externally [id of %, %'s id, %'id] all mean %id_by_obj.% diff --git a/core/io.nom b/core/io.nom index 1e87209..6b67b50 100644 --- a/core/io.nom +++ b/core/io.nom @@ -1,10 +1,10 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file contains basic input/output code use "core/metaprogramming.nom" -compile [say %message] to (..) +(say %message) compiles to (..) lua> "\ ..if \%message.type == "Text" then return LuaCode(tree.source, "print(", \(%message as lua expr), ");"); @@ -12,7 +12,7 @@ compile [say %message] to (..) return LuaCode(tree.source, "print(tostring(", \(%message as lua expr), "));"); end" -compile [ask %prompt] to (..) +(ask %prompt) compiles to (..) lua> "\ ..if \%prompt.type == "Text" then return LuaCode.Value(tree.source, "(io.write(", \(%prompt as lua expr), ") and io.read())"); diff --git a/core/math.nom b/core/math.nom index d6ddbce..66f5aba 100644 --- a/core/math.nom +++ b/core/math.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines some common math literals and functions @@ -14,17 +14,18 @@ test: ..math constants failed" %nan = (NaN) assume (%nan != %nan) or barf "NaN failed" -compile [infinity, inf] to (Lua value "math.huge") -compile [not a number, NaN, nan] to (Lua value "(0/0)") -compile [pi, Pi, PI] to (Lua value "math.pi") -compile [tau, Tau, TAU] to (Lua value "(2*math.pi)") -compile [golden ratio] to (Lua value "((1+math.sqrt(5))/2)") -compile [e] to (Lua value "math.exp(1)") +[infinity, inf] all compile to (Lua value "math.huge") +[not a number, NaN, nan] all compile to (Lua value "(0/0)") +[pi, Pi, PI] all compile to (Lua value "math.pi") +[tau, Tau, TAU] all compile to (Lua value "(2*math.pi)") +(golden ratio) compiles to (Lua value "((1+math.sqrt(5))/2)") +(e) compiles to (Lua value "math.exp(1)") # Functions: test: assume (("5" as a number) == 5) -compile [% as a number, % as number] to (Lua value "tonumber(\(% as lua expr))") +[% as a number, % as number] all compile to (..) + Lua value "tonumber(\(% as lua expr))" test: assume (..) @@ -33,79 +34,127 @@ test: arc tangent 5, arc tangent 5 / 10, hyperbolic sine 5, hyperbolic cosine 5 hyperbolic tangent 5, e^ 5, ln 5, log base 2 of 5, floor 5, ceiling 5, round 5 ..or barf "math functions failed" -compile [absolute value %, | % |, abs %] to (..) +[absolute value %, | % |, abs %] all compile to (..) Lua value "math.abs(\(% as lua expr))" -compile [square root %, square root of %, √ %, sqrt %] to (..) +[square root %, square root of %, √ %, sqrt %] all compile to (..) Lua value "math.sqrt(\(% as lua expr))" -compile [sine %, sin %] to (Lua value "math.sin(\(% as lua expr))") -compile [cosine %, cos %] to (Lua value "math.cos(\(% as lua expr))") -compile [tangent %, tan %] to (Lua value "math.tan(\(% as lua expr))") -compile [arc sine %, asin %] to (Lua value "math.asin(\(% as lua expr))") -compile [arc cosine %, acos %] to (Lua value "math.acos(\(% as lua expr))") -compile [arc tangent %, atan %] to (Lua value "math.atan(\(% as lua expr))") -compile [arc tangent %y / %x, atan2 %y %x] to (..) +[sine %, sin %] all compile to (Lua value "math.sin(\(% as lua expr))") +[cosine %, cos %] all compile to (Lua value "math.cos(\(% as lua expr))") +[tangent %, tan %] all compile to (Lua value "math.tan(\(% as lua expr))") +[arc sine %, asin %] all compile to (Lua value "math.asin(\(% as lua expr))") +[arc cosine %, acos %] all compile to (Lua value "math.acos(\(% as lua expr))") +[arc tangent %, atan %] all compile to (Lua value "math.atan(\(% as lua expr))") +[arc tangent %y / %x, atan2 %y %x] all compile to (..) Lua value "math.atan2(\(%y as lua expr), \(%x as lua expr))" -compile [hyperbolic sine %, sinh %] to (Lua value "math.sinh(\(% as lua expr))") -compile [hyperbolic cosine %, cosh %] to (Lua value "math.cosh(\(% as lua expr))") -compile [hyperbolic tangent %, tanh %] to (..) +[hyperbolic sine %, sinh %] all compile to (..) + Lua value "math.sinh(\(% as lua expr))" + +[hyperbolic cosine %, cosh %] all compile to (..) + Lua value "math.cosh(\(% as lua expr))" + +[hyperbolic tangent %, tanh %] all compile to (..) Lua value "math.tanh(\(% as lua expr))" -compile [e^ %, exp %] to (Lua value "math.exp(\(% as lua expr))") -compile [natural log %, ln %, log %] to (Lua value "math.log(\(% as lua expr))") -compile [log % base %base, log base %base of %] to (..) +[e^ %, exp %] all compile to (Lua value "math.exp(\(% as lua expr))") +[natural log %, ln %, log %] all compile to (..) + Lua value "math.log(\(% as lua expr))" + +[log % base %base, log base %base of %] all compile to (..) Lua value "math.log(\(% as lua expr), \(%base as lua expr))" -compile [floor %] to (Lua value "math.floor(\(% as lua expr))") -compile [ceiling %, ceil %] to (Lua value "math.ceil(\(% as lua expr))") -compile [round %, % rounded] to (Lua value "math.floor(\(% as lua expr) + .5)") +(floor %) compiles to (Lua value "math.floor(\(% as lua expr))") +[ceiling %, ceil %] all compile to (Lua value "math.ceil(\(% as lua expr))") +[round %, % rounded] all compile to (..) + Lua value "math.floor(\(% as lua expr) + .5)" test: 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" -action [%n to the nearest %rounder] (..) +externally (%n to the nearest %rounder) means (..) =lua "(\%rounder)*math.floor((\%n / \%rounder) + .5)" # Any/all/none -compile [all of %items, all %items] to: +[all of %items, all %items] all compile to: unless (%items.type is "List"): return (Lua value "utils.all(\(%items as lua expr))") - %clauses = ((% as lua expr) for % in %items) + %clauses = (((% as lua expr)::text) for % in %items) return (Lua value "(\(%clauses::joined with " and "))") -parse [not all of %items, not all %items] as (not (all of %items)) -compile [any of %items, any %items] to: +[not all of %items, not all %items] all parse as (not (all of %items)) +[any of %items, any %items] all compile to: unless (%items.type is "List"): return (Lua value "utils.any(\(%items as lua expr))") - %clauses = ((% as lua expr) for % in %items) + %clauses = (((% as lua expr)::text) for % in %items) return (Lua value "(\(%clauses::joined with " or "))") -parse [none of %items, none %items] as (not (any of %items)) -compile [sum of %items, sum %items] to: +[none of %items, none %items] all parse as (not (any of %items)) +[sum of %items, sum %items] all compile to: unless (%items.type is "List"): return (Lua value "utils.sum(\(%items as lua expr))") - %clauses = ((% as lua expr) for % in %items) + %clauses = (((% as lua expr)::text) for % in %items) return (Lua value "(\(%clauses::joined with " + "))") -compile [product of %items, product %items] to: +[if all of %items %body, if all of %items then %body] all parse as (..) + if (all of %items) %body + +[unless all of %items %body, unless all of %items then %body] all parse as (..) + if (not (all of %items)) %body + +[if any of %items %body, if any of %items then %body] all parse as (..) + if (any of %items) %body + +[unless any of %items %body, unless any of %items then %body] all parse as (..) + if (not (any of %items)) %body + +[if none of %items %body, if none of %items then %body] all parse as (..) + if (not (any of %items)) %body + +[unless none of %items %body, unless none of %items then %body] all parse as (..) + if (any of %items) %body + +[if all of %items %body else %else, if all of %items then %body else %else] all parse \ +..as (if (all of %items) %body else %else) + +[..] + unless all of %items %body else %else, unless all of %items then %body else %else +..all parse as (if (not (all of %items)) %body else %else) + +[if any of %items %body else %else, if any of %items then %body else %else] all parse \ +..as (if (any of %items) %body else %else) + +[..] + unless any of %items %body else %else, unless any of %items then %body else %else +..all parse as (if (not (any of %items)) %body else %else) + +[if none of %items %body else %else, if none of %items then %body else %else] all \ +..parse as (if (not (any of %items)) %body else %else) + +[..] + 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) for % in %items) + %clauses = (((% as lua expr)::text) for % in %items) return (Lua value "(\(%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 (..) +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))" -compile [max of %items, biggest of %items, largest of %items, highest of %items] to (..) - Lua value "utils.max(\(%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))") test: assume ((min of [3, -4, 1, 2] by % = (% * %)) == 1) assume ((max of [3, -4, 1, 2] by % = (% * %)) == -4) -parse [min of %items by %item = %value_expr] as (..) +(min of %items by %item = %value_expr) parses as (..) result of: set {%best:nil, %best_key:nil} for %item in %items: @@ -115,7 +164,7 @@ parse [min of %items by %item = %value_expr] as (..) return %best -parse [max of %items by %item = %value_expr] as (..) +(max of %items by %item = %value_expr) parses as (..) result of: set {%best:nil, %best_key:nil} for %item in %items: @@ -126,20 +175,19 @@ parse [max of %items by %item = %value_expr] as (..) return %best # Random functions -action [seed random with %] (..) +externally (seed random with %) means (..) 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 (Lua value "math.random()") -compile [random int %n, random integer %n, randint %n] to (..) +(seed random) parses as (seed random with (=lua "os.time()")) +[random number, random, rand] all compile to (Lua value "math.random()") +[random int %n, random integer %n, randint %n] all compile to (..) Lua value "math.random(\(%n as lua expr))" -compile [..] - random from %low to %high, random number from %low to %high - rand %low %high -..to (Lua value "math.random(\(%low as lua expr), \(%high as lua expr))") +[random from %low to %high, random number from %low to %high, rand %low %high] all \ +..compile to (Lua value "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)]" +externally [..] + random choice from %elements, random choice %elements, random %elements +..all mean (=lua "\%elements[math.random(#\%elements)]") diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom index c50f783..61c877f 100644 --- a/core/metaprogramming.nom +++ b/core/metaprogramming.nom @@ -1,9 +1,9 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This File contains actions for making actions and compile-time actions and some helper functions to make that easier. -lua> "NOMSU_CORE_VERSION = 8" +lua> "NOMSU_CORE_VERSION = 9" lua> "\ ..do local mangle_index = 0 @@ -16,16 +16,16 @@ lua> "\ end end COMPILE_ACTIONS["define mangler"] = function(nomsu, tree) - return LuaCode(tree.source, "local mangle_1 = mangler()") + return LuaCode(tree.source, "local mangle = mangler()") end" lua> "\ - ..COMPILE_ACTIONS["1 -> 2"] = function(nomsu, tree, \%args, \%body) + ..COMPILE_ACTIONS["1 ->"] = function(nomsu, tree, \%args, \%body) 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) + if SyntaxTree:is_instance(\%args) and \%args.type == "Action" then \%args = \%args:get_args() end + local lua_args = table.map(\%args, function(a) return SyntaxTree:is_instance(a) and nomsu:compile(a):text() or a end) lua:concat_append(lua_args, ", ") - local body_lua = AST.is_syntax_tree(\%body) and nomsu:compile(\%body):as_statements("return ") or \%body + local body_lua = SyntaxTree:is_instance(\%body) and nomsu:compile(\%body):as_statements("return ") or \%body body_lua:remove_free_vars(lua_args) body_lua:declare_locals() lua:append(")\\n ", body_lua, "\\nend)") @@ -33,8 +33,8 @@ lua> "\ end" lua> "\ - ..COMPILE_ACTIONS["compile as 1"] = function(nomsu, tree, \%action) - local lua = LuaCode.Value(tree.source, "COMPILE_ACTIONS[", repr(\%action.stub), "](") + ..COMPILE_ACTIONS["what 1 compiles to"] = function(nomsu, tree, \%action) + local lua = LuaCode.Value(tree.source, "COMPILE_ACTIONS[", \%action.stub:as_lua(), "](") local lua_args = table.map(\%action:get_args(), function(a) return nomsu:compile(a) end) table.insert(lua_args, 1, "nomsu") table.insert(lua_args, 2, "tree") @@ -46,78 +46,84 @@ lua> "\ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ test: - compile [five] to (Lua value "5") + (five) compiles to (Lua value "5") test: assume ((five) == 5) or barf "Compile to expression failed." - compile [loc x] to (Lua "local x = 99;") + (loc x) compiles to (Lua "local x = 99;") test: lua> "do" loc x assume (%x is 99) or barf "Compile to statements with locals failed." lua> "end" assume (%x is (nil)) or barf "Failed to properly localize a variable." - compile [asdf] to: + (asdf) compiles to: %tmp = "" return (Lua %tmp) test: asdf assume (%tmp is (nil)) or barf "compile to is leaking variables" lua> "\ - ..COMPILE_ACTIONS["compile 1 to 2"] = function(nomsu, tree, \%actions, \%body) - local \%args = {"nomsu", "tree", unpack(table.map(\%actions[1]:get_args(), function(a) return tostring(nomsu:compile(\ - ..a)) end))} - local lua = LuaCode(tree.source, "COMPILE_ACTIONS[", repr(\%actions[1].stub), - "] = ", \(compile as (%args -> %body))) + ..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(), + "] = ", \(what (%args -> %body) compiles to)) for i=2,#\%actions do local alias = \%actions[i] - local \%alias_args = {"nomsu", "tree", unpack(table.map(alias:get_args(), function(a) return tostring(nomsu:compile(\ - ..a)) end))} - lua:append("\\nCOMPILE_ACTIONS[", repr(alias.stub), "] = ") + local \%alias_args = {"nomsu", "tree", unpack(table.map(alias:get_args(), function(a) return nomsu:compile(a):text() \ + ..end))} + lua:append("\\nCOMPILE_ACTIONS[", alias.stub:as_lua(), "] = ") if utils.equivalent(\%args, \%alias_args) then - lua:append("COMPILE_ACTIONS[", repr(\%actions[1].stub), "]") + lua:append("COMPILE_ACTIONS[", \%actions[1].stub:as_lua(), "]") else lua:append("function(") lua:concat_append(\%alias_args, ", ") - lua:append(")\\n return COMPILE_ACTIONS[", repr(\%actions[1].stub), "](") + lua:append(")\\n return COMPILE_ACTIONS[", \%actions[1].stub:as_lua(), "](") lua:concat_append(\%args, ", ") lua:append(")\\nend") end end return lua - end" + end + COMPILE_ACTIONS["1 all compile to"] = COMPILE_ACTIONS["1 compiles to"]" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -compile [call %fn with %args] to: +(call %fn with %args) compiles to: lua> "\ ..local lua = LuaCode.Value(tree.source, nomsu:compile(\%fn), "(") - lua:concat_append(table.map(\%args, function(a) return nomsu:compile(a) end), ", ") + if \%args.type == 'List' then + lua:concat_append(table.map(\%args, function(a) return nomsu:compile(a) end), ", ") + else + lua:append('unpack(', nomsu:compile(\%args), ')') + end lua:append(")") return lua" test: - local action [foo %x]: return "outer" - with local [action (foo %)]: - local action [foo %x]: + (foo %x) means (return "outer") + with local [(foo %)'s meaning]: + (foo %x) means: %y = (%x + 1) return %y assume ((foo 10) == 11) or barf "Action didn't work." assume (%y is (nil)) or barf "Action leaked a local into globals." - parse [baz %] as (foo %) + (baz %) parses as (foo %) assume ((foo 1) == "outer") -compile [local action %actions %body] to: +[%actions means %body, %actions all mean %body] all compile 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) - local lua = LuaCode(tree.source, fn_name, " = ", \(compile as (%args -> %body))) + ..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 lua = LuaCode(tree.source, fn_name, " = ", \(what (%args -> %body) compiles to)) lua:add_free_vars({fn_name}) 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 tostring(nomsu:compile(a)) end) + local \%alias_args = table.map(alias:get_args(), function(a) return nomsu:compile(a):text() end) lua:append("\\n", alias_name, " = ") if utils.equivalent(\%args, \%alias_args) then lua:append(fn_name) @@ -132,23 +138,24 @@ compile [local action %actions %body] to: return lua" test: - action [baz1]: return "baz1" - action [baz2] "baz2" + externally (baz1) means: return "baz1" + externally (baz2) means "baz2" test: assume ((baz1) == "baz1") assume ((baz2) == "baz2") -compile [action %actions %body] to (..) +[externally %actions means %body, externally %actions all mean %body] all compile to: lua> "\ - ..local lua = \(compile as (local action %actions %body)) + ..local lua = \(what (%actions means %body) compiles to) + if \%actions.type ~= "List" then \%actions = {\%actions, type="List"} end lua:remove_free_vars(table.map(\%actions, function(a) return a.stub:as_lua_id() end)) return lua" test: - assume ((action (say %)) == (=lua "say_1")) -compile [action %action] to (Lua value (%action as lua id)) + assume (((say %)'s meaning) == (=lua "say")) +(%action's meaning) compiles to (Lua value (%action.stub as lua id)) test: - parse [swap %x and %y] as (..) + (swap %x and %y) parses as (..) do: %tmp = %x %x = %y @@ -162,20 +169,21 @@ test: swap %tmp and %tmp2 assume ((%tmp == 2) and (%tmp2 == 1)) or barf "\ ..'parse % as %' variable mangling failed." -compile [parse %actions as %body] to (..) +[%actions parses as %body, %actions all parse as %body] all compile to: lua> "\ ..local replacements = {} + if \%actions.type ~= "List" then \%actions = {\%actions, type="List"} end for i,arg in ipairs(\%actions[1]:get_args()) do - replacements[arg[1]] = tostring(nomsu:compile(arg)) + replacements[arg[1]] = nomsu:compile(arg):text() end local function make_tree(t) - if AST.is_syntax_tree(t, "Var") then + if SyntaxTree:is_instance(t) and t.type == "Var" then if replacements[t[1]] then return replacements[t[1]] else - return t.type.."{mangle("..repr(t[1]).."), source="..repr(tostring(t.source)).."}" + return "SyntaxTree{mangle("..t[1]:as_lua().."), type="..t.type:as_lua()..", source="..tostring(t.source):as_lua().."}" end - elseif AST.is_syntax_tree(t) then + elseif SyntaxTree:is_instance(t) then local ret = {} local i = 1 for k, v in pairs(t) do @@ -183,71 +191,76 @@ compile [parse %actions as %body] to (..) ret[#ret+1] = make_tree(t[i]) i = i + 1 elseif k == "source" then - ret[#ret+1] = k.."= "..repr(tostring(v)) - elseif type(k) == 'string' and k:match("[_a-zA-Z][_a-zA-Z0-9]*") then + ret[#ret+1] = k.."= "..tostring(v):as_lua() + elseif lua_type_of(k) == 'string' and k:match("[_a-zA-Z][_a-zA-Z0-9]*") then ret[#ret+1] = k.."= "..make_tree(v) else ret[#ret+1] = "["..make_tree(k).."]= "..make_tree(v) end end - return t.type.."{"..table.concat(ret, ", ").."}" + return "SyntaxTree{"..table.concat(ret, ", ").."}" + elseif lua_type_of(t) == 'number' then + return tostring(t) else - return repr(t) + return t:as_lua() end end local \%new_body = LuaCode(\%body.source, "local mangle = mangler()", "\\nreturn ", make_tree(\%body)) - local ret = \(compile as (compile %actions to %new_body)) + local ret = \(what (%actions compiles to %new_body) compiles to) return ret" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TODO: add check for .is_value -compile [%tree as lua expr] to (Lua value "nomsu:compile(\(=lua "nomsu:compile(\%tree, nil, true)"), nil, true)") +(%tree as lua expr) compiles to (..) + Lua value "nomsu:compile(\(=lua "nomsu:compile(\%tree, nil, true)"), nil, true)" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -compile [%tree as lua] to (Lua value "nomsu:compile(\(%tree as lua expr))") -compile [%tree as lua statements] to (..) +(%tree as lua) compiles to (Lua value "nomsu:compile(\(%tree as lua expr))") +(%tree as lua statements) compiles to (..) Lua value "nomsu:compile(\(%tree as lua expr)):as_statements()" -compile [%tree as lua return] to (..) +(%tree as lua return) compiles to (..) Lua value "nomsu:compile(\(%tree as lua expr)):as_statements('return ')" -compile [remove action %action] to (..) - Lua "\(=lua "(\(%action.stub)):as_lua_id()") = nil" - test: assume ("\(\(foo \%x) as nomsu)" == "foo %x") or barf "\ ..action source code failed." -compile [%tree as nomsu] to (..) +(%tree as nomsu) compiles to (..) Lua value "nomsu:tree_to_nomsu(\(%tree as lua expr))" -compile [%tree as inline nomsu] to (..) - Lua value "nomsu:tree_to_nomsu(\(%tree as lua expr), true)" +(%tree as inline nomsu) compiles to (..) + Lua value "nomsu:tree_to_inline_nomsu(\(%tree as lua expr), true)" -action [%var as lua identifier, %var as lua id] (..) +externally [%var as lua identifier, %var as lua id] all mean: lua> "\ - ..if type(\%var) == 'string' then return \%var:as_lua_id() - elseif AST.is_syntax_tree(\%var, 'Var') then return \%var[1]:as_lua_id() - elseif AST.is_syntax_tree(\%var, 'Action') then return \%var.stub:as_lua_id() + ..if lua_type_of(\%var) == 'string' then return \%var:as_lua_id() + elseif SyntaxTree:is_instance(\%var, 'Var') then return \%var[1]:as_lua_id() + elseif SyntaxTree:is_instance(\%var) then + local lua = \(%var as lua expr) + if not lua:text():match("^[_a-zA-Z][_a-zA-Z0-9]*$") then + nomsu:compile_error(\%var, "This is not a valid Lua identifier.") + end + return lua else error("Unknown type: "..tostring(\%var)) end" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -compile [% is syntax tree] to (Lua value "AST.is_syntax_tree(\(% as lua expr))") -compile [% is %kind syntax tree] to (..) - Lua value "AST.is_syntax_tree(\(% as lua expr), \(%kind as lua expr))" +(% is syntax tree) compiles to (Lua value "SyntaxTree:is_instance(\(% as lua expr))") +(% is %kind syntax tree) compiles to (..) + Lua value "SyntaxTree:is_instance(\(% as lua expr), \(%kind as lua expr))" -compile [%tree with %t -> %replacement] to (..) +(%tree with %t -> %replacement) compiles to (..) Lua value "\ ..\(%tree as lua expr):map(function(\(%t as lua expr)) \(%replacement as lua return) end)" -action [%tree with vars %replacements] (..) +externally (%tree with vars %replacements) means (..) =lua "\ ..\%tree:map(function(\%t) if \%t.type == "Var" then @@ -255,15 +268,15 @@ action [%tree with vars %replacements] (..) end end)" -compile [tree %tree with vars %replacements] to (..) +(tree %tree with vars %replacements) compiles to (..) Lua value "\ - ..\(=lua "repr(\%tree)"):map(function(t) + ..\(=lua "(\%tree):as_lua()"):map(function(t) if t.type == "Var" then return \(%replacements as lua expr)[t[1]] end end)" -compile [%tree has subtree %match_tree] to (..) +(%tree has subtree %match_tree) compiles to (..) Lua value "\ ..(function() local match_tree = \(%match_tree as lua expr) @@ -272,14 +285,14 @@ compile [%tree has subtree %match_tree] to (..) end end)()" -action [match %tree with %patt]: +externally (match %tree with %patt) means: lua> "\ ..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{} for \%i=1,#\%patt do - if AST.is_syntax_tree(\%tree[\%i]) then + if SyntaxTree:is_instance(\%tree[\%i]) then local submatch = \(match %tree.%i with %patt.%i) if not submatch then return nil end for k,v in pairs(submatch) do @@ -290,7 +303,7 @@ action [match %tree with %patt]: end return matches" -action [%tree with %patt ~> %replacement]: +externally (%tree with %patt ~> %replacement) means: lua> "\ ..return \%tree:map(function(\%t) local \%vars = \(match %t with %patt) @@ -312,11 +325,31 @@ test: ..one "two"" ..== "\"one\\n\\\"two\\\"\"" -compile [quote %s] to (Lua value "repr(\(%s as lua expr))") +(quote %s) compiles to (Lua value "tostring(\(%s as lua expr)):as_lua()") test: - assume ((type of {}) == "table") or barf "type of failed." -compile [type of %obj] to (Lua value "type(\(%obj as lua expr))") + assume (lua type of {}) == "table" + assume (type of {}) == "Dict" + assume ({} is a "Dict") + assume ("" is text) + assume ("" isn't a "Dict") +externally (% is text) means (=lua "\(lua type of %) == 'string'") +externally [% is not text, % isn't text] all mean (..) + =lua "\(lua type of %) ~= 'string'" + +externally (type of %) means: + lua> "\ + ..local lua_type = \(lua type of %) + if lua_type == 'string' then return 'Text' + elseif lua_type == 'table' then + local mt = getmetatable(\%) + if mt and mt.__type then return mt.__type end + return 'Lua table' + else return lua_type end" + +[% 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 (..) + (type of %) != %type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -325,8 +358,8 @@ test: %a = (parse "\\1") %b = \(\(1)) assume ((parse "\\1") == \(\(1))) -compile [parse %text] to (Lua value "nomsu:parse(\(%text as lua expr))") -compile [parse %text from %filename] to (..) +(parse %text) compiles to (Lua value "nomsu:parse(\(%text as lua expr))") +(parse %text from %filename) compiles to (..) Lua value "\ ..nomsu:parse(NomsuCode(Source(\(%filename as lua expr), 1, #\(%text as lua expr)), \(..) %text as lua expr @@ -337,37 +370,39 @@ test: external %passed = (no) run "external %passed = (yes)" assume %passed -compile [run %nomsu_code] to (..) +(run %nomsu_code) compiles to (..) Lua value "\ - ..nomsu:run(NomsuCode(\(..) - =lua "repr(tostring(\(%nomsu_code.source)))" - .., \(%nomsu_code as lua expr)))" + ..nomsu:run(NomsuCode(\(=lua "tostring(\(%nomsu_code.source)):as_lua()"), \(..) + %nomsu_code as lua expr + ..))" test: assume ((\(\(5) + \(5)) as value) == 10) or barf "%tree as value failed." -compile [run tree %tree, %tree as value] to (Lua value "nomsu:run(\(%tree as lua expr))") -compile [compile %block, compiled %block, %block compiled] to (..) +[run tree %tree, %tree as value] all compile to (..) + Lua value "nomsu:run(\(%tree as lua expr))" + +[compile %block, compiled %block, %block compiled] all compile to (..) Lua value "nomsu:compile(\(%block as lua))" # 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 (Lua "do return; end") -compile [return %return_value] to (..) +(return) compiles to (Lua "do return; end") +(return %return_value) compiles to (..) Lua "do return \(%return_value as lua expr) end" # Literals -compile [yes] to (Lua value "true") -compile [no] to (Lua value "false") -compile [nothing, nil, null] to (Lua value "nil") -compile [Nomsu syntax version] to (Lua value "NOMSU_SYNTAX_VERSION") -compile [Nomsu compiler version] to (Lua value "NOMSU_COMPILER_VERSION") -compile [core version] to (Lua value "NOMSU_CORE_VERSION") -compile [lib version] to (Lua value "NOMSU_LIB_VERSION") -compile [command line args] to (Lua value "arg") +(yes) compiles to (Lua value "true") +(no) compiles to (Lua value "false") +[nothing, nil, null] all compile to (Lua value "nil") +(Nomsu syntax version) compiles to (Lua value "NOMSU_SYNTAX_VERSION") +(Nomsu compiler version) compiles to (Lua value "NOMSU_COMPILER_VERSION") +(core version) compiles to (Lua value "NOMSU_CORE_VERSION") +(lib version) compiles to (Lua value "NOMSU_LIB_VERSION") +(command line args) compiles to (Lua value "arg") ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -compile [with local compile actions %body] to (..) +(with local compile actions %body) compiles to (..) Lua "\ ..do local nomsu = nomsu:fork() @@ -375,7 +410,7 @@ compile [with local compile actions %body] to (..) \(%body as lua statements) end" -action [Nomsu version]: +externally (Nomsu version) means: use "lib/version.nom" return "\ ..\(Nomsu syntax version).\(core version).\(Nomsu compiler version).\(lib version)" diff --git a/core/operators.nom b/core/operators.nom index 3ae7c2f..6d574b7 100644 --- a/core/operators.nom +++ b/core/operators.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file contains definitions of operators like "+" and "and". @@ -9,14 +9,14 @@ test: assume (all [1 < 2, 2 > 1, 1 <= 2, 2 >= 1, 1 == 1, 1 != 2]) # Comparison Operators -compile [%x < %y] to (Lua value "(\(%x as lua expr) < \(%y as lua expr))") -compile [%x > %y] to (Lua value "(\(%x as lua expr) > \(%y as lua expr))") -compile [%x <= %y] to (Lua value "(\(%x as lua expr) <= \(%y as lua expr))") -compile [%x >= %y] to (Lua value "(\(%x as lua expr) >= \(%y as lua expr))") -compile [%a is %b, %a == %b] to (..) +(%x < %y) compiles to (Lua value "(\(%x as lua expr) < \(%y as lua expr))") +(%x > %y) compiles to (Lua value "(\(%x as lua expr) > \(%y as lua expr))") +(%x <= %y) compiles to (Lua value "(\(%x as lua expr) <= \(%y as lua expr))") +(%x >= %y) compiles to (Lua value "(\(%x as lua expr) >= \(%y as lua expr))") +[%a is %b, %a == %b] all compile to (..) Lua value "(\(%a as lua expr) == \(%b as lua expr))" -compile [%a isn't %b, %a is not %b, %a not= %b, %a != %b] to (..) +[%a isn't %b, %a is not %b, %a not= %b, %a != %b] all compile to (..) Lua value "(\(%a as lua expr) ~= \(%b as lua expr))" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -26,7 +26,7 @@ test: assume (%x == 10) # Variable assignment operator -compile [%var = %value] to: +(%var = %value) compiles to: lua> "local \%var_lua = \(%var as lua expr)" assume %var_lua.is_value or barf "Invalid target for assignment: \%var" lua> "local \%value_lua = \(%value as lua expr)" @@ -34,7 +34,7 @@ compile [%var = %value] to: lua> "\ ..local lua = LuaCode(tree.source, \%var_lua, ' = ', \%value_lua, ';') if \%var.type == 'Var' then - lua:add_free_vars({tostring(nomsu:compile(\%var))}) + lua:add_free_vars({nomsu:compile(\%var):text()}) end return lua" @@ -45,7 +45,7 @@ test: assume ((%y == 10) and (%x == 20)) or barf "swapping vars failed." # Simultaneous mutli-assignments like: x,y,z = 1,x,3; -compile [set %assignments] to: +(set %assignments) compiles to: assume (%assignments.type is "Dict") or barf "\ ..Expected a Dict for the assignments part of '<- %' statement, not \%assignments" lua> "\ @@ -53,7 +53,7 @@ compile [set %assignments] to: for i, item in ipairs(\%assignments) do local \%target, \%value = item[1], item[2] \%value = \%value:map(function(t) - if Action:is_instance(t) and t.stub == "?" then + if SyntaxTree:is_instance(t) and t.type == "Action" and t.stub == "?" then return \%target end end) @@ -66,7 +66,7 @@ compile [set %assignments] to: %value as text ..) end if \%target.type == "Var" then - lhs:add_free_vars({tostring(target_lua)}) + lhs:add_free_vars({target_lua:text()}) end if i > 1 then lhs:append(", ") @@ -81,13 +81,13 @@ compile [set %assignments] to: test: set {%foozle:"outer", %y:"outer"} - action [set global x local y]: + externally (set global x local y) means: external %foozle = "inner" %y = "inner" set global x local y assume ((%foozle == "inner") and (%y == "outer")) or barf "external failed." -compile [external %var = %value] to: +(external %var = %value) compiles to: %var_lua = (%var as lua) assume %var_lua.is_value or barf "Invalid target for assignment: \%var" %value_lua = (%value as lua) @@ -96,7 +96,7 @@ compile [external %var = %value] to: test: set {%foozle:"outer", %y:"outer"} - action [set global x local y] (..) + externally (set global x local y) means (..) with external [%foozle]: %foozle = "inner" %y = "inner" @@ -104,10 +104,10 @@ test: set global x local y assume ((%foozle == "inner") and (%y == "outer")) or barf "\ ..'with external' failed." -compile [with external %externs %body] to: +(with external %externs %body) compiles to: %body_lua = (%body as lua statements) lua> "\ - ..\%body_lua:remove_free_vars(table.map(\%externs, function(v) return tostring(nomsu:compile(v)) end))" + ..\%body_lua:remove_free_vars(table.map(\%externs, function(v) return nomsu:compile(v):text() end))" return %body_lua test: @@ -119,7 +119,7 @@ test: assume (%x == 1) or barf "'with' scoping failed" assume (%z == (nil)) or barf "'with' scoping failed" -compile [with %assignments %body] to: +(with %assignments %body) compiles to: %lua = (%body as lua statements) lua> "\ ..local lhs, rhs = LuaCode(tree.source), LuaCode(tree.source) @@ -141,7 +141,7 @@ compile [with %assignments %body] to: lhs:append(target_lua) rhs:append(value_lua) if \%target.type == "Var" then - vars[i] = tostring(target_lua) + vars[i] = target_lua:text() end end \%lua:remove_free_vars(vars) @@ -156,53 +156,53 @@ compile [with %assignments %body] to: # Math Operators test: assume ((5 wrapped around 2) == 1) or barf "mod not working" -compile [%x wrapped around %y, %x mod %y] to (..) - Lua value "(\(%x as lua expr) % \(%y as lua expr))" +[%x wrapped around %y, %x mod %y] all compile to (..) + Lua value "((\(%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) test: %calls = 0 - local action [one]: + (one) means: external %calls = (%calls + 1) return 1 assume (0 <= (one) <= 2) or barf "Three-way chained comparison failed." assume (%calls == 1) or barf "\ ..Three-way comparison evaluated middle value multiple times" -parse [%x < %y < %z] as (..) +(%x < %y < %z) parses as (..) call ([%a, %b, %c] -> ((%a < %b) and (%b < %c))) with [%x, %y, %z] -parse [%x <= %y < %z] as (..) +(%x <= %y < %z) parses as (..) call ([%a, %b, %c] -> ((%a <= %b) and (%b < %c))) with [%x, %y, %z] -parse [%x < %y <= %z] as (..) +(%x < %y <= %z) parses as (..) call ([%a, %b, %c] -> ((%a < %b) and (%b <= %c))) with [%x, %y, %z] -parse [%x <= %y <= %z] as (..) +(%x <= %y <= %z) parses as (..) call ([%a, %b, %c] -> ((%a <= %b) and (%b <= %c))) with [%x, %y, %z] -parse [%x > %y > %z] as (..) +(%x > %y > %z) parses as (..) call ([%a, %b, %c] -> ((%a > %b) and (%b > %c))) with [%x, %y, %z] -parse [%x >= %y > %z] as (..) +(%x >= %y > %z) parses as (..) call ([%a, %b, %c] -> ((%a >= %b) and (%b > %c))) with [%x, %y, %z] -parse [%x > %y >= %z] as (..) +(%x > %y >= %z) parses as (..) call ([%a, %b, %c] -> ((%a > %b) and (%b >= %c))) with [%x, %y, %z] -parse [%x >= %y >= %z] as (..) +(%x >= %y >= %z) parses as (..) call ([%a, %b, %c] -> ((%a >= %b) and (%b >= %c))) with [%x, %y, %z] # TODO: optimize for common case where x,y,z are all either variables or number literals # Boolean Operators test: - local action [barfer] (barf "short circuiting failed") + (barfer) means (barf "short circuiting failed") assume (((no) and (barfer)) == (no)) assume ((no) or (yes)) assume ((yes) or (barfer)) -compile [%x and %y] to (Lua value "(\(%x as lua expr) and \(%y as lua expr))") -compile [%x or %y] to (Lua value "(\(%x as lua expr) or \(%y as lua expr))") +(%x and %y) compiles to (Lua value "(\(%x as lua expr) and \(%y as lua expr))") +(%x or %y) compiles to (Lua value "(\(%x as lua expr) or \(%y as lua expr))") ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -219,31 +219,31 @@ test: # Lua 5.3 introduced bit operators like | and &. Use them when possible, otherwise fall back to bit.bor(), bit.band(), etc. %use_bitops = ((is jit) or ((Lua version) == "Lua 5.2")) -compile [NOT %, ~ %] to (..) +[NOT %, ~ %] all compile to (..) Lua value (..) (%use_bitops and "bit.bnot(\(% as lua expr))") or "~(\(% as lua expr))" -compile [%a OR %b, %a | %b] to (..) +[%a OR %b, %a | %b] all compile to (..) Lua value (..) (%use_bitops and "bit.bor(\(%a as lua expr), \(%b as lua expr))") or "\ ..(\(%a as lua expr) | \(%b as lua expr))" -compile [%a XOR %b, %a ~ %b] to (..) +[%a XOR %b, %a ~ %b] all compile to (..) Lua value (..) (%use_bitops and "bit.bxor(\(%a as lua expr), \(%b as lua expr))") or "\ ..(\(%a as lua expr) ~ \(%b as lua expr))" -compile [%a AND %b, %a & %b] to (..) +[%a AND %b, %a & %b] all compile to (..) Lua value (..) (%use_bitops and "bit.band(\(%a as lua expr), \(%b as lua expr))") or "\ ..(\(%a as lua expr) & \(%b as lua expr))" -compile [%x LSHIFT %shift, %x << %shift] to (..) +[%x LSHIFT %shift, %x << %shift] all compile to (..) Lua value (..) (%use_bitops and "bit.lshift(\(%x as lua expr), \(%shift as lua expr))") or "\ ..(\(%x as lua expr) << \(%shift as lua expr))" -compile [%x RSHIFT %shift, %x >> %shift] to (..) +[%x RSHIFT %shift, %x >> %shift] all compile to (..) Lua value (..) (%use_bitops and "bit.rshift(\(%x as lua expr), \(%shift as lua expr))") or "\ ..(\(%x as lua expr) >> \(%shift as lua expr))" @@ -252,15 +252,15 @@ compile [%x RSHIFT %shift, %x >> %shift] to (..) test: assume ((- 5) == -5) assume ((not (yes)) == (no)) -compile [- %] to (Lua value "(- \(% as lua expr))") -compile [not %] to (Lua value "(not \(% as lua expr))") +(- %) compiles to (Lua value "(- \(% as lua expr))") +(not %) compiles to (Lua value "(not \(% as lua expr))") test: assume ((size of [1, 2, 3]) == 3) -compile [size of %list, size of %list, size of %list, size of %list] to (..) +[size of %list, size of %list, size of %list, size of %list] all compile to (..) Lua value "(#\(%list as lua expr))" -compile [%list is empty] to (Lua value "(#\(%list as lua expr) == 0)") +(%list is empty) compiles to (Lua value "(#\(%list as lua expr) == 0)") ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -273,11 +273,11 @@ test: assume (%x == 4) or barf "*= failed" wrap %x around 3 assume (%x == 1) or barf "wrap around failed" -parse [%var += %] as (%var = (%var + %)) -parse [%var -= %] as (%var = (%var - %)) -parse [%var *= %] as (%var = (%var * %)) -parse [%var /= %] as (%var = (%var / %)) -parse [%var ^= %] as (%var = (%var ^ %)) -parse [%var and= %] as (%var = (%var and %)) -parse [%var or= %] as (%var = (%var or %)) -parse [wrap %var around %] as (%var = (%var wrapped around %)) +(%var += %) parses as (%var = (%var + %)) +(%var -= %) parses as (%var = (%var - %)) +(%var *= %) parses as (%var = (%var * %)) +(%var /= %) parses as (%var = (%var / %)) +(%var ^= %) parses as (%var = (%var ^ %)) +(%var and= %) parses as (%var = (%var and %)) +(%var or= %) parses as (%var = (%var or %)) +(wrap %var around %) parses as (%var = (%var wrapped around %)) diff --git a/core/scopes.nom b/core/scopes.nom index c01a361..2d86ca4 100644 --- a/core/scopes.nom +++ b/core/scopes.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file contains definitions pertaining to variable scoping @@ -14,19 +14,19 @@ test: assume (%x == "inner") assume (%x == "outer") - action [foo] "outer foo" - with local [action (foo)]: - action [foo] "inner foo" + externally (foo) means "outer foo" + with local [(foo)'s meaning]: + externally (foo) means "inner foo" assume ((foo) == "inner foo") assume ((foo) == "outer foo") -compile [with local %locals %body, with local %locals do %body] to: +[with local %locals %body, with local %locals do %body] all compile to: %body_lua = (%body as lua statements) if %locals.type is: "Dict": %body_lua = (..) Lua "\ - ..\(compile as (<- %locals)) + ..\(what (<- %locals) compiles to) \%body_lua" %body_lua::declare locals ("\(%.1 as lua)" for % in %locals) diff --git a/core/text.nom b/core/text.nom index 25e9c3c..05ec409 100644 --- a/core/text.nom +++ b/core/text.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file contains some definitions of text escape sequences, including ANSI console color codes. @@ -22,12 +22,12 @@ test: assume ("asdf"::uppercase) == "ASDF" assume ("asdf"::with "s" -> "X") == "aXdf" assume ("one\ntwo\n"::lines) == ["one", "two", ""] - parse [アクション %spec %body] as (action %spec %body) + (アクション %spec %body) parses as (externally %spec means %body) test: %こんにちは = "こんにちは" アクション [% と言う] "\(%)世界" assume (%こんにちは と言う) == "こんにちは世界" -compile [%expr for %match in %text matching %patt] to (..) +(%expr for %match in %text matching %patt) compiles to (..) Lua value "\ ..(function() local ret = _List{} diff --git a/examples/how_do_i.nom b/examples/how_do_i.nom index 1d43982..ec75596 100644 --- a/examples/how_do_i.nom +++ b/examples/how_do_i.nom @@ -1,10 +1,10 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # How do I... # Write a comment? Put a # and go till the end of the line # How do I write a multi-line comment? After a comment line, any indented text is considered part of the comment - (including any deeper-level indented text) + (including any deeper-level indented text) The comment ends when the indentation ends # How do I import a file? use "lib/os.nom" @@ -167,14 +167,14 @@ do: # How do I define a function/method/procedure? # In nomsu, they're called "action"s, and they can be declared like this: -action [say both %first and also %second]: +(say both %first and also %second) means: say %first # Function arguments are accessed just like variables say %second # Actions can use "return" to return a value early -action [first fibonacci above %n]: +(first fibonacci above %n) means: %f1 = 0 %f2 = 1 repeat: @@ -186,11 +186,11 @@ action [first fibonacci above %n]: say (first fibonacci above 10) # Actions can have aliases, which may or may not have the arguments in different order -action [..] +[..] I hate %worse_things more than %better_things I think %worse_things are worse than %better_things I like %better_things more than %worse_things -..: +..all mean: say "\(%better_things::capitalized) rule and \%worse_things drool!" I like "dogs" more than "cats" @@ -201,7 +201,7 @@ I think "chihuahuas" are worse than "corgis" say both "Hello" and also "again!" # Actions can even start with a parameter -action [%what_she_said is what she said]: +(%what_she_said is what she said) means: say %what_she_said say "-- she said" @@ -209,7 +209,7 @@ action [%what_she_said is what she said]: # The language only reserves []{}().,:;% as special characters, so actions can have really funky names! -action [>> %foo_bar $$$^ --> % @&_~-^-~_~-^ %1 !]: +(>> %foo_bar $$$^ --> % @&_~-^-~_~-^ %1 !) means: say %foo_bar say % say %1 @@ -218,7 +218,7 @@ action [>> %foo_bar $$$^ --> % @&_~-^-~_~-^ %1 !]: # There's also full unicode support %こんにちは = "こんにちは" -action [% と言う] "\%世界" +(% と言う) means "\%世界" say (%こんにちは と言う) # Math and logic operations are just treated the same as actions in the syntax @@ -226,7 +226,7 @@ say (2 + 3) # So you can define your own operators, although they will need to be parenthesized to play nicely with other operators -action [%a ++ %b] (2 * (%a + %b)) +(%a ++ %b) means (2 * (%a + %b)) say (1 ++ (2 * 3)) # How do I do grouping? @@ -240,36 +240,38 @@ say (2 + 3) say both "Very long first argument that needs its own line" and also "\ ..short second arg" -action [my favorite number] (21 + 2) +(my favorite number) means (21 + 2) # This can be nested: say both (my favorite number) and also "foo" # Macros: # The "lua> %" and "=lua %" macros can be used to write raw lua code: -action [say the time] (lua> "io.write(\"The OS time is: \", os.time(), \"\\n\");") +(say the time) means: + 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")" # Variables can be accessed via \%varname -action [square root of %n] (=lua "math.sqrt(\%n)") +(square root of %n) means (=lua "math.sqrt(\%n)") say "The square root of 2 is \(square root of 2)" # 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) +(if %condition is untrue %body) parses 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 (..) +(if %condition on opposite day %body) compiles to (..) Lua "\ ..if not \(%condition as lua expr) then \(%body as lua statements) end" # Constants can be defined as macros -parse [TWENTY] as 20 +(TWENTY) parses as 20 # When they're invoked, they'll need parentheses just like a function call -parse [TWENTY ONE] as 21 +(TWENTY ONE) parses as 21 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -286,7 +288,7 @@ if (1 > (TWENTY)) on opposite day: # How do I use an action as a value? # Well... it's always *possible* to fall back to Lua behavior for something like this: -action [best of %items according to %key_fn]: +(best of %items according to %key_fn) means: set {%best:nil, %best_key:nil} for %item in %items: %key = (=lua "\%key_fn(\%item)") @@ -301,7 +303,7 @@ say (best of [2, -3, 4, -8] according to ([%x] -> (%x * %x))) 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: -parse [best of %items where %item_var has score %key_expr] as (..) +(best of %items where %item_var has score %key_expr) parses as (..) result of: set {%best:nil, %best_key:nil} for %item_var in %items: @@ -181,6 +181,8 @@ Files.walk = function(path, flush_cache) files = { path } + elseif path:match("^[~/]") or path:match("^%./") or path:match("^%.%./") then + files = browse(path) else for nomsupath in package.nomsupath:gmatch("[^;]+") do do @@ -191,20 +193,18 @@ Files.walk = function(path, flush_cache) end end end - local iter - iter = function(files, i) - if not (files) then - return - end - i = i + 1 - do - local f = files[i] - if f then - return i, f - end + files = files or { } + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #files do + local f = files[_index_0] + _accum_0[_len_0] = gsub(f, "^%./", "") + _len_0 = _len_0 + 1 end + files = _accum_0 end - return iter, files, 0 + return ipairs(files) end local line_counter = re.compile([[ lines <- {| line (%nl line)* |} line <- {} (!%nl .)* @@ -33,7 +33,6 @@ Files.read = (filename)-> _FILE_CACHE[filename] = contents return contents --- `walk` returns an iterator over all files matching a path. {:match, :gsub} = string -- TODO: improve sanitization @@ -105,15 +104,14 @@ Files.walk = (path, flush_cache=false)-> local files if path == 'stdin' or _SPOOFED_FILES[path] files = {path} + elseif path\match("^[~/]") or path\match("^%./") or path\match("^%.%./") + files = browse(path) else for nomsupath in package.nomsupath\gmatch("[^;]+") if files = browse(nomsupath.."/"..path) then break - iter = (files, i)-> - return unless files - i += 1 - if f = files[i] - return i, f - return iter, files, 0 + files or= {} + files = [gsub(f, "^%./", "") for f in *files] + return ipairs(files) line_counter = re.compile([[ lines <- {| line (%nl line)* |} diff --git a/lib/base64.nom b/lib/base64.nom index 8fc4e6b..2091615 100644 --- a/lib/base64.nom +++ b/lib/base64.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines actions for encoding/decoding base 64, as specified in: https://tools.ietf.org/html/rfc4648 @@ -13,7 +13,7 @@ test: %plain = "foobar".[1, %len - 1] assume (base64 %plain) == %encoded assume (base64 decode %encoded) == %plain -action [base64 %str, base64 encode %str, %str base64]: +externally [base64 %str, base64 encode %str, %str base64] all mean: %chars = [] for %i in 1 to (size of %str) via 3: %bytes = [=lua "\%str:byte(\%i, \(%i + 2))"] @@ -36,8 +36,8 @@ action [base64 %str, base64 encode %str, %str base64]: return (%chars::joined) -action [chr %] (=lua "string.char(\%)") -action [decode base64 %str, %str base64 decoded, base64 decode %str]: +externally (chr %) means (=lua "string.char(\%)") +externally [decode base64 %str, %str base64 decoded, base64 decode %str] all mean: %chars = [] for %i in 1 to (size of %str) via 4: %indices = (%reverse_b64.(%str.%) for % in %i to (%i + 3)) diff --git a/lib/consolecolor.nom b/lib/consolecolor.nom index f4c4df9..fe7da4c 100644 --- a/lib/consolecolor.nom +++ b/lib/consolecolor.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines actions for ANSI console color escape codes. @@ -14,9 +14,10 @@ test: for %name = %colornum in %colors: %colornum = "\%colornum" - (=lua "COMPILE_ACTIONS").%name = (..) - [%nomsu, %tree] -> (..) - Lua value "'\\027[\(%colornum)m'" - (=lua "COMPILE_ACTIONS")."\%name 1" = (..) - [%nomsu, %tree, %text] -> (..) - Lua value "('\\027[\(%colornum)m'..\(%text as lua expr)..'\\027[0m')" + #(=lua "COMPILE_ACTIONS").%name = (..) + [%nomsu, %tree] -> (Lua value "'\\027[\(%colornum)m'") + (=lua "COMPILE_ACTIONS")."\%name" = (..) + [%nomsu, %tree, %text] ->: + if %text: + return (Lua value "('\\027[\(%colornum)m'..\(%text as lua expr)..'\\027[0m')") + ..else: return (Lua value "'\\027[\(%colornum)m'") diff --git a/lib/file_hash.nom b/lib/file_hash.nom index 6fdb2f4..6c815f5 100644 --- a/lib/file_hash.nom +++ b/lib/file_hash.nom @@ -1,4 +1,4 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines some actions for hashing files and looking up files by hash. @@ -26,14 +26,14 @@ test: if %use_sha1: assume ((hash "hello world") == "Kq5sNclPz7QV2+lfQIuc6R7oRu0=") if %use_sha1: - action [hash %]: + externally (hash %) means: %hash = (=lua "\%hashlib.new('sha1'):final(\%)") return (base64 %hash) ..else: # TODO: remove warning? say "\ ..\027[31;1mWARNING: OpenSSL module not found. Defaulting to a non-cryptographically secure hash function.\027[0m" - action [hash %]: + externally (hash %) means: %bytes = (%::bytes) %hash = (%bytes.1 << 7) for %i in 2 to (size of %bytes): @@ -41,10 +41,10 @@ if %use_sha1: %hash = (%hash ~ (size of %bytes)) return "\%hash" -action [file with hash %hash]: +externally (file with hash %hash) means: for file %filename in ".": %contents = (read file %filename) %file_hash = (hash %contents) if (%file_hash == %hash): return %filename -parse [hash of file %filename] as (hash (read file %filename)) +(hash of file %filename) parses as (hash (read file %filename)) diff --git a/lib/object.nom b/lib/object.nom index b49b8f2..d5555df 100644 --- a/lib/object.nom +++ b/lib/object.nom @@ -1,9 +1,16 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file contains the implementation of an Object-Oriented programming system. +%globals.METAMETHOD_MAP = {..} + "as text":"__tostring", "clean up":"__gc", "+ 1":"__add", "- 1":"__sub" + "* 1":"__mul", "/ 1":"__div", "-":"__unm", "// 1":"__idiv", "mod 1":"__mod" + "^ 1":"__pow", "& 1":"__band", "| 1":"__bor", "~ 1":"__bxor", "~":"__bnot" + "<< 1":"__bshl", ">> 1":"__bshr", "== 1":"__eq", "< 1":"__lt", "<= 1":"__le" + "set 1 = 2":"__newindex", size:"__len", iterate:"__ipairs", "iterate all":"__pairs" + test: - object "Dog": + object (Dog): (Dog).genus = "Canus" my action [set up]: %me.barks or= 0 my action [bark, woof]: @@ -12,8 +19,10 @@ test: my action [get pissed off]: %me.barks += 1 - %d = (new Dog {barks:2}) - assume (%d.barks == 2) + %d = (Dog {barks:2}) + assume (type of %d) == "Dog" + assume (%d is a "Dog") + assume %d.barks == 2 assume ((%d::bark) == "Bark! Bark!") assume ((%d::woof) == "Bark! Bark!") %d::get pissed off @@ -23,38 +32,38 @@ test: assume ("\(%d.class)" == "Dog") assume (%d.genus == "Canus") assume (%d.barks == 3) - %d2 = (new Dog) + %d2 = (Dog {}) assume (%d2.barks == 0) or barf "Default initializer failed" - with {%d:new Dog {barks:1}}: + with {%d:Dog {barks:1}}: assume ((%d::bark) == "Bark!") - object "Corgi" extends (Dog): + object (Corgi) extends (Dog): my action [sploot] "splooted" my action [bark, woof]: %barks = ("Yip!" for % in 1 to %me.barks) return (%barks::joined with " ") - %corg = (new Corgi) + %corg = (Corgi {}) assume (%corg.barks == 0) - with {%d:new Corgi {barks:1}}: + with {%d:Corgi {barks:1}}: assume ((%d::sploot) == "splooted") or barf "subclass method failed" assume ((%d::bark) == "Yip!") or barf "inheritance failed" assume ((%d::woof) == "Yip!") - with {%d:new Dog {barks:2}}: + with {%d:Dog {barks:2}}: assume ((%d::bark) == "Bark! Bark!") -compile [my action %actions %body] to: +(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, \(\%me as lua id)) + table.insert(\%args, 1, \(\%me as lua id)) local lua = LuaCode(tree.source, "class.", fn_name, " = ", \(..) - compile as (%args -> %body) + 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, \(\%me as lua id)) + table.insert(\%alias_args, 1, \(\%me as lua id)) lua:append("\\nclass.", alias_name, " = ") if utils.equivalent(\%args, \%alias_args) then lua:append("class.", fn_name) @@ -68,45 +77,39 @@ compile [my action %actions %body] to: end return lua" -compile [object %classname extends %parent %class_body] to: +(object %classname extends %parent %class_body) compiles to: + unless (%classname.type == "Action"): + compile error at %classname "Expected this to be an action, not a \(%classname.type)" + for % in %classname: + unless (% is text): + compile error at % "Class names should not have arguments." + return (..) Lua "\ ..do - local class = {name=\(%classname as lua expr)} + local class = {name=\(quote %classname.stub)} + class.__type = class.name setmetatable(class, { __index=\(%parent as lua expr), __tostring=function(cls) return cls.name end, __call=function(cls, inst) - inst = setmetatable(inst or {}, cls) - if inst.set_up then - inst:set_up() - end + if inst == nil then return cls end + inst = setmetatable(inst, cls) + if inst.set_up then inst:set_up() end return inst end, }) - nomsu.environment[("new "..class.name):as_lua_id()] = class - nomsu.environment[("new "..class.name.." 1"):as_lua_id()] = class - nomsu.environment[class.name:as_lua_id()] = function() return class end + nomsu.environment[class.name:as_lua_id()] = class class.__index = class class.class = class class.__tostring = function(inst) return inst.name..getmetatable(_Dict{}).__tostring(inst) end - \(%class_body as lua statements) - - local metamethod_map = {["as text"]="__tostring", ["clean up"]="__gc", - ["+ 1"]="__add", ["- 1"]="__sub", ["* 1"]="__mul", ["/ 1"]="__div", - ["-"]="__unm", ["// 1"]="__idiv", ["mod 1"]="__mod", ["^ 1"]="__pow", - ["& 1"]="__band", ["| 1"]="__bor", ["~ 1"]="__bxor", ["~"]="__bnot", - ["<< 1"]="__bshl", [">> 1"]="__bshr", ["== 1"]="__eq", ["< 1"]="__lt", - ["<= 1"]="__le", ["set 1 = 2"]="__newindex", ["length"]="__len", - ["__ipairs"]="__ipairs", ["__pairs"]="__pairs", - } - for stub,metamethod in pairs(metamethod_map) do + for stub,metamethod in pairs(globals.METAMETHOD_MAP) do class[metamethod] = class[stub:as_lua_id()] end end" -parse [object %classname %class_body] as (..) +(object %classname %class_body) parses as (..) object %classname extends (nil) %class_body @@ -1,14 +1,14 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file defines some actions that interact with the operating system and filesystem. test: path of Nomsu file "lib/os.nom" -action [path of Nomsu file %filename]: +externally (path of Nomsu file %filename) means: lua> "for i,f in Files.walk(\%filename) do return f end" barf "Could not find file: \%filename" -action [sh> %cmd]: +externally (sh> %cmd) means: lua> "\ ..local result = io.popen(\%cmd) local contents = result:read("*a") @@ -17,19 +17,19 @@ action [sh> %cmd]: test: read file "lib/os.nom" -action [read file %filename] (=lua "Files.read(\%filename)") +externally (read file %filename) means (=lua "Files.read(\%filename)") test: for file %f in "core": do nothing -compile [for file %f in %path %body] to (..) +(for file %f in %path %body) compiles to (..) Lua "\ ..for i,\(%f as lua expr) in Files.walk(\(%path as lua expr)) do \(%body as lua statements) - \(compile as (===next %f ===)) + \(what (===next %f ===) compiles to) end - \(compile as (===stop %f ===))" + \(what (===stop %f ===) compiles to)" -compile [%expr for file %f in %path] to (..) +(%expr for file %f in %path) compiles to (..) Lua value "\ ..(function() local ret = _List{} @@ -39,10 +39,10 @@ compile [%expr for file %f in %path] to (..) return ret end)()" -action [..] +externally [..] write to file %filename %text, to file %filename write %text write %text to file %filename -..: +..all mean: assume (%filename != "stdin") or barf "Cannot write to stdin" lua> "\ ..local file = io.open(\%filename, 'w') @@ -51,15 +51,17 @@ action [..] test: assume (line number of 3 in "x\ny") == 2 -action [line number of %pos in %str] (=lua "Files.get_line_number(\%str, \%pos)") +externally (line number of %pos in %str) means (..) + =lua "Files.get_line_number(\%str, \%pos)" test: assume (line 2 in "one\ntwo\nthree") == "two" -action [line %line_num in %str] (=lua "Files.get_line(\%str, \%line_num)") +externally (line %line_num in %str) means (..) + =lua "Files.get_line(\%str, \%line_num)" test: assume (source lines of \(this)) -action [source lines of %tree]: +externally (source lines of %tree) means: %source = (%tree.source if (%tree is syntax tree) else %tree) %file = (read file %source.filename) return (..) diff --git a/lib/training_wheels.nom b/lib/training_wheels.nom index 6155069..f16b700 100644 --- a/lib/training_wheels.nom +++ b/lib/training_wheels.nom @@ -1,26 +1,28 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file contains a set of definitions that bring some familiar language features from other languages into nomsu (e.g. "||" and "continue") -parse [%a === %b] as ((%a 's id) is (%b 's id)) -parse [%a !== %b] as ((%a 's id) is not (%b 's id)) -parse [function %names %body, def %names %body] as (action %names %body) -parse [switch %branch_value %body] as (if %branch_value is %body) -parse [None, Null] as (nil) -parse [True, true] as (yes) -parse [False, false] as (no) -parse [pass] as (do nothing) -parse [%a || %b] as (%a or %b) -parse [%a && %b] as (%a and %b) -parse [continue] as (do next) -parse [break] as (stop) -parse [let %thing = %value in %action] as (with local {%thing:%value}) -parse [print %, println %] as (say %) -parse [error!, panic!, fail!, abort!] as (barf!) -parse [error %, panic %, fail %, abort %] as (barf %) -parse [assert %condition] as (assume %condition) -parse [assert %condition %message] as (assume %condition or barf %message) -parse [%cond ? %if_true %if_false] as (%if_true if %cond else %if_false) -parse [lambda %args %body] as (%args -> %body) -parse [function %name %args %body] as (%name = (%args -> %body)) +(%a === %b) parses as ((%a 's id) is (%b 's id)) +(%a !== %b) parses as ((%a 's id) is not (%b 's id)) +[function %names %body, def %names %body] all parse as (..) + externally %names means %body + +(switch %branch_value %body) parses as (if %branch_value is %body) +[None, Null] all parse as (nil) +[True, true] all parse as (yes) +[False, false] all parse as (no) +(pass) parses as (do nothing) +(%a || %b) parses as (%a or %b) +(%a && %b) parses as (%a and %b) +(continue) parses as (do next) +(break) parses as (stop) +(let %thing = %value in %action) parses as (with local {%thing:%value}) +[print %, println %] all parse as (say %) +[error!, panic!, fail!, abort!] all parse as (barf!) +[error %, panic %, fail %, abort %] all parse as (barf %) +(assert %condition) parses as (assume %condition) +(assert %condition %message) parses as (assume %condition or barf %message) +(%cond ? %if_true %if_false) parses as (%if_true if %cond else %if_false) +(lambda %args %body) parses as (%args -> %body) +(function %name %args %body) parses as (%name = (%args -> %body)) diff --git a/lib/version.nom b/lib/version.nom index afb9e14..2f4003f 100644 --- a/lib/version.nom +++ b/lib/version.nom @@ -1,3 +1,3 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # This file sets the current library version. lua> "NOMSU_LIB_VERSION = 6" diff --git a/nomnom/ast.nom b/nomnom/ast.nom new file mode 100644 index 0000000..ef41b26 --- /dev/null +++ b/nomnom/ast.nom @@ -0,0 +1,88 @@ +#!/usr/bin/env nomsu -V4.8.10 +use "lib/object.nom" + +# The types are [..] + "Number", "Var", "Block", "EscapedNomsu", "Text", "List", "Dict", "DictEntry", + "IndexChain", "Action", "FileChunks", "Error", "Comment" +object (Syntax Tree): + my action [set up]: + if (%me.type == "Action"): + %stub_bits = [] + %argnum = 1 + for %bit in %me: + if: + (%bit is text): + %stub_bits::add %bit + (%bit.type != "Comment"): + %stub_bits::add "\%argnum" + %argnum += 1 + + %me.stub = (%stub_bits::joined with " ") + if (%me.stub == "Lua Code 1 2"): + lua> "require('ldt').breakpoint()" + + (Syntax Tree).source_code_for_tree = (..) + {} with fallback % -> (read file %.source.filename) + my action [children]: + %children = [] + for % in %me: + if ((% is a "Syntax Tree") and (%.type != "Comment")): %children::add % + if ((%me.type == "Action") and %me.target): + %children::add %me.target + return %children + + my action [as lua] "\ + ..a_Syntax_Tree_with(\(call ({} 's metatable).as_lua with [%me]))" + my action [as nomsu] "\ + ..(a Syntax Tree with \(call ({} 's metatable).as_nomsu with [%me]))" + my action [as text] "\ + ..(Syntax Tree \(call ({} 's metatable).__tostring with [%me]))" + my action [get source code] (Syntax Tree).source_code_for_tree.%me + my action [map %fn]: + %replacement = (call %fn with [%me]) + if %replacement: + if (%replacement is a "Syntax Tree"): + %replacement = (%k = %v for %k = %v in %replacement) + %replacement.source = %me.source + return (Syntax Tree %replacement) + return %replacement + ..else: + %replacement = {} + %changes = (no) + for %k = %v in %me: + %replacement.%k = %v + if (%v is a "Syntax Tree"): + %r = (%v::map %fn) + if ((%r == %v) or (%r == (nil))): do next %k + %changes = (yes) + %replacement.%k = %r + + unless %changes: return %me + return (Syntax Tree %replacement) + + my action [with %overrides]: + %new = (%k = %v for %k = %v in %me) + for %k = %v in %overrides: %new.%k = %v + return (Syntax Tree %new) + + my action [== %other]: + unless (..) + all of [..] + (type of %me) == (type of %other), (%me 's metatable) == (%other 's metatable) + (size of %me) == (size of %other), %me.type == %other.type + ..: return (no) + + for %item in %me at %i: + if (%other.%i != %item): return (no) + if (%me.type == "Action"): + if (%me.target != %other.target): return (no) + return (yes) + + my action [get args]: + assume (%me.type == "Action") or barf "\ + ..Only actions have arguments, not \(%me.type)" + %args = [] + for % in %me: + unless ((% is text) or (%.type == "Comment")): %args::add % + return %args + diff --git a/nomnom/code_obj.nom b/nomnom/code_obj.nom new file mode 100644 index 0000000..c8d2784 --- /dev/null +++ b/nomnom/code_obj.nom @@ -0,0 +1,210 @@ +#!/usr/bin/env nomsu -V4.8.10 +# This file contains objects that are used to track code positions and incrementally + build up generated code, while keeping track of where it came from, and managing + indentation levels. +use "lib/things.nom" + +a (Code Buffer) is a thing: + that can (set up) by: + assume %its.source + %old_bits = (%its.bits if (%its.bits is a "List") else [%its.bits]) + %its.bits = [] + if (type of %its.source) is: + "Text": + %its.source = (Source from text %its.source) + "Syntax Tree": + %its.source = %its.source.source + + for % in %old_bits: %its::add % + + whose (text) means: + if (%its._text == (nil)): + %buff = [] + %indent = 0 + for %bit in %its.bits: + if (%bit is text): + %spaces = (%bit::matching "\n([ ]*)[^\n]*$") + if %spaces: %indent = (size of %spaces.1) + ..else: + %bit = (%bit::text) + if (%indent > 0): + %bit = (%bit::with "\n" -> "\n\(" "::* %indent)") + %buff::add %bit + %its._text = (%buff::joined) + return %its._text + + whose (lua code) means "\ + ..a_\(%its.class.name::as lua id)_with{source=\(..) + (%its.source::as lua) if %its.source else "nil" + .., \(%its.bits::as lua)}" + + whose (nomsu code) means "\ + ..(a \(%its.class.name) with {source: \((%its.source::as nomsu) if %its.source else "(nil)"), bits: \(..) + %its.bits::as nomsu + ..})" + + whose (size) means (size of (%its::text)) + + that can (mark as dirty) by: + %its._text = (nil) + %its._trailing_line_len = (nil) + %its._num_lines = (nil) + + that can (add %new_bits) by: + unless (%new_bits is a "List"): + %new_bits = [%new_bits] + for % in %new_bits: + if (% == ""): do next % + #if ((% isn't text) and (% isn't a (Code))): + % = (%::as lua) + %its.bits::add % + %its::mark as dirty + + whose (trailing line length) means: + if (%its._trailing_line_len == (nil)): + %its._trailing_line_len = (size of ((%its::text)::matching "[^\n]*$")) + return %its._trailing_line_len + + whose (number of lines) means: + unless %its._num_lines: + %num_lines = 1 + for % in %its: + if (% is text): + %num_lines += (size of (%::all matches of "\n")) + ..else: + %num_lines += ((%::number of lines) - 1) + + %its._num_lines = %num_lines + + return %its._num_lines + + whose [is multiline, is multi-line] all mean ((%its::number of lines) > 1) + whose [is one line, is single line] all mean ((%its::number of lines) == 1) + that can (add %values joined with %joiner) by: + %its::add %values joined with %joiner or %joiner + that can [add %values joined with %joiner or %wrapping_joiner] by: + %line_len = 0 + %bits = %its.bits + for %value in %values at %i: + if (%i > 1): + if (%line_len > 80): + %bits::add %wrapping_joiner + %line_len = 0 + ..else: %bits::add %joiner + + %bits::add %value + unless (%value is text): + %value = (%value::text) + %line = (%value::matching "\n([^\n]*)$") + if %line: + %line_len = (size of %line) + ..else: + %line_len += (size of %value) + %its::mark as dirty + + that can (prepend %) by: + #if ((% isn't text) and (% isn't a %its.__type)): + % = (%::as lua) + %its.bits::add % at index 1 + %its::mark as dirty + + that can (parenthesize) by: + %its.bits::add "(" at index 1 + %its.bits::add ")" + %its::mark as dirty + +a (Lua Buffer) is a thing: + that has [..] + text, lua code, nomsu code, trailing line length, size, number of lines, + is multiline, is multi-line, is one line, is single line, + ..like a (Code Buffer) + that can [..] + set up, mark as dirty, add %, prepend %, parenthesize, + add % joined with %, add % joined with % or %, + ..like a (Code Buffer) + + that can (add free vars %vars) by: + if ((size of %vars) == 0): return + %seen = (%v = (yes) for %v in %its.free_vars) + for %var in %vars: + assume (%var is text) + unless %seen.%var: + %its.free_vars::add %var + %seen.%var = (yes) + + %its::mark as dirty + + that can (remove free vars %vars) by: + if ((size of %vars) == 0): return + %removals = {} + for %var in %vars: + assume (%var is text) + %removals.%var = (yes) + + %stack = [%its] + repeat while ((size of %stack) > 0): + %lua = (%stack::pop) + for %i in (size of %lua.free_vars) to 1 by -1: + if %removals.(%lua.free_vars.%i): + %lua.free_vars::remove at index %i + + for % in %lua.bits: + unless (% is text): %stack::add % + + %its::mark as dirty + + that can (declare locals) by (%its::declare locals (nil)) + that can (declare locals %to_declare) by: + unless %to_declare: + %to_declare = [] + %seen = {} + for %lua in recursive %its: + for %var in %lua.free_vars: + unless %seen.%var: + %seen.%var = (yes) + %to_declare::add %var + + for % in %lua.bits: + unless (% is text): recurse %lua on % + + if ((size of %to_declare) > 0): + %its::remove free vars %to_declare + %its::prepend "local \(%to_declare::joined with ", ");\n" + return %to_declare + + whose (as statements) means (%its::as statements with "") + whose (as statements with %prefix) means: + unless %its.is_value: return %its + %statements = (a Lua Buffer with {source:%its.source}) + if ((%prefix or "") != ""): + %statements::add %prefix + %statements::add %its + %statements::add ";" + return %statements + + that can (mark as value) by: + %its.is_value = (yes) + + that can (mark as variable) by: + %its.is_variable = (yes) + %its.is_value = (yes) + + that can (variables) by: + %vars = [] + for %code in recursive %its: + if %code.is_variable: + %vars::add (%code::text) + for % in %code.bits: + unless (% is text): recurse %code on % + + return %vars + +a (Nomsu Buffer) is a thing: + that has [..] + text, lua code, nomsu code, trailing line length, size, number of lines, + is multiline, is multi-line, is one line, is single line, + ..like a (Code Buffer) + that can [..] + set up, mark as dirty, add %, prepend %, parenthesize, + add % joined with %, add % joined with % or %, + ..like a (Code Buffer) diff --git a/nomnom/compile.nom b/nomnom/compile.nom new file mode 100644 index 0000000..8241ef2 --- /dev/null +++ b/nomnom/compile.nom @@ -0,0 +1,276 @@ +#!/usr/bin/env nomsu -V4.8.10 +# This file contains the code to convert syntax trees to Lua code +use "nomnom/code_obj.nom" +use "nomnom/parser.nom" +use "nomnom/pretty_errors.nom" + +externally (report compile error at %tree %err) means: + barf (pretty "Compile Error" error at %tree %err) + +externally (report compile error at %pos %err hint %hint) means: + barf (pretty "Compile Error" error at %tree %err hint %hint) + +externally (barf any errors in %t) means: + assume (%t is a "Syntax Tree") + %errs = [] + for % in recursive %t: + if (%.type == "Error"): %errs::add % + for %k = %v in %: + if (%v is a "Syntax Tree"): recurse % on %v + + sort %errs by % -> %.source + %errs = ((% as a pretty error) for % in %errs) + if ((size of %errs) > 0): + if ((size of %errs) > 3): + %n = ((size of %errs) - 3) + for %i in 4 to (size of %errs): %errs.%i = (nil) + %errs::add "\027[31;1m +\%n additional errors...\027[0m\n" + barf (%errs::joined with "\n\n") + +externally (%tree compiled with %compile_actions) means: + assume (%tree is a "Syntax Tree") + if all of [..] + %tree.version, ((Nomsu version)'s meaning) != (nil), %tree.version != (Nomsu version) + ((1 upgraded from 2 to 3)'s meaning) != (nil) + ..then: + %tree = (upgrade %tree from %tree.version to (Nomsu version)) + + if %tree.type is: + "Action": + %stub = %tree.stub + %compile_action = %compile_actions.%stub + + # Don't apply compiler actions to methods + if (%compile_action and (not %tree.target)): + # TODO: restore this: + #%args = [%tree, %compile_actions] + %args = [%nomsu, %tree] + for % in (%tree::arguments): %args::add % + %result = (call %compile_action with %args) + if (%result == (nil)): + report compile error at %tree "\ + ..The compile-time action here (\(%tree.stub)) failed to return any value." + ..hint "\ + ..Look at the implementation of (\(%tree.stub)) and make sure it's returning something." + + if (%result is a "Syntax Tree"): + if (%result == %tree): + report compile error at %tree "\ + ..The compile-time action here (\(%tree.stub)) is producing an endless loop." + ..hint "\ + ..Look at the implementation of (\(%tree.stub)) and make sure it's not just returning the original tree." + + return (%result compiled with %compile_actions) + + return %result + + %lua = (a Lua Buffer with {source:%tree}) + if %tree.target: + # Method call + %target_lua = (%tree.target compiled with %compile_actions) + if (..) + ((%target_lua::text)::matches "^%(.*%)$") or (..) + (%target_lua::text)::matches "^[_a-zA-Z][_a-zA-Z0-9]*$" + ..: + %lua::add [%target_lua, ":"] + ..else: + %lua::add ["(", %target_lua, "):"] + + %lua::add [%stub as lua id, "("] + %args = [] + for %tok in %tree at %i: + if (%tok is text): do next %tok + + # TODO: maybe don't translate Lua comments + #if (%tok.type == "Comment"): do next %tok + if (%tok.type == "Block"): + %values = [] + for %line in %tok: + #unless (%line.type == "Comment"): + %values::add (%line compiled with %compile_actions) + + if all of (%.is_value for % in %values): + if ((size of %values) == 1): + %arg_lua = %values.1 + ..else: + %arg_lua = (a Lua Buffer with {source:%tok, is_value:yes, bits:["("]}) + %arg_lua::add %values joined with " and nil or " + %arg_lua::add ")" + ..else: + %arg_lua = (a Lua Buffer with {source:%tok, is_value:yes, bits:["((function()"]}) + for %v in %values at %i: + if %v.is_value: + %v = (%v::as statements with ("return " if (%i == (size of %values) else ""))) + %arg_lua::add ["\n ", %v] + + %arg_lua::add "\nend)())" + ..else: + %arg_lua = (%tok compiled with %compile_actions) + unless %arg_lua.is_value: + if (%tok.type == "Action"): + report compile error at %tok "\ + ..Can't use this as an argument to (\%stub), since it's not an expression, it produces: \%arg_lua" + ..hint "\ + ..Check the implementation of (\(%tok.stub)) to see if it is actually meant to produce an expression." + ..else: + report compile error at %tok "\ + ..Can't use this as an argument to (\%stub), since it's not an expression, it produces: \%arg_lua" + + assume (%arg_lua != %lua) or barf "Huh? \%tree .\%i = \%tok -> \%arg_lua" + %args::add %arg_lua + + %lua::add %args joined with ", " + %lua::add ")" + return %lua + + "EscapedNomsu": + %lua = (a Lua Buffer with {source:%tree, is_value:yes, bits:["a_Syntax_Tree_with{type=", quote %tree.(1).type]}) + set {%needs_comma:no, %i:1} + (% as shmua) means: + if (% is a "Lua number"): return "\%" + if (% is a "Syntax Tree"): + return (% compiled with %compile_actions) + if (% is text): return (quote %) + return (%::as lua) + + for %k = %v in (((%tree.(1).type == "EscapedNomsu") and %tree) or %tree.1): + %lua::add ", " + if: + (%k == %i): %i += 1 + ((%k is text) and (%k::is a lua identifier)): + %lua::add [%k, "= "] + else: + %lua::add ["[", % as shmua, "]= "] + + if (%k == "source"): + %lua::add (quote "\%v") + ..else: + %lua::add (%v as shmua) + + %lua::add "}" + return %lua + + "Block": + %lua = (a Lua Buffer with {source:%tree}) + %lua::add (..) + ((%line compiled with %compile_actions)::as statements) for %line in %tree + ..joined with "\n" + + return %lua + + "Text": + %lua = (a Lua Buffer with {source:%tree}) + %lua_bits = [] + %string_buffer = "" + for % in %tree: + if (% is text): + %string_buffer = "\%string_buffer\%" + do next % + + if (%string_buffer != ""): + %lua_bits::add (%string_buffer::as lua) + %string_buffer = "" + + %bit_lua = (% compiled with %compile_actions) + unless %bit_lua.is_value: + report compile error at % "\ + ..Can't use this as a string interpolation value, since it doesn't have a value." + + if (%.type != "Text"): + %bit_lua = (a Lua Buffer with {source:%, is_value:yes, bits:["tostring(", %bit_lua, ")"]}) + %lua_bits::add %bit_lua + + if ((%string_buffer != "") or ((size of %lua_bits) == 0)): + %lua_bits::add (%string_buffer::as lua) + %lua::add %lua_bits joined with ".." + if ((size of %lua_bits) > 1): %lua::parenthesize + return %lua + + "List": + %lua = (a Lua Buffer with {source:%tree, is_value:yes, bits:["List{"]}) + %lua::add ((% compiled with %compile_actions) for % in %tree) joined with ", " or ",\n " + %lua::add "}" + return %lua + + "Dict": + %lua = (a Lua Buffer with {source:%tree, is_value:yes, bits:["Dict{"]}) + %lua::add ((% compiled with %compile_actions) for % in %tree) joined with ", " or ",\n " + %lua::add "}" + return %lua + + "DictEntry": + set {%key:%tree.1, %value:%tree.2} + %key_lua = (%key compiled with %compile_actions) + unless %key_lua.is_value: + report compile error at %tree.1 "\ + ..Can't use this as a dict key, since it's not an expression." + + %value_lua = (..) + (%value compiled with %compile_actions) if %value else (..) + a Lua Buffer with {source:%key, is_value:yes, bits:["true"]} + + unless %value_lua.is_value: + report compile error at %tree.2 "\ + ..Can't use this as a dict value, since it's not an expression." + + %key_str = ((%key_lua::text)::matching "^[\"']([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") + if: + %key_str: + return (a Lua Buffer with {source:%tree, bits:[%key_str, "=", %value_lua]}) + ((%key_lua::text).1 == "["): + # NOTE: this *must* use a space after the [ to avoid freaking out + Lua's parser if the inner expression is a long string. Lua + parses x[[[y]]] as x("[y]"), not as x["y"] + return (a Lua Buffer with {source:%tree, bits:["[ ", %key_lua, "]=", %value_lua]}) + + else: + return (a Lua Buffer with {source:%tree, bits:["[", %key_lua, "]=", %value_lua]}) + + "IndexChain": + %lua = (%tree.1 compiled with %compile_actions) + unless %lua.is_value: + report compile error at %tree.1 "\ + ..Can't index into this, since it's not an expression." + + %first_char = (%lua::text).1 + if (any of [%first_char == "{", %first_char == "\"", %first_char == "["]): + %lua::parenthesize + for %i in 2 to (size of %tree): + %key = %tree.%i + %key_lua = (%key compiled with %compile_actions) + unless %key_lua.is_value: + report compile error at %key "\ + ..Can't use this as an index, since it's not an expression." + + %key_lua_str = (%key_lua::text) + %lua_id = (%key_lua_str::matching "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") + if: + %lua_id: + %lua::add [".", %lua_id] + (%key_lua_str.1 == "["): + # NOTE: this *must* use a space after the [ to avoid freaking out + Lua's parser if the inner expression is a long string. Lua + parses x[[[y]]] as x("[y]"), not as x["y"] + %lua::add ["[ ", %key_lua, " ]"] + + else: + %lua::add ["[", %key_lua, "]"] + + return %lua + + "Number": + return (a Lua Buffer with {source:%tree, is_value:yes, bits:["\(%tree.1)"]}) + "Var": + return (a Lua Buffer with {source:%tree, is_value:yes, is_variable:yes, bits:[%tree.1::as lua id]}) + "FileChunks": + barf "\ + ..Can't convert FileChunks to a single block of lua, since each chunk's compilation depends on the earlier chunks" + + "Comment": + # TODO: de-implement? + return (a Lua Buffer with {source:%tree, bits:["-- \(%tree.1::with "\n" -> "\n-- ")"]}) + + "Error": + barf (%tree as a pretty error) + else: + barf "Unknown type: \(%tree.type)" diff --git a/nomnom/decompile.nom b/nomnom/decompile.nom new file mode 100644 index 0000000..11ec233 --- /dev/null +++ b/nomnom/decompile.nom @@ -0,0 +1,347 @@ +#!/usr/bin/env nomsu -V4.8.10 +# This file contains the code to convert syntax trees to Nomsu code +use "nomnom/code_obj.nom" +use "nomnom/parser.nom" + +# TODO: maybe re-implement the fancy coroutine checker that aborts early if nomsu gets too long +externally (%tree decompiled inline) means: + assume (%tree is a "Syntax Tree") + if %tree.type is: + "Action": + %nomsu = (a Nomsu Buffer with {source: %tree}) + if %tree.target: + %target_nomsu = (%tree.target decompiled inline) + if %tree.target.type is: + "Action" "Block": + %target_nomsu::parenthesize + + %nomsu::add [%target_nomsu, "::"] + + for %bit in %tree at %i: + if (%bit is text): + unless (..) + any of [..] + %i == 1, %tree.(%i - 1) isn't text, (%bit is a nomsu operator) == (%tree.(%i - 1) is a nomsu operator) + ..: %nomsu::add " " + + %nomsu::add %bit + ..else: + %arg_nomsu = (%bit decompiled inline) + unless ((%i == (size of %tree)) and (%bit.type == "Block")): + %nomsu::add " " + if ((%bit.type == "Action") or (%bit.type == "Block")): + %arg_nomsu::parenthesize + + %nomsu::add %arg_nomsu + + return %nomsu + + "EscapedNomsu": + %inner_nomsu = (%tree.1 decompiled inline) + unless (..) + any of [..] + %tree.(1).type == "List", %tree.(1).type == "Dict", %tree.(1).type == "Var" + ..: + %inner_nomsu::parenthesize + + %nomsu = (a Nomsu Buffer with {source: %tree, bits: ["\\", %inner_nomsu]}) + return %nomsu + + "Block": + %nomsu = (a Nomsu Buffer with {source: %tree, bits: [":"]}) + for %line in %tree at %i: + %nomsu::add [" " if (%i == 1) else "; ", %line decompiled inline] + return %nomsu + + "Text": + %nomsu = (a Nomsu Buffer with {source: %tree, bits: []}) + for %text in recursive %tree: + for %bit in %text at %i: + if (%bit is text): %nomsu::add %bit + ..else: + if %bit.type is: + "Text": + recurse %text on %bit + "Var": + %interp_nomsu = (%bit decompiled inline) + + # Make sure "...\(%x)y..." isn't confused with "...\(%xy)..." + # TODO: make this more robust against "...\%x\("y").." + if (..) + (%tree.(%i + 1) is text) and (..) + not (%tree.(%i + 1)::matches "^[ \n\t,.:;#(){}%[%]]") + ..: %interp_nomsu::parenthesize + %nomsu::add ["\\", %interp_nomsu] + + "List" "Dict": + %nomsu::add ["\\", %bit decompiled inline] + else: + %nomsu::add ["\\(", %bit decompiled inline, ")"] + + return (a Nomsu Buffer with {source: %tree, bits: ["\"", %nomsu, "\""]}) + + "List" "Dict": + %nomsu = (a Nomsu Buffer with {source: %tree, bits: ["[" if (%tree.type == "List") else "{"]}) + for %item in %tree at %i: + if (%i > 1): %nomsu::add ", " + %nomsu::add (%item decompiled inline) + + %nomsu::add ("]" if (%tree.type == "List") else "}") + return %nomsu + + "DictEntry": + set {%key:%tree.1, %value:%tree.2} + if (..) + all of [%key.type == "Text", (size of %key) == 1, %key.1 is a nomsu identifier] + ..: + %nomsu = (a Nomsu Buffer with {source: %key, bits: [%key.1]}) + ..else: + %nomsu = (%key decompiled inline) + + if (%key.type == "Action"): + %nomsu::parenthesize + if %value: + %nomsu::add ":" + %nomsu::add (%value decompiled inline) + + return %nomsu + + "IndexChain": + %nomsu = (a Nomsu Buffer with {source: %tree}) + for %bit in %tree at %i: + if (%i > 1): %nomsu::add "." + if (..) + all of [..] + %i > 1, %bit.type == "Text", (size of %bit) == 1, %bit.1 is text, %bit.1 is a nomsu identifier + ..: %nomsu::add %bit.1 + ..else: + %bit_nomsu = (%bit decompiled inline) + if (..) + any of [..] + %bit.type == "Action", %bit.type == "Block", %bit.type == "IndexChain" + (%bit.type == "Number") and (%i < (size of %tree)) + ..: + %bit_nomsu::parenthesize + + %nomsu::add %bit_nomsu + + return %nomsu + + "Number": + return (a Nomsu Buffer with {source: %tree, bits: [(%tree.1 as hex) if %tree.hex else "\(%tree.1)"]}) + "Var": + return (a Nomsu Buffer with {source: %tree, bits: ["%\(%tree.1)"]}) + "Comment": return (nil) + "FileChunks": + barf "Can't inline a FileChunks" + "Error": + barf "Can't compile errors" + else: + barf "Unknown type: \(%tree.type)" + +%MAX_LINE = 90 +externally (%tree decompiled) means: + %nomsu = (a Nomsu Buffer with {source: %tree}) + + # For concision: + (recurse on %t) means: + %space = (%MAX_LINE - (%nomsu::trailing line length)) + if (%space <= 0): + go to (Use Indented) + for %subtree in recursive %tree: + if %subtree.type is: + "Block": + if ((size of %subtree) > 1): + go to (Use Indented) + if ((size of "\(%subtree decompiled inline)") > 20): + go to (Use Indented) + + for %k = %v in %subtree: + if (%v is a "Syntax Tree"): + recurse %subtree on %v + + %inline_nomsu = (%t decompiled inline) + if (%inline_nomsu and ((size of "\%inline_nomsu") <= %space)): + return %inline_nomsu + === (Use Indented) === + %indented = (%t decompiled) + if (%t.type == "Action"): + %indented = (..) + a Nomsu Buffer with {source: %t, bits: ["(..)\n ", %indented]} + + return %indented + + if %tree.type is: + "FileChunks": + (%1 and %2 should clump) means: + if ((%1.type == "Action") and (%2.type == "Action")): + if (%1.stub == "use 1"): + return (%2.stub == "use 1") + if (%1.stub == "test 1"): return (yes) + if (%2.stub == "test 1"): return (no) + + return (not ((recurse on %1)::is multi-line)) + + for %chunk in %tree at %chunk_no: + if (%chunk_no > 1): + %nomsu::add "\n\n\("~"::* 80)\n\n" + if (%chunk.type == "Block"): + for %line in %chunk at %line_no: + if (%line_no > 1): + if (%chunk.(%line_no - 1) and %line should clump): %nomsu::add "\n" + ..else: %nomsu::add "\n\n" + + %nomsu::add (%line decompiled) + ..else: + %nomsu::add (%chunk decompiled) + + unless ("\%nomsu"::matches "\n$"): %nomsu::add "\n" + return %nomsu + + "Action": + %pos = %tree.source.start + %next_space = "" + if %tree.target: + %target_nomsu = (recurse on %tree.target) + if ((%tree.target.type == "Action") and (%target_nomsu::is one line)): + %target_nomsu::parenthesize + %nomsu::add %target_nomsu + %pos = %tree.target.source.stop + %next_space = ("\n..::" if (%target_nomsu::is multi-line) else "::") + + for %bit in %tree at %i: + if ((%next_space == " ") and ((%nomsu::trailing line length) > %MAX_LINE)): + %next_space = " \\\n" + + %nomsu::add %next_space + if (%bit is text): + unless (..) + all of [..] + %tree.(%i - 1) is text, (%tree.(%i - 1) is a nomsu operator) != (%bit is a nomsu operator) + ..: + %nomsu::add %next_space + + %nomsu::add %bit + %next_space = " " + do next %bit + + %bit_nomsu = (recurse on %bit) + if (%bit.type == "Comment"): %next_space = "\n" + ..else: + %next_space = (" " if (%bit_nomsu::is one line) else "\n..") + if (%bit.type == "Action"): + %bit_nomsu::parenthesize + + return %nomsu + + "EscapedNomsu": + %nomsu::add "\\" + %val_nomsu = (recurse on %tree.1) + if ((%tree.(1).type == "Action") and (%val_nomsu::is one line)): + %val_nomsu::parenthesize + %nomsu::add %val_nomsu + return %nomsu + + "Block": + for %line in %tree at %i: + if ((%i > 1) and (%line.type == "Comment")): %nomsu::add "\n" + %line_nomsu = (recurse on %line) + %nomsu::add + if (%i < (size of %tree)): + if ((%line_nomsu::number of lines) > 2): %nomsu::add "\n\n" + ..else: %nomsu::add "\n" + + return (..) + a Nomsu Buffer with {source: %tree, bits: [":\n ", %nomsu]} + + "Text": + # Multi-line text has more generous wrap margins + %max_line = ((1.5 * %MAX_LINE) rounded down) + %nomsu = (a Nomsu Buffer with {source: %tree}) + (add text from %tree) means: + for %bit in %tree at %i: + if (%bit is text): + # TODO: escape properly? + %bit = (escape text %bit) + for %line in (%bit::lines) at %j: + if: + (%j > 1): %nomsu::add "\n" + (((size of %line) > 10) and ((%nomsu::trailing line length) > %max_line)): + %nomsu::add "\\\n.." + + repeat while ((size of %line) > 0): + %space = (%max_line - (%nomsu::trailing line length)) + %split = (%line::position of "[%p%s]" after %space) + if ((not %split) or (%split > %space + 10)): + %split = (%space + 10) + if ((%line - %split) < 10): + %split = (size of %line) + set {%bite:%line.[1, %split], %line:%line.[%split + 1, -1]} + %nomsu::add %bite + if ((size of %line) > 0): + %nomsu::add "\\\n.." + if (%bit.type == "Text"): + add text from %bit + ..else: + %nomsu::add "\\" + %interp_nomsu = (recurse on %bit) + unless (%interp_nomsu::is multi-line): + if %bit.type is: + "Var": + if ((%tree.(%i+1) is text) and (not (%tree.(%i+1)::matches "^[ \n\t,.:#(){}[%]]"))): + %interp_nomsu::parenthesize + + "List" "Dict": + %interp_nomsu::parenthesize + + %nomsu::add %interp_nomsu + if (%interp_nomsu::is multi-line): %nomsu::add "\n.." + + add text from %tree + return (..) + a Nomsu Buffer with {source: %tree, bits: ["\"\\\n ..", %nomsu, "\""]} + + "List" "Dict": + if ((size of %tree) == 0): + %nomsu::add ("[]" if (%tree.type == "List") else "{}") + return %nomsu + + for %item in %tree at %i: + %item_nomsu = (%item decompiled inline) + if ((not %item_nomsu) or ((size of "\%item_nomsu") > %MAX_LINE)): + %item_nomsu = (recurse on %item_nomsu) + %nomsu::add %item_nomsu + if (%i < (size of %tree)): + if any of [..] + %item_nomsu::is multi-line, ((%nomsu::trailing line length) + (size of "\%item_nomsu")) >= %MAX_LINE + ..: %nomsu::add "\n" + ..else: %nomsu::add ", " + + return (..) + a Nomsu Buffer with {..} + source: %tree, bits: [..] + "[..]\n " if (%tree.type == "List") else "{..}\n ", %nomsu + + "DictEntry": + set {%key:%tree.1, %value:%tree.2} + if (..) + all of [%key.type == "Text", (size of %key) == 1, %key.1 is a nomsu identifier] + ..: %nomsu::add %key.1 + ..else: + %nomsu::add (%key decompiled inline) + + if ((%key.type == "Action") or (%key.type == "Block")): + %nomsu::parenthesize + %nomsu::add [": ", recurse on %value] + return %nomsu + + "Comment": + %nomsu::add ["#", %tree.1::with "\n" -> "\n "] + return %nomsu + + "IndexChain" "Number" "Var": + return (%tree decompiled inline) + "Error": + barf "Cannot decompile an error" + else: + barf "Unknown type: \(%tree.type)" diff --git a/nomnom/files.nom b/nomnom/files.nom new file mode 100644 index 0000000..352ddfa --- /dev/null +++ b/nomnom/files.nom @@ -0,0 +1,108 @@ +#!/usr/bin/env nomsu -V4.8.10 +# Some file utilities for searching for files recursively and using package.nomsupath +use "lib/os.nom" + +%_SPOOFED_FILES = {} +%_FILE_CACHE = ({} with fallback % -> %_SPOOFED_FILES.%) +%_BROWSE_CACHE = {} + +# Create a fake file and put it in the cache +externally (spoof file %filename %contents) means: + %_SPOOFED_FILES.%filename = %contents + return %contents + +# Read a file's contents +externally (read file %filename) means: + %contents = %_FILE_CACHE.%filename + if %contents: return %contents + if (%filename == "stdin"): + return (spoof file "stdin" (=lua "io.read('*a')")) + %file = (=lua "io.open(\%filename)") + unless %file: return (nil) + %contents = (call %file.read with [%file, "*a"]) + %file::close + %_FILE_CACHE.%filename = %contents + return %contents + +externally (%path sanitized) means: + %path = (%path::with "\\" -> "\\\\") + %path = (%path::with "`" -> "") + %path = (%path::with "\"" -> "\\\"") + %path = (%path::with "$" -> "") + %path = (%path::with "%.%." -> "\\..") + return %path + +try: + %lfs = (=lua "require('lfs')") +..and if it succeeds: + (filesystem has %filename) means: + %mode = (call %lfs.attributes with [%filename, "mode"]) + if %mode is: + "file" "directory" "link" "char device": return (yes) + else: return (no) + + externally (file %path exists) means: + if (any of [%_SPOOFED_FILES.%path, %path == "stdin", filesystem has %path]): + return (yes) + for %nomsupath in (%package.nomsupath::all matches of "[^;]+"): + if (filesystem has "\(%nomsupath)/\%path"): return (yes) + return (no) + + externally (files in %path) means: + unless %_BROWSE_CACHE.%path: + if (%_SPOOFED_FILES.%path or (%filename == "stdin")): + %_BROWSE_CACHE.%path = [%path] + ..else: + if (call %lfs.attributes with [%filename, "mode"]) is: + "file" "char device": + %_BROWSE_CACHE.%path = [%filename] + "directory" "link": + for %nomsupath in (%package.nomsupath::all matches of "[^;]+"): + %files = [] + for %member in (call %lfs.dir with ["\(%nomsupath)/\%filename"]): + if ((%member == ".") or (%member == "..")): do next %member + for % in (files in %member): %files::add % + + if ((size of %files) > 0): + %_BROWSE_CACHE.%path = %files + go to (Found Files) + + %_BROWSE_CACHE.%path = [] + + else: + %_BROWSE_CACHE.%path = [] + + === (Found Files) === + return %_BROWSE_CACHE.%filename +..or if it barfs: + # LFS not found! Fall back to shell commands, if available. + unless (sh> "find . -maxdepth 0"): + barf "\ + ..Could not find 'luafilesystem' module and couldn't run system command 'find' (this might happen on Windows). Please install \ + ..'luafilesystem' (which can be found at \(..) + "https://github.com/spacewander/luafilesystem" if %jit else "\ + ..https://github.com/keplerproject/luafilesystem" + .. or obtained through `luarocks install luafilesystem`)" + + externally (file %path exists) means: + if (any of [%_SPOOFED_FILES.%path, %path == "stdin", sh> "ls \(%path sanitized)"]) \ + ..: return (yes) + for %nomsupath in (%package.nomsupath::all matches of "[^;]+"): + if (sh> "ls \(%nomsupath)/\%path"): return (yes) + return (no) + + externally (files in %path) means: + unless %_BROWSE_CACHE.%path: + if %_SPOOFED_FILES.%path: + %_BROWSE_CACHE.%path = [%_SPOOFED_FILES.%path] + ..else: + for %nomsupath in (%package.nomsupath::all matches of "[^;]+"): + %files = (sh> "find -L '\(%path)' -not -path '*/\\.*' -type f'") + if %files: + %_BROWSE_CACHE.%path = (%files::lines) + go to (Found Files) + + %_BROWSE_CACHE.%path = [] + + === (Found Files) === + return %_BROWSE_CACHE.%path diff --git a/nomnom/parser.nom b/nomnom/parser.nom new file mode 100644 index 0000000..fe237e8 --- /dev/null +++ b/nomnom/parser.nom @@ -0,0 +1,91 @@ +#!/usr/bin/env nomsu -V4.8.10 +# This file contains the parser, which converts text into abstract syntax trees +#use "nomonom/ast.nom" +%lpeg = (=lua "require('lpeg')") +%re = (=lua "require('re')") +call %lpeg.setmaxstack with [20000] +set {..} + ((P 1)'s meaning):%lpeg.P, ((R 1)'s meaning):%lpeg.R + ((Carg 1)'s meaning):%lpeg.Carg, ((S 1)'s meaning):%lpeg.S + ((Cc 1)'s meaning):%lpeg.Cc, ((lpeg re pattern 1)'s meaning):%re.compile + ((lpeg re pattern 1 using 2)'s meaning):%re.compile + ((lpeg pattern 1's match of 2)'s meaning):%lpeg.match + ((lpeg pattern 1's match of 2 with 3)'s meaning): (..) + [%1, %2, %3] -> (call %lpeg.match with [%1, %2, nil, %3]) + +%source_code_for_tree = {} +%defs = (..) + {..} + nl: (P "\r")^(-1) * (P "\n") + tab: P "\t" + tonumber: %tonumber + tochar: %string.char + unpack: %unpack + nil: Cc (nil) + userdata: Carg 1 + utf8_char: (..) + (R "\194\223")*(R "\128\191") + + ..(R "\224\239")*(R "\128\191")*(R "\128\191") + + ..(R "\240\244")*(R "\128\191")*(R "\128\191")*(R "\128\191") + + Tree: [%t, %userdata] ->: + %source = (..) + Source {filename:%userdata.filename, start:%t.start, stop:%t.stop} + set {%t.start: nil, %t.stop: nil, %t.source: %source} + %t = (a Syntax Tree with %t) + (Syntax Tree).source_code_for_tree.%t = %userdata.source + return %t + ..with fallback %key ->: + if: + (%key::matches "^ascii_(%d+)$"): + %i = (%key::matching "^ascii_(%d+)$") + return (call %string.char with [%i as a number]) + + (%key::matches "^number_(%d+)$"): + %i = (%key::matching "^number_(%d+)$") + return (Cc (%i as a number)) + +%id_patt = (..) + ((P "") - (R "09")) * (..) + (%defs.utf8_char + (R "az") + (R "AZ") + (P "_") + (R "09")) ^ 1 * -1 + +%operator_patt = ((S "'`~!@$^&*+=|<>?/-") ^ 1 * -1) +externally [%text is a nomsu id, %text is a nomsu identifier] all mean (..) + lpeg pattern %id_patt's match of %text + +externally (%text is a nomsu operator) means (..) + lpeg pattern %operator_patt's match of %text + +%peg_tidier = (..) + lpeg re pattern "\ + ..file <- %nl* {~ (def/comment) (%nl+ (def/comment))* %nl* ~} + def <- anon_def / captured_def + anon_def <- + ({ident} (" "*) ":" {[^%nl]* (%nl+ " "+ [^%nl]*)*}) + -> "%1 <- %2" + captured_def <- + ({ident} (" "*) "(" {ident} ")" (" "*) ":" {[^%nl]* (%nl+ " "+ [^%nl]*)*}) + -> "%1 <- ({| {:start:{}:} %3 {:stop:{}:} {:type: (''->'%2') :} |} %%userdata) -> Tree" + ident <- [a-zA-Z_][a-zA-Z0-9_]* + comment <- "--" [^%nl]* + " + +externally (make parser from %peg) means (make parser from %peg using (nil)) +externally (make parser from %peg using %make_tree) means: + %peg = (lpeg pattern %peg_tidier's match of %peg) + %peg = (lpeg re pattern %peg using %defs) + (%input from %filename parsed) means: + %input = "\%input" + %tree_mt = {__index:{source:%input, filename:%filename}} + %userdata = {..} + make_tree:%make_tree or ([%] -> (: set %'s metatable to %tree_mt; return %)) + filename:%filename, source:%input + + %tree = (lpeg pattern %peg's match of %input with %userdata) + assume %tree or barf "\ + ..File \%filename failed to parse: + \%input" + + return %tree + + return ((1 from 2 parsed)'s meaning) diff --git a/nomnom/pretty_errors.nom b/nomnom/pretty_errors.nom new file mode 100644 index 0000000..db4be5a --- /dev/null +++ b/nomnom/pretty_errors.nom @@ -0,0 +1,85 @@ +#!/usr/bin/env nomsu -V4.8.10 +# This file has code for converting errors to user-friendly format, with colors, + line numbers, code excerpts, and so on. +(visible size of %text) means: + return (size of (%text::with "\027%[[0-9;]*m" -> "")) + +(%text boxed) means: + %max_line = (max of ((visible size of %line) for %line in (%text::lines))) + %ret = (..) + "\n\%text"::with "\n([^\n]*)" -> (..) + [%] -> (..) + "\n\%\(" "::* (%max_line - (visible size of %))) \027[0m" + return %ret.[2,-1] + +%CONTEXT = 2 +externally (pretty %title error at %tree %err hint %hint) means: + %source_code = (%tree::get source code) + %start = %tree.source.start + %stop = %tree.source.stop + %filename = (%tree.source.filename or "???") + %err_line = (%source_code::line at %start) + %err_linenum = (%source_code::line number at %start) + %err_linepos = (%source_code::line position at %start) + + # TODO: better handle multi-line errors + %err_size = (min of [%stop - %start, (size of %err_line) - %err_linepos + 1]) + %nl_indicator = (" " if (%err_linepos > (size of %err_line)) else "") + %fmt_str = " %\(size of "\(%err_linenum + %CONTEXT)")d|" + (num %i) means (%fmt_str::formatted with %i) + %linenum_size = (size of (num 0)) + %pointer = "\(" "::* (%err_linepos + %linenum_size - 1))" + if (%err_size >= 2): + %pointer += "╚\("═"::* (%err_size - 2))╝" + ..else: %pointer += "⬆" + + %err_msg = "\ + ..\027[33;41;1m\(%title or "Error") at \%filename:\%err_linenum\027[0m" + for %i in (%err_linenum - %CONTEXT) to (%err_linenum - 1): + %line = (%source_code::line %i) + if %line: + %err_msg += "\n\027[2m\(num %i)\027[0m\(%line)\027[0m" + + if %err_line: + %before = %err_line.[1, %err_linepos - 1] + %during = %err_line.[%err_linepos, %err_linepos + %err_size - 1] + %after = %err_line.[%err_linepos + %err_size, -1] + %err_line = "\027[0m\(%before)\027[41;30m\%during\(%nl_indicator)\027[0m\%after" + %err_msg += "\n\027[2m\(num %err_linenum)\(%err_line)\027[0m" + + %err_linenum_end = (%source_code::line number at %stop) + %err_linepos_end = (%source_code::line position at %stop) + %err_linenum_end or= %err_linenum + if (%err_linenum_end == %err_linenum): + %err_msg += "\n\%pointer" + ..else: + for %i in (%err_linenum + 1) to %err_linenum_end: + %line = (%source_code::line %i) + if %line: + if (%i == %err_linenum_end): + %during = %line.[1, %err_linepos_end - 1] + %after = %line.[%err_linepos_end, -1] + %err_msg += "\n\027[2m\(num %i)\027[0;41;30m\(%during)\027[0m\%after" + ..else: + %err_msg += "\n\027[2m\(num %i)\027[0;41;30m\(%line)\027[0m" + + %box_width = 70 + %err_text = "\ + ..\027[47;31;1m\((" \%err"::wrapped to %box_width)::with "\n" -> "\n\027[47;31;1m ")" + if %hint: + %err_text += "\n\027[47;30m\((" Suggestion: \(%hint)"::wrapped to %box_width)::with "\n" -> "\n\027[47;30m ")" + %err_msg += "\n\027[33;1m \((%err_text boxed)::with "\n" -> "\n ")" + + %err_msg += "\n\027[33;1m \((%err_text boxed)::with "\n" -> "\n ")" + for %i in (%err_linenum_end + 1) to (%err_linenum_end + %CONTEXT): + %line = (%source_code::line %i) + if %line: + %err_msg += "\n\027[2m\(num %i)\027[0m\(%line)\027[0m" + + return %err_msg + +externally (pretty %title error at %tree %err) means (..) + pretty %title error at %tree %err (nil) + +externally (%err_tree as a pretty error) means (..) + pretty %err_tree.title error at %err_tree %err_tree.error hint %err_tree.hint diff --git a/nomnom/source.nom b/nomnom/source.nom new file mode 100644 index 0000000..c36216f --- /dev/null +++ b/nomnom/source.nom @@ -0,0 +1,54 @@ +#!/usr/bin/env nomsu -V4.8.10 +use "lib/object.nom" + +object (Source): + externally (Source from text %text) means: + %match = (%text::matching groups "^@(.-)%[(%d+):(%d+)%]$") + set {%filename:%match.1, %start:%match.2, %stop:%match.3} + unless %filename: + %match = (%text::matching groups "^@(.-)%[(%d+)%]$") + set {%filename:%match.1, %start:%match.2} + + return (..) + Source {filename:%filename, start:(%start or 1) as number, stop:%stop as number} + + my action [as text] "\ + ..@\(%me.filename)[\(%me.start)\(":\(%me.stop)" if %me.stop else "")]" + my action [as lua] "\ + ..Source{filename=\(%me.filename::as lua), start=\(%me.start)\(..) + ", stop=\(%me.stop)" if %stop else "" + ..}" + + my action [as nomsu] "\ + ..(Source {filename:\(%me.filename::as nomsu), start:\(%me.start)\(..) + ", stop:\(%me.stop)" if %stop else "" + ..})" + + my action [== %other] (..) + all of [..] + (%me's metatable) == (%other's metatable) + %me.filename == %other.filename + %me.start == %other.start + %me.stop == %other.stop + + my action [< %other]: + assume %me.filename == %other.filename + if (%start == %other.start): + return ((%me.stop or %me.start) < (%other.stop or %other.start)) + ..else: + return (%me.start < %other.start) + + my action [<= %other]: + assume %me.filename == %other.filename + if (%start == %other.start): + return ((%me.stop or %me.start) <= (%other.stop or %other.start)) + ..else: + return (%me.start <= %other.start) + + my action [+ %offset]: + if ((type of %me) == "number"): + set {%me:%offset, %offset:%me} + ..else: + assume (type of %offset) == "number" + + return (Source {filename:%me.filename, start:%me.start + %offset, stop:%me.stop}) diff --git a/nomsu.4.peg b/nomsu.4.peg index 32a170f..a3677c6 100644 --- a/nomsu.4.peg +++ b/nomsu.4.peg @@ -8,6 +8,8 @@ file: shebang: "#!" (!"nomsu" [^%nl])* "nomsu" ws+ "-V" ws* {:version: [0-9.]+ :} [^%nl]* +eof: !. + file_chunks (FileChunks): {:curr_indent: ' '* :} shebang? comment? blank_lines? @@ -71,7 +73,7 @@ tab_error (Error): section_division: ("~")^+3 eol inline_block: - "(" ws* inline_block ws* ")" / raw_inline_block + "(" ws* inline_block ws* (eof / ")") / raw_inline_block raw_inline_block (Block): (!"::") ":" ws* ((inline_statement (ws* ";" ws* inline_statement)*) / !(eol nl_indent)) indented_block (Block): @@ -89,7 +91,7 @@ noindex_inline_expression: / ( "(" ws* (inline_action / inline_expression) ws* (ws* ',' ws* (inline_action / inline_expression) ws*)* - (")" / missing_paren_err / unexpected_code) + (")" / eof / missing_paren_err / unexpected_code) ) inline_expression: index_chain / noindex_inline_expression indented_expression: @@ -119,9 +121,9 @@ inline_action (Action): inline_arg: inline_expression / inline_block action (Action): !section_division - ({:target: arg :} (eol nl_nodent "..")? ws* "::" (eol nl_nodent "..")? ws*)? - ( (arg ((eol nl_nodent "..")? ws* (arg / word))+) - / (word ((eol nl_nodent "..")? ws* (arg / word))*)) + ({:target: arg :} ((ws* "\")? eol nl_nodent "..")? ws* "::" ((ws* "\")? eol nl_nodent "..")? ws*)? + ( (arg (((ws* "\")? eol nl_nodent "..")? ws* (arg / word))+) + / (word (((ws* "\")? eol nl_nodent "..")? ws* (arg / word))*)) arg: expression / inline_block / indented_block word: !number { operator_char+ / ident_char+ } @@ -130,7 +132,7 @@ text_word (Text): word inline_text (Text): !(indented_text) - '"' _inline_text* ('"' / missing_quote_err / unexpected_code) + '"' _inline_text* ('"' / eof / missing_quote_err / unexpected_code) _inline_text: {~ (('\"' -> '"') / ('\\' -> '\') / escaped_char / text_char+)+ ~} / inline_text_interpolation / illegal_char @@ -140,7 +142,7 @@ inline_text_interpolation: / ("(" ws* (inline_action / inline_expression) ws* (ws* ',' ws* (inline_action / inline_expression) ws*)* - (")" / missing_paren_err / unexpected_code)) + (")" / eof / missing_paren_err / unexpected_code)) ) text_char: %utf8_char / !["\] %print / %tab @@ -156,7 +158,7 @@ indented_text (Text): (('\' %nl+ {:curr_indent: indent :} ('..')?) / disallowed_interpolation? {%nl+} {:curr_indent: indent :}) (indented_plain_text / text_interpolation / illegal_char / {~ %nl+ (=curr_indent -> "") ~})* - ('"' eol / missing_quote_err) + ('"' eol / eof / missing_quote_err) {:curr_indent: %nil :} -- Tracking text-lines-within-indented-text as separate objects allows for better debugging line info indented_plain_text (Text): @@ -180,7 +182,7 @@ inline_list (List): !('[..]') "[" ws* (inline_list_item (ws* ',' ws* inline_list_item)* (ws* ',')?)? ws* - ("]" / (","? (missing_bracket_error / unexpected_code))) + ("]" / eof / (","? (missing_bracket_error / unexpected_code))) indented_list (List): "[..]" eol nl_indent list_line (nl_nodent list_line)* @@ -195,7 +197,7 @@ inline_dict (Dict): !('{..}') "{" ws* (inline_dict_entry (ws* ',' ws* inline_dict_entry)*)? ws* - ("}" / (","? (missing_brace_error / unexpected_code))) + ("}" / eof / (","? (missing_brace_error / unexpected_code))) indented_dict (Dict): "{..}" eol nl_indent dict_line (nl_nodent dict_line)* @@ -91,8 +91,6 @@ do local _obj_0 = require("code_obj") NomsuCode, LuaCode, Source = _obj_0.NomsuCode, _obj_0.LuaCode, _obj_0.Source end -local repr -repr = require("utils").repr if not arg or debug.getinfo(2).func == require then return NomsuCompiler end @@ -135,8 +133,7 @@ end local nomsu = NomsuCompiler nomsu.environment.arg = NomsuCompiler.environment._List(args.nomsu_args) if args.version then - nomsu:run([[use "core" -say (Nomsu version)]]) + nomsu:run([[(: use "core"; say (Nomsu version))]]) os.exit(EXIT_SUCCESS) end FILE_CACHE = setmetatable({ }, { @@ -185,11 +182,7 @@ run = function() return true end if not (args.no_core) then - for _, filename in Files.walk('core') do - if filename:match("%.nom$") then - nomsu:import(nomsu:run_file(filename)) - end - end + nomsu:import_file('core') end local get_file_and_source get_file_and_source = function(filename) @@ -282,8 +275,8 @@ run = function() if not (args.primary_file or args.exec_strings) then nomsu:run([[#!/usr/bin/env nomsu -V4 use "lib/consolecolor.nom" -action [quit, exit]: lua> "os.exit(0)" -action [help]: +[quit, exit] all mean: lua> "os.exit(0)" +(help) means: say "\ ..This is the Nomsu v\(Nomsu version) interactive console. You can type in Nomsu code here and hit 'enter' twice to run it. @@ -322,9 +315,13 @@ say "\ return Errhand.print_error(error_message) end local ret - ok, ret = xpcall(nomsu.run, err_hand, nomsu, buff, Source(pseudo_filename, 1, #buff)) + ok, ret = xpcall(nomsu.run, err_hand, nomsu, NomsuCode(Source(pseudo_filename, 1, #buff), buff)) if ok and ret ~= nil then - print("= " .. repr(ret)) + if type(ret) == 'number' then + print("= " .. tostring(ret)) + else + print("= " .. tostring(ret:as_nomsu())) + end elseif not ok then Errhand.print_error(ret) end @@ -48,7 +48,6 @@ Files = require "files" Errhand = require "error_handling" NomsuCompiler = require "nomsu_compiler" {:NomsuCode, :LuaCode, :Source} = require "code_obj" -{:repr} = require "utils" -- If this file was reached via require(), then just return the Nomsu compiler if not arg or debug.getinfo(2).func == require @@ -90,9 +89,7 @@ nomsu = NomsuCompiler nomsu.environment.arg = NomsuCompiler.environment._List(args.nomsu_args) if args.version - nomsu\run [[ -use "core" -say (Nomsu version)]] + nomsu\run [[(: use "core"; say (Nomsu version))]] os.exit(EXIT_SUCCESS) export FILE_CACHE @@ -124,9 +121,7 @@ run = -> return true unless args.no_core - for _,filename in Files.walk('core') - if filename\match "%.nom$" - nomsu\import(nomsu\run_file(filename)) + nomsu\import_file('core') get_file_and_source = (filename)-> local file, source @@ -186,8 +181,8 @@ run = -> nomsu\run [[ #!/usr/bin/env nomsu -V4 use "lib/consolecolor.nom" -action [quit, exit]: lua> "os.exit(0)" -action [help]: +[quit, exit] all mean: lua> "os.exit(0)" +(help) means: say "\ ..This is the Nomsu v\(Nomsu version) interactive console. You can type in Nomsu code here and hit 'enter' twice to run it. @@ -216,13 +211,18 @@ say "\ break -- Exit buff = table.concat(buff) + + -- TODO: support local variables pseudo_filename = "user input #"..repl_line Files.spoof(pseudo_filename, buff) err_hand = (error_message)-> Errhand.print_error error_message - ok, ret = xpcall(nomsu.run, err_hand, nomsu, buff, Source(pseudo_filename, 1, #buff)) + ok, ret = xpcall nomsu.run, err_hand, nomsu, NomsuCode(Source(pseudo_filename,1,#buff), buff) if ok and ret != nil - print "= "..repr(ret) + if type(ret) == 'number' + print "= #{ret}" + else + print "= #{ret\as_nomsu!}" elseif not ok Errhand.print_error ret diff --git a/nomsu_compiler.lua b/nomsu_compiler.lua index c3fc802..67e9621 100644 --- a/nomsu_compiler.lua +++ b/nomsu_compiler.lua @@ -4,8 +4,8 @@ R, P, S = lpeg.R, lpeg.P, lpeg.S local re = require('re') local utils = require('utils') local Files = require('files') -local repr, stringify, equivalent -repr, stringify, equivalent = utils.repr, utils.stringify, utils.equivalent +local stringify, equivalent +stringify, equivalent = utils.stringify, utils.equivalent local List, Dict, Text do local _obj_0 = require('containers') @@ -35,7 +35,7 @@ do local _obj_0 = require("code_obj") NomsuCode, LuaCode, Source = _obj_0.NomsuCode, _obj_0.LuaCode, _obj_0.Source end -local AST = require("syntax_tree") +local SyntaxTree = require("syntax_tree") local make_parser = require("parser") local pretty_error = require("pretty_errors") SOURCE_MAP = { } @@ -97,16 +97,14 @@ escape = function(s) end local make_tree make_tree = function(tree, userdata) - local cls = AST[tree.type] tree.source = Source(userdata.filename, tree.start, tree.stop) tree.start, tree.stop = nil, nil - tree.type = nil do local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #tree do local t = tree[_index_0] - if AST.is_syntax_tree(t, "Comment") then + if SyntaxTree:is_instance(t) and t.type == "Comment" then _accum_0[_len_0] = t _len_0 = _len_0 + 1 end @@ -117,34 +115,29 @@ make_tree = function(tree, userdata) tree.comments = nil end for i = #tree, 1, -1 do - if AST.is_syntax_tree(tree[i], "Comment") then + if SyntaxTree:is_instance(tree[i]) and tree[i].type == "Comment" then table.remove(tree, i) end end - tree = setmetatable(tree, cls) - cls.source_code_for_tree[tree] = userdata.source - if tree.__init then - tree:__init() - end + tree = SyntaxTree(tree) return tree end local Parsers = { } -local max_parser_version = 0 -for version = 1, 999 do - local found_version = false - for _, full_path in Files.walk("nomsu." .. tostring(version) .. ".peg") do - do - local peg_contents = Files.read(full_path) - if peg_contents then - found_version = true - max_parser_version = version - Parsers[version] = make_parser(peg_contents, make_tree) +local max_parser_version = 4 +for version = 1, max_parser_version do + local peg_file = io.open("nomsu." .. tostring(version) .. ".peg") + if not peg_file and package.nomsupath then + for path in package.nomsupath:gmatch("[^;]+") do + peg_file = io.open(path .. "/nomsu." .. tostring(version) .. ".peg") + if peg_file then + break end end end - if not (found_version) then - break - end + assert(peg_file, "could not find nomsu .peg file") + local peg_contents = peg_file:read('*a') + peg_file:close() + Parsers[version] = make_parser(peg_contents, make_tree) end local MAX_LINE = 80 local NomsuCompiler = setmetatable({ }, { @@ -154,13 +147,11 @@ local NomsuCompiler = setmetatable({ }, { }) local _anon_chunk = 0 do - NomsuCompiler.NOMSU_COMPILER_VERSION = 9 - NomsuCompiler.NOMSU_SYNTAX_VERSION = max_parser_version NomsuCompiler.can_optimize = function() return false end NomsuCompiler.environment = { - NOMSU_COMPILER_VERSION = 8, + NOMSU_COMPILER_VERSION = 11, NOMSU_SYNTAX_VERSION = max_parser_version, next = next, unpack = unpack, @@ -189,7 +180,7 @@ do assert = assert, dofile = dofile, loadstring = loadstring, - type = type, + lua_type_of = type, select = select, math = math, io = io, @@ -198,16 +189,14 @@ do ipairs = ipairs, _List = List, _Dict = Dict, - repr = repr, stringify = stringify, utils = utils, lpeg = lpeg, re = re, Files = Files, - AST = AST, - TESTS = Dict({ }, { - globals = Dict({ }) - }), + SyntaxTree = SyntaxTree, + TESTS = Dict({ }), + globals = Dict({ }), LuaCode = LuaCode, NomsuCode = NomsuCode, Source = Source, @@ -250,9 +239,6 @@ do if jit or _VERSION == "Lua 5.2" then NomsuCompiler.environment.bit = require("bitops") end - for k, v in pairs(AST) do - NomsuCompiler.environment[k] = v - end NomsuCompiler.fork = function(self) local f = setmetatable({ }, { __index = self @@ -293,7 +279,7 @@ do for k, v in pairs(t) do local _continue_0 = false repeat - if not (AST.is_syntax_tree(v)) then + if not (SyntaxTree:is_instance(v)) then _continue_0 = true break end @@ -390,7 +376,7 @@ do add_lua_string_bits = function(self, val_or_stmt, code) local cls_str = val_or_stmt == "value" and "LuaCode.Value(" or "LuaCode(" if code.type ~= "Text" then - return LuaCode.Value(code.source, cls_str, repr(tostring(code.source)), ", ", self:compile(code), ")") + return LuaCode.Value(code.source, cls_str, tostring(code.source):as_lua(), ", ", self:compile(code), ")") end local add_bit_lua add_bit_lua = function(lua, bit_lua) @@ -400,11 +386,11 @@ do end local operate_on_text operate_on_text = function(text) - local lua = LuaCode.Value(text.source, cls_str, repr(tostring(text.source))) + local lua = LuaCode.Value(text.source, cls_str, tostring(text.source):as_lua()) for _index_0 = 1, #text do local bit = text[_index_0] if type(bit) == "string" then - add_bit_lua(lua, repr(bit)) + add_bit_lua(lua, bit:as_lua()) elseif bit.type == "Text" then add_bit_lua(lua, operate_on_text(bit)) else @@ -420,7 +406,7 @@ do end return operate_on_text(code) end - local math_expression = re.compile([[ ([+-] " ")* [0-9]+ (" " [*/^+-] (" " [+-])* " " [0-9]+)+ !. ]]) + local math_expression = re.compile([[ (([*/^+-] / [0-9]+) " ")* [*/^+-] !. ]]) local compile_math_expression compile_math_expression = function(self, tree, ...) local lua = LuaCode.Value(tree.source) @@ -445,36 +431,36 @@ do end NomsuCompiler.environment.COMPILE_ACTIONS = setmetatable({ __imported = Dict({ }), - ["Lua 1"] = function(self, tree, code) + ["Lua"] = function(self, tree, code) return add_lua_string_bits(self, 'statements', code) end, - ["Lua value 1"] = function(self, tree, code) + ["Lua value"] = function(self, tree, code) return add_lua_string_bits(self, 'value', code) end, - ["lua > 1"] = function(self, tree, code) + ["lua >"] = function(self, tree, code) if code.type ~= "Text" then return LuaCode(tree.source, "nomsu:run_lua(", self:compile(code), ", nomsu);") end return add_lua_bits(self, "statements", code) end, - ["= lua 1"] = function(self, tree, code) + ["= lua"] = function(self, tree, code) if code.type ~= "Text" then return LuaCode.Value(tree.source, "nomsu:run_lua(", self:compile(code), ":as_statements('return '), nomsu)") end return add_lua_bits(self, "value", code) end, - ["use 1"] = function(self, tree, path) + ["use"] = function(self, tree, path) if path.type == 'Text' and #path == 1 and type(path[1]) == 'string' then - for _, f in Files.walk(path[1]) do - self:import(self:run_file(f)) + if not (self:import_file(path[1])) then + self:compile_error(tree, "Could not find anything to import for " .. tostring(path)) end end - return LuaCode(tree.source, "for i,f in Files.walk(", self:compile(path), ") do nomsu:import(nomsu:run_file(f)) end") + return LuaCode(tree.source, "nomsu:import_file(" .. tostring(self:compile(path)) .. ")") end, ["tests"] = function(self, tree) return LuaCode.Value(tree.source, "TESTS") end, - ["test 1"] = function(self, tree, body) + ["test"] = function(self, tree, body) local test_str = table.concat((function() local _accum_0 = { } local _len_0 = 1 @@ -485,13 +471,13 @@ do end return _accum_0 end)(), "\n") - return LuaCode(tree.source, "TESTS[" .. tostring(repr(tostring(tree.source))) .. "] = ", repr(test_str)) + return LuaCode(tree.source, "TESTS[" .. tostring(tostring(tree.source):as_lua()) .. "] = ", test_str:as_lua()) end, ["is jit"] = function(self, tree, code) return LuaCode.Value(tree.source, jit and "true" or "false") end, ["Lua version"] = function(self, tree, code) - return LuaCode.Value(tree.source, repr(_VERSION)) + return LuaCode.Value(tree.source, _VERSION:as_lua()) end, __parent = setmetatable({ }, { __index = function(self, key) @@ -531,6 +517,16 @@ do end end end + NomsuCompiler.import_file = function(self, path) + local found = false + for _, f in Files.walk(path) do + if match(f, "%.lua$") or match(f, "%.nom$") or match(f, "^/dev/fd/[012]$") then + found = true + self:import(self:run_file(f)) + end + end + return found + end NomsuCompiler.run = function(self, to_run, compile_actions) local source = to_run.source or Source(to_run, 1, #to_run) if type(source) == 'string' then @@ -540,7 +536,7 @@ do Files.spoof(source.filename, to_run) end local tree - if AST.is_syntax_tree(to_run) then + if SyntaxTree:is_instance(to_run) then tree = to_run else tree = self:parse(to_run, source) @@ -682,7 +678,7 @@ do local get_version = self[("Nomsu version"):as_lua_id()] if get_version then do - local upgrade = self[("1 upgraded from 2 to 3"):as_lua_id()] + local upgrade = self[("1 upgraded from 2 to"):as_lua_id()] if upgrade then tree = upgrade(tree, tree.version, get_version()) end @@ -693,38 +689,36 @@ do local _exp_0 = tree.type if "Action" == _exp_0 then local stub = tree.stub - do - local compile_action = compile_actions[stub] - if compile_action then - local args - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #tree do - local arg = tree[_index_0] - if type(arg) ~= "string" then - _accum_0[_len_0] = arg - _len_0 = _len_0 + 1 - end + local compile_action = compile_actions[stub] + if compile_action and not tree.target then + local args + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #tree do + local arg = tree[_index_0] + if type(arg) ~= "string" then + _accum_0[_len_0] = arg + _len_0 = _len_0 + 1 end - args = _accum_0 end - local ret = compile_action(self, tree, unpack(args)) - if ret == nil then + args = _accum_0 + end + local ret = compile_action(self, tree, unpack(args)) + if ret == nil 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) .. ") 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) .. ") 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.") + 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 AST.is_syntax_tree(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 - return self:compile(ret, compile_actions) - end - return ret + return self:compile(ret, compile_actions) end + return ret end local lua = LuaCode.Value(tree.source) if tree.target then @@ -749,9 +743,9 @@ do if tok.type == "Block" then self:compile_error(tok, "Can't compile action (" .. tostring(stub) .. ") with a Block as an argument.", "Maybe there should be a compile-time action with that name that isn't being found?") elseif tok.type == "Action" then - self:compile_error(tok, "Can't use this as an argument to (" .. tostring(stub) .. "), since it's not an expression, it produces: " .. tostring(repr(arg_lua)), "Check the implementation of (" .. tostring(tok.stub) .. ") to see if it is actually meant to produce an expression.") + self:compile_error(tok, "Can't use this as an argument to (" .. tostring(stub) .. "), since it's not an expression, it produces: " .. tostring(tostring(arg_lua)), "Check the implementation of (" .. tostring(tok.stub) .. ") to see if it is actually meant to produce an expression.") else - self:compile_error(tok, "Can't use this as an argument to (" .. tostring(stub) .. "), since it's not an expression, it produces: " .. tostring(repr(arg_lua))) + self:compile_error(tok, "Can't use this as an argument to (" .. tostring(stub) .. "), since it's not an expression, it produces: " .. tostring(tostring(arg_lua))) end end insert(args, arg_lua) @@ -765,9 +759,19 @@ do lua:append(")") return lua elseif "EscapedNomsu" == _exp_0 then - local lua = LuaCode.Value(tree.source, tree[1].type, "{") + local lua = LuaCode.Value(tree.source, "SyntaxTree{") local needs_comma, i = false, 1 - for k, v in pairs(AST.is_syntax_tree(tree[1], "EscapedNomsu") and tree or tree[1]) do + local as_lua + as_lua = function(x) + if type(x) == 'number' then + return tostring(x) + elseif SyntaxTree:is_instance(x) then + return self:compile(x, compile_actions) + else + return x:as_lua() + end + end + for k, v in pairs((SyntaxTree:is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1]) do if needs_comma then lua:append(", ") else @@ -778,12 +782,12 @@ do elseif type(k) == 'string' and match(k, "[_a-zA-Z][_a-zA-Z0-9]*") then lua:append(k, "= ") else - lua:append("[", (AST.is_syntax_tree(k) and self:compile(k, compile_actions) or repr(k)), "]= ") + lua:append("[", as_lua(k), "]= ") end if k == "source" then - lua:append(repr(tostring(v))) + lua:append(tostring(v):as_lua()) else - lua:append(AST.is_syntax_tree(v) and self:compile(v, compile_actions) or repr(v)) + lua:append(as_lua(v)) end end lua:append("}") @@ -854,7 +858,7 @@ do if #lua.bits > 0 then lua:append("..") end - lua:append(repr(string_buffer)) + lua:append(string_buffer:as_lua()) string_buffer = "" end local bit_lua = self:compile(bit, compile_actions) @@ -880,7 +884,7 @@ do if #lua.bits > 0 then lua:append("..") end - lua:append(repr(string_buffer)) + lua:append(string_buffer:as_lua()) end if #lua.bits > 1 then lua:parenthesize() @@ -925,7 +929,7 @@ do self:compile_error(tree[2], "Can't use this as a dict value, since it's not an expression.") end local key_str = match(tostring(key_lua), [=[^["']([a-zA-Z_][a-zA-Z0-9_]*)['"]$]=]) - if key_str then + if key_str and key_str:is_lua_id() then return LuaCode(tree.source, key_str, "=", value_lua) elseif sub(tostring(key_lua), 1, 1) == "[" then return LuaCode(tree.source, "[ ", key_lua, "]=", value_lua) @@ -948,15 +952,13 @@ do self:compile_error(key, "Can't use this as an index, since it's not an expression.") end local key_lua_str = tostring(key_lua) - do - local lua_id = match(key_lua_str, "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") - if lua_id then - lua:append("." .. tostring(lua_id)) - elseif sub(key_lua_str, 1, 1) == '[' then - lua:append("[ ", key_lua, " ]") - else - lua:append("[", key_lua, "]") - end + local lua_id = match(key_lua_str, "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") + if lua_id and lua_id:is_lua_id() then + lua:append("." .. tostring(lua_id)) + elseif sub(key_lua_str, 1, 1) == '[' then + lua:append("[ ", key_lua, " ]") + else + lua:append("[", key_lua, "]") end end return lua @@ -1004,7 +1006,11 @@ do elseif "Action" == _exp_0 then local nomsu = NomsuCode(tree.source) if tree.target then - nomsu:append(self:tree_to_inline_nomsu(tree.target), "::") + local inline_target = self:tree_to_inline_nomsu(tree.target) + if tree.target.type == "Action" then + inline_target:parenthesize() + end + nomsu:append(inline_target, "::") end for i, bit in ipairs(tree) do if type(bit) == "string" then @@ -1165,7 +1171,7 @@ do local _list_0 = t for _index_0 = 1, #_list_0 do local x = _list_0[_index_0] - if AST.is_syntax_tree(x) then + if SyntaxTree:is_instance(x) then find_comments(x) end end @@ -1272,13 +1278,13 @@ do local should_clump should_clump = function(prev_line, line) if prev_line.type == "Action" and line.type == "Action" then - if prev_line.stub == "use 1" then - return line.stub == "use 1" + if prev_line.stub == "use" then + return line.stub == "use" end - if prev_line.stub == "test 1" then + if prev_line.stub == "test" then return true end - if line.stub == "test 1" then + if line.stub == "test" then return false end end diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon index f5cc7d2..a1194d9 100644 --- a/nomsu_compiler.moon +++ b/nomsu_compiler.moon @@ -14,7 +14,7 @@ lpeg = require 'lpeg' re = require 're' utils = require 'utils' Files = require 'files' -{:repr, :stringify, :equivalent} = utils +{:stringify, :equivalent} = utils {:List, :Dict, :Text} = require 'containers' export colors, colored colors = require 'consolecolors' @@ -23,7 +23,7 @@ colored = setmetatable({}, {__index:(_,color)-> ((msg)-> colors[color]..tostring unpack or= table.unpack {:match, :sub, :gsub, :format, :byte, :find} = string {:NomsuCode, :LuaCode, :Source} = require "code_obj" -AST = require "syntax_tree" +SyntaxTree = require "syntax_tree" make_parser = require("parser") pretty_error = require("pretty_errors") -- Mapping from source string (e.g. "@core/metaprogramming.nom[1:100]") to a mapping @@ -62,53 +62,49 @@ escape = (s)-> -- Re-implement nomsu-to-lua comment translation? make_tree = (tree, userdata)-> - cls = AST[tree.type] tree.source = Source(userdata.filename, tree.start, tree.stop) tree.start, tree.stop = nil, nil - tree.type = nil - tree.comments = [t for t in *tree when AST.is_syntax_tree(t, "Comment")] + tree.comments = [t for t in *tree when SyntaxTree\is_instance(t) and t.type == "Comment"] if #tree.comments == 0 then tree.comments = nil for i=#tree,1,-1 - if AST.is_syntax_tree(tree[i], "Comment") + if SyntaxTree\is_instance(tree[i]) and tree[i].type == "Comment" table.remove(tree, i) - tree = setmetatable(tree, cls) - cls.source_code_for_tree[tree] = userdata.source - if tree.__init then tree\__init! + tree = SyntaxTree(tree) return tree Parsers = {} -max_parser_version = 0 -for version=1,999 - found_version = false - for _, full_path in Files.walk("nomsu.#{version}.peg") - if peg_contents = Files.read(full_path) - found_version = true - max_parser_version = version - Parsers[version] = make_parser(peg_contents, make_tree) - break unless found_version +max_parser_version = 4 +for version=1,max_parser_version + peg_file = io.open("nomsu.#{version}.peg") + if not peg_file and package.nomsupath + for path in package.nomsupath\gmatch("[^;]+") + peg_file = io.open(path.."/nomsu.#{version}.peg") + break if peg_file + assert(peg_file, "could not find nomsu .peg file") + peg_contents = peg_file\read('*a') + peg_file\close! + Parsers[version] = make_parser(peg_contents, make_tree) MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value NomsuCompiler = setmetatable {}, {__tostring: => "Nomsu"} _anon_chunk = 0 with NomsuCompiler - .NOMSU_COMPILER_VERSION = 9 - .NOMSU_SYNTAX_VERSION = max_parser_version .can_optimize = -> false -- Discretionary/convenience stuff .environment = { - NOMSU_COMPILER_VERSION: 8, NOMSU_SYNTAX_VERSION: max_parser_version + NOMSU_COMPILER_VERSION: 11, NOMSU_SYNTAX_VERSION: max_parser_version -- Lua stuff: :next, :unpack, :setmetatable, :coroutine, :rawequal, :getmetatable, :pcall, :error, :package, :os, :require, :tonumber, :tostring, :string, :xpcall, :module, :print, :loadfile, :rawset, :_VERSION, :collectgarbage, :rawget, :rawlen, - :table, :assert, :dofile, :loadstring, :type, :select, :math, :io, :load, + :table, :assert, :dofile, :loadstring, lua_type_of:type, :select, :math, :io, :load, :pairs, :ipairs, -- Nomsu types: _List:List, _Dict:Dict, -- Utilities and misc. - repr:repr, stringify:stringify, utils:utils, lpeg:lpeg, re:re, Files:Files, - :AST, TESTS: Dict{}, globals: Dict{} + stringify:stringify, utils:utils, lpeg:lpeg, re:re, Files:Files, + :SyntaxTree, TESTS: Dict({}), globals: Dict({}), :LuaCode, :NomsuCode, :Source nomsu:NomsuCompiler __imported: Dict{} @@ -130,7 +126,6 @@ with NomsuCompiler return ipairs(x) if jit or _VERSION == "Lua 5.2" .environment.bit = require("bitops") - for k,v in pairs(AST) do .environment[k] = v .fork = => f = setmetatable({}, {__index:@}) @@ -160,7 +155,7 @@ with NomsuCompiler coroutine.yield t else for k,v in pairs(t) - continue unless AST.is_syntax_tree(v) + continue unless SyntaxTree\is_instance(v) find_errors(v) errs = [err for err in coroutine.wrap(-> find_errors(tree))] @@ -205,16 +200,16 @@ with NomsuCompiler add_lua_string_bits = (val_or_stmt, code)=> cls_str = val_or_stmt == "value" and "LuaCode.Value(" or "LuaCode(" if code.type != "Text" - return LuaCode.Value(code.source, cls_str, repr(tostring(code.source)), ", ", @compile(code), ")") + return LuaCode.Value(code.source, cls_str, tostring(code.source)\as_lua!, ", ", @compile(code), ")") add_bit_lua = (lua, bit_lua)-> bit_leading_len = #(bit_lua\match("^[^\n]*")) lua\append(lua\trailing_line_len! + bit_leading_len > MAX_LINE and ",\n " or ", ") lua\append(bit_lua) operate_on_text = (text)-> - lua = LuaCode.Value(text.source, cls_str, repr(tostring(text.source))) + lua = LuaCode.Value(text.source, cls_str, tostring(text.source)\as_lua!) for bit in *text if type(bit) == "string" - add_bit_lua(lua, repr(bit)) + add_bit_lua(lua, bit\as_lua!) elseif bit.type == "Text" add_bit_lua(lua, operate_on_text(bit)) else @@ -230,7 +225,7 @@ with NomsuCompiler -- This is a bit of a hack, but this code handles arbitrarily complex -- math expressions like 2*x + 3^2 without having to define a single -- action for every possibility. - math_expression = re.compile [[ ([+-] " ")* [0-9]+ (" " [*/^+-] (" " [+-])* " " [0-9]+)+ !. ]] + math_expression = re.compile [[ (([*/^+-] / [0-9]+) " ")* [*/^+-] !. ]] compile_math_expression = (tree, ...)=> lua = LuaCode.Value(tree.source) for i,tok in ipairs tree @@ -247,39 +242,38 @@ with NomsuCompiler return lua .environment.COMPILE_ACTIONS = setmetatable({ __imported: Dict{} - ["Lua 1"]: (tree, code)=> + ["Lua"]: (tree, code)=> return add_lua_string_bits(@, 'statements', code) - ["Lua value 1"]: (tree, code)=> + ["Lua value"]: (tree, code)=> return add_lua_string_bits(@, 'value', code) - ["lua > 1"]: (tree, code)=> + ["lua >"]: (tree, code)=> if code.type != "Text" return LuaCode tree.source, "nomsu:run_lua(", @compile(code), ", nomsu);" return add_lua_bits(@, "statements", code) - ["= lua 1"]: (tree, code)=> + ["= lua"]: (tree, code)=> if code.type != "Text" return LuaCode.Value tree.source, "nomsu:run_lua(", @compile(code), ":as_statements('return '), nomsu)" return add_lua_bits(@, "value", code) - ["use 1"]: (tree, path)=> + ["use"]: (tree, path)=> if path.type == 'Text' and #path == 1 and type(path[1]) == 'string' - for _,f in Files.walk(path[1]) - @import(@run_file(f)) - - return LuaCode(tree.source, "for i,f in Files.walk(", @compile(path), ") do nomsu:import(nomsu:run_file(f)) end") + unless @import_file(path[1]) + @compile_error tree, "Could not find anything to import for #{path}" + return LuaCode(tree.source, "nomsu:import_file(#{@compile(path)})") ["tests"]: (tree)=> LuaCode.Value(tree.source, "TESTS") - ["test 1"]: (tree, body)=> + ["test"]: (tree, body)=> test_str = table.concat [tostring(@tree_to_nomsu(line)) for line in *body], "\n" - LuaCode tree.source, "TESTS[#{repr(tostring(tree.source))}] = ", repr(test_str) + LuaCode tree.source, "TESTS[#{tostring(tree.source)\as_lua!}] = ", test_str\as_lua! ["is jit"]: (tree, code)=> return LuaCode.Value(tree.source, jit and "true" or "false") ["Lua version"]: (tree, code)=> - return LuaCode.Value(tree.source, repr(_VERSION)) + return LuaCode.Value(tree.source, _VERSION\as_lua!) __parent: setmetatable({}, { __index: (key)=> @@ -296,11 +290,19 @@ with NomsuCompiler continue if k == "__imported" or k == "__parent" @environment.COMPILE_ACTIONS.__imported[k] or= v + .import_file = (path)=> + found = false + for _,f in Files.walk(path) + if match(f, "%.lua$") or match(f, "%.nom$") or match(f, "^/dev/fd/[012]$") + found = true + @import(@run_file(f)) + return found + .run = (to_run, compile_actions)=> source = to_run.source or Source(to_run, 1, #to_run) if type(source) == 'string' then source = Source\from_string(source) if not Files.read(source.filename) then Files.spoof(source.filename, to_run) - tree = if AST.is_syntax_tree(to_run) then to_run else @parse(to_run, source) + tree = if SyntaxTree\is_instance(to_run) then to_run else @parse(to_run, source) if tree == nil -- Happens if pattern matches, but there are no captures, e.g. an empty string return nil if tree.type != "FileChunks" @@ -396,12 +398,13 @@ with NomsuCompiler compile_actions or= @environment.COMPILE_ACTIONS if tree.version if get_version = @[("Nomsu version")\as_lua_id!] - if upgrade = @[("1 upgraded from 2 to 3")\as_lua_id!] + if upgrade = @[("1 upgraded from 2 to")\as_lua_id!] tree = upgrade(tree, tree.version, get_version!) switch tree.type when "Action" stub = tree.stub - if compile_action = compile_actions[stub] + compile_action = compile_actions[stub] + if compile_action and not tree.target args = [arg for arg in *tree when type(arg) != "string"] -- Force Lua to avoid tail call optimization for debugging purposes -- TODO: use tail call? @@ -412,7 +415,7 @@ 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 AST.is_syntax_tree(ret) + if SyntaxTree\is_instance(ret) if ret == tree info = debug.getinfo(compile_action, "S") filename = Source\from_string(info.source).filename @@ -442,20 +445,27 @@ with NomsuCompiler elseif tok.type == "Action" @compile_error tok, - "Can't use this as an argument to (#{stub}), since it's not an expression, it produces: #{repr arg_lua}", + "Can't use this as an argument to (#{stub}), since it's not an expression, it produces: #{tostring(arg_lua)}", "Check the implementation of (#{tok.stub}) to see if it is actually meant to produce an expression." else @compile_error tok, - "Can't use this as an argument to (#{stub}), since it's not an expression, it produces: #{repr arg_lua}" + "Can't use this as an argument to (#{stub}), since it's not an expression, it produces: #{tostring(arg_lua)}" insert args, arg_lua lua\concat_append args, ", " lua\append ")" return lua when "EscapedNomsu" - lua = LuaCode.Value tree.source, tree[1].type, "{" + lua = LuaCode.Value tree.source, "SyntaxTree{" needs_comma, i = false, 1 - for k,v in pairs(AST.is_syntax_tree(tree[1], "EscapedNomsu") and tree or tree[1]) + as_lua = (x)-> + if type(x) == 'number' + tostring(x) + elseif SyntaxTree\is_instance(x) + @compile(x, compile_actions) + else x\as_lua! + + for k,v in pairs((SyntaxTree\is_instance(tree[1]) and tree[1].type == "EscapedNomsu" and tree) or tree[1]) if needs_comma then lua\append ", " else needs_comma = true if k == i @@ -463,11 +473,11 @@ with NomsuCompiler elseif type(k) == 'string' and match(k,"[_a-zA-Z][_a-zA-Z0-9]*") lua\append(k, "= ") else - lua\append("[", (AST.is_syntax_tree(k) and @compile(k, compile_actions) or repr(k)), "]= ") + lua\append("[", as_lua(k), "]= ") if k == "source" - lua\append repr(tostring(v)) + lua\append tostring(v)\as_lua! else - lua\append(AST.is_syntax_tree(v) and @compile(v, compile_actions) or repr(v)) + lua\append as_lua(v) lua\append "}" return lua @@ -502,9 +512,9 @@ with NomsuCompiler if type(bit) == "string" string_buffer ..= bit continue - if string_buffer ~= "" + if string_buffer != "" if #lua.bits > 0 then lua\append ".." - lua\append repr(string_buffer) + lua\append string_buffer\as_lua! string_buffer = "" bit_lua = @compile(bit, compile_actions) unless bit_lua.is_value @@ -519,7 +529,7 @@ with NomsuCompiler if string_buffer ~= "" or #lua.bits == 0 if #lua.bits > 0 then lua\append ".." - lua\append repr(string_buffer) + lua\append string_buffer\as_lua! if #lua.bits > 1 lua\parenthesize! @@ -547,9 +557,8 @@ with NomsuCompiler unless value_lua.is_value @compile_error tree[2], "Can't use this as a dict value, since it's not an expression." - -- TODO: support arbitrary words here, like operators and unicode key_str = match(tostring(key_lua), [=[^["']([a-zA-Z_][a-zA-Z0-9_]*)['"]$]=]) - return if key_str + return if key_str and key_str\is_lua_id! LuaCode tree.source, key_str,"=",value_lua elseif sub(tostring(key_lua),1,1) == "[" -- NOTE: this *must* use a space after the [ to avoid freaking out @@ -575,7 +584,8 @@ with NomsuCompiler @compile_error key, "Can't use this as an index, since it's not an expression." key_lua_str = tostring(key_lua) - if lua_id = match(key_lua_str, "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") + lua_id = match(key_lua_str, "^['\"]([a-zA-Z_][a-zA-Z0-9_]*)['\"]$") + if lua_id and lua_id\is_lua_id! lua\append ".#{lua_id}" elseif sub(key_lua_str,1,1) == '[' -- NOTE: this *must* use a space after the [ to avoid freaking out @@ -623,7 +633,10 @@ with NomsuCompiler when "Action" nomsu = NomsuCode(tree.source) if tree.target - nomsu\append @tree_to_inline_nomsu(tree.target), "::" + inline_target = @tree_to_inline_nomsu(tree.target) + if tree.target.type == "Action" + inline_target\parenthesize! + nomsu\append inline_target, "::" for i,bit in ipairs tree if type(bit) == "string" clump_words = (type(tree[i-1]) == 'string' and is_operator(bit) != is_operator(tree[i-1])) @@ -729,7 +742,7 @@ with NomsuCompiler find_comments = (t)-> if t.comments and t.source.filename == tree.source.filename comment_set[c] = true for c in *t.comments - find_comments(x) for x in *t when AST.is_syntax_tree x + find_comments(x) for x in *t when SyntaxTree\is_instance x find_comments(tree) -- Sort in reversed order so they can be easily popped comments = [c for c in pairs comment_set] @@ -778,9 +791,9 @@ with NomsuCompiler nomsu = NomsuCode(tree.source, pop_comments(tree.source.start)) should_clump = (prev_line, line)-> if prev_line.type == "Action" and line.type == "Action" - if prev_line.stub == "use 1" then return line.stub == "use 1" - if prev_line.stub == "test 1" then return true - if line.stub == "test 1" then return false + if prev_line.stub == "use" then return line.stub == "use" + if prev_line.stub == "test" then return true + if line.stub == "test" then return false return not recurse(prev_line)\is_multiline! for chunk_no, chunk in ipairs tree nomsu\append "\n\n#{("~")\rep(80)}\n\n" if chunk_no > 1 @@ -3,8 +3,6 @@ local re = require('re') lpeg.setmaxstack(20000) local P, R, S, C, Cmt, Carg, Cc P, R, S, C, Cmt, Carg, Cc = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cmt, lpeg.Carg, lpeg.Cc -local repr -repr = require('utils').repr local DEFS do local _with_0 = { } diff --git a/parser.moon b/parser.moon index 9a2e2ff..78e6291 100644 --- a/parser.moon +++ b/parser.moon @@ -3,7 +3,6 @@ lpeg = require 'lpeg' re = require 're' lpeg.setmaxstack 20000 {:P,:R,:S,:C,:Cmt,:Carg,:Cc} = lpeg -{:repr} = require 'utils' DEFS = with {} -- Newline supports either windows-style CR+LF or unix-style LF diff --git a/string2.lua b/string2.lua index 5995de2..09cb5ea 100644 --- a/string2.lua +++ b/string2.lua @@ -27,34 +27,39 @@ isplit = function(self, sep) }, 0 end local lua_keywords = { - "and", - "break", - "do", - "else", - "elseif", - "end", - "false", - "for", - "function", - "goto", - "if", - "in", - "local", - "nil", - "not", - "or", - "repeat", - "return", - "then", - "true", - "until", - "while" + ["and"] = true, + ["break"] = true, + ["do"] = true, + ["else"] = true, + ["elseif"] = true, + ["end"] = true, + ["false"] = true, + ["for"] = true, + ["function"] = true, + ["goto"] = true, + ["if"] = true, + ["in"] = true, + ["local"] = true, + ["nil"] = true, + ["not"] = true, + ["or"] = true, + ["repeat"] = true, + ["return"] = true, + ["then"] = true, + ["true"] = true, + ["until"] = true, + ["while"] = true } +local is_lua_id +is_lua_id = function(str) + return match(str, "^[_a-zA-Z][_a-zA-Z0-9]*$") and not lua_keywords[str] +end local string2 = { isplit = isplit, uppercase = upper, lowercase = lower, reversed = reverse, + is_lua_id = is_lua_id, capitalized = function(self) return gsub(self, '%l', upper, 1) end, @@ -73,6 +78,12 @@ local string2 = { end return _accum_0 end, + starts_with = function(self, s) + return sub(self, 1, #s) == s + end, + ends_with = function(self, s) + return #self >= #s and sub(self, #self - #s, -1) == s + end, lines = function(self) local _accum_0 = { } local _len_0 = 1 @@ -109,19 +120,35 @@ local string2 = { for _index_0 = 1, #_list_0 do local line = _list_0[_index_0] while #line > maxlen do - local chunk = line:sub(1, maxlen) - local split = chunk:find(' ', maxlen - buffer, true) or maxlen - chunk = line:sub(1, split) - line = line:sub(split + 1, -1) + local chunk = sub(line, 1, maxlen) + local split = find(chunk, ' ', maxlen - buffer, true) or maxlen + chunk = sub(line, 1, split) + line = sub(line, split + 1, -1) lines[#lines + 1] = chunk end lines[#lines + 1] = line end return table.concat(lines, "\n") end, + as_lua = function(self) + local escaped = gsub(self, "\\", "\\\\") + escaped = gsub(escaped, "\n", "\\n") + escaped = gsub(escaped, '"', '\\"') + escaped = gsub(escaped, "[^ %g]", function(c) + return format("\\%03d", byte(c, 1)) + end) + return '"' .. escaped .. '"' + end, + as_nomsu = function(self) + local escaped = gsub(self, "\\", "\\\\") + escaped = gsub(escaped, "\n", "\\n") + escaped = gsub(escaped, '"', '\\"') + escaped = gsub(escaped, "[^ %g]", function(c) + return format("\\%03d", byte(c, 1)) + end) + return '"' .. escaped .. '"' + end, as_lua_id = function(str) - local orig = str - str = gsub(str, "^ *$", "%1 ") str = gsub(str, "x([0-9A-F][0-9A-F])", "x78%1") str = gsub(str, "%W", function(c) if c == ' ' then @@ -130,36 +157,42 @@ local string2 = { return format("x%02X", byte(c)) end end) - str = gsub(str, "^_*%d", "_%1") - if match(str, "^_*[abdefgilnortuw][aefhilnoru][acdefiklnoprstu]*$") then - for _index_0 = 1, #lua_keywords do - local kw = lua_keywords[_index_0] - if match(str, ("^_*" .. kw .. "$")) then - str = "_" .. str - end - end + if not (is_lua_id(match(str, "^_*(.*)$"))) then + str = "_" .. str end return str end, from_lua_id = function(str) - if match(str, "^_+[abdefgilnortuw][aefhilnoru][acdefiklnoprstu]*$") then - for _index_0 = 1, #lua_keywords do - local kw = lua_keywords[_index_0] - if match(str, ("^_+" .. kw .. "$")) then - str = str:sub(2, -1) - end - end + if not (is_lua_id(match(str, "^_*(.*)$"))) then + str = sub(str, 2, -1) end - str = gsub(str, "^_(_*%d.*)", "%1") str = gsub(str, "_", " ") str = gsub(str, "x([0-9A-F][0-9A-F])", function(hex) return char(tonumber(hex, 16)) end) - str = gsub(str, "^ ([ ]*)$", "%1") return str end } for k, v in pairs(string) do string2[k] = string2[k] or v end +local _list_0 = { + "", + "_", + " ", + "return", + "asdf", + "one two", + "one_two", + "Hex2Dec", + "He-ec", + "\3" +} +for _index_0 = 1, #_list_0 do + local test = _list_0[_index_0] + local lua_id = string2.as_lua_id(test) + assert(is_lua_id(lua_id), "failed to convert '" .. tostring(test) .. "' to a valid Lua identifier (got '" .. tostring(lua_id) .. "')") + local roundtrip = string2.from_lua_id(lua_id) + assert(roundtrip == test, "Failed lua_id roundtrip: '" .. tostring(test) .. "' -> '" .. tostring(lua_id) .. "' -> '" .. tostring(roundtrip) .. "'") +end return string2 diff --git a/string2.moon b/string2.moon index 2259272..e6db628 100644 --- a/string2.moon +++ b/string2.moon @@ -13,15 +13,21 @@ isplit = (sep='%s+')=> return step, {str:@, pos:1, :sep}, 0 lua_keywords = { - "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", - "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" + ["and"]:true, ["break"]:true, ["do"]:true, ["else"]:true, ["elseif"]:true, ["end"]:true, + ["false"]:true, ["for"]:true, ["function"]:true, ["goto"]:true, ["if"]:true, + ["in"]:true, ["local"]:true, ["nil"]:true, ["not"]:true, ["or"]:true, ["repeat"]:true, + ["return"]:true, ["then"]:true, ["true"]:true, ["until"]:true, ["while"]:true } +is_lua_id = (str)-> + match(str, "^[_a-zA-Z][_a-zA-Z0-9]*$") and not lua_keywords[str] string2 = { - :isplit, uppercase:upper, lowercase:lower, reversed:reverse + :isplit, uppercase:upper, lowercase:lower, reversed:reverse, :is_lua_id capitalized: => gsub(@, '%l', upper, 1) byte: byte, bytes: (i, j)=> {byte(@, i or 1, j or -1)} split: (sep)=> [chunk for i,chunk in isplit(@, sep)] + starts_with: (s)=> sub(@, 1, #s) == s + ends_with: (s)=> #@ >= #s and sub(@, #@-#s, -1) == s lines: => [line for i,line in isplit(@, '\n')] line: (line_num)=> for i, line, start in isplit(@, '\n') @@ -37,22 +43,32 @@ string2 = { lines = {} for line in *@lines! while #line > maxlen - chunk = line\sub(1, maxlen) - split = chunk\find(' ', maxlen-buffer, true) or maxlen - chunk = line\sub(1, split) - line = line\sub(split+1, -1) + chunk = sub(line, 1, maxlen) + split = find(chunk, ' ', maxlen-buffer, true) or maxlen + chunk = sub(line, 1, split) + line = sub(line, split+1, -1) lines[#lines+1] = chunk lines[#lines+1] = line return table.concat(lines, "\n") + as_lua: => + escaped = gsub(@, "\\", "\\\\") + escaped = gsub(escaped, "\n", "\\n") + escaped = gsub(escaped, '"', '\\"') + escaped = gsub(escaped, "[^ %g]", (c)-> format("\\%03d", byte(c, 1))) + return '"'..escaped..'"' + + as_nomsu: => + escaped = gsub(@, "\\", "\\\\") + escaped = gsub(escaped, "\n", "\\n") + escaped = gsub(escaped, '"', '\\"') + escaped = gsub(escaped, "[^ %g]", (c)-> format("\\%03d", byte(c, 1))) + return '"'..escaped..'"' + -- Convert an arbitrary text into a valid Lua identifier. This function is injective, -- but not idempotent. In logic terms: (x != y) => (as_lua_id(x) != as_lua_id(y)), -- but not (as_lua_id(a) == b) => (as_lua_id(b) == b). as_lua_id: (str)-> - orig = str - -- Empty strings are not valid lua identifiers, so treat them like " ", - -- and treat " " as " ", etc. to preserve injectivity. - str = gsub str, "^ *$", "%1 " -- Escape 'x' (\x78) when it precedes something that looks like an uppercase hex sequence. -- This way, all Lua IDs can be unambiguously reverse-engineered, but normal usage -- of 'x' won't produce ugly Lua IDs. @@ -62,29 +78,26 @@ string2 = { str = gsub str, "%W", (c)-> if c == ' ' then '_' else format("x%02X", byte(c)) - -- Lua IDs can't start with numbers, so map "1" -> "_1", "_1" -> "__1", etc. - str = gsub str, "^_*%d", "_%1" - -- This pattern is guaranteed to match all keywords, but also matches some other stuff. - if match str, "^_*[abdefgilnortuw][aefhilnoru][acdefiklnoprstu]*$" - for kw in *lua_keywords - if match str, ("^_*"..kw.."$") - str = "_"..str + + unless is_lua_id(match(str, "^_*(.*)$")) + str = "_"..str return str -- from_lua_id(as_lua_id(str)) == str, but behavior is unspecified for inputs that -- did not come from as_lua_id() from_lua_id: (str)-> - -- This pattern is guaranteed to match all keywords, but also matches some other stuff. - if match str, "^_+[abdefgilnortuw][aefhilnoru][acdefiklnoprstu]*$" - for kw in *lua_keywords - if match str, ("^_+"..kw.."$") - str = str\sub(2,-1) - str = gsub(str, "^_(_*%d.*)", "%1") + unless is_lua_id(match(str, "^_*(.*)$")) + str = sub(str,2,-1) str = gsub(str, "_", " ") str = gsub(str, "x([0-9A-F][0-9A-F])", (hex)-> char(tonumber(hex, 16))) - str = gsub(str, "^ ([ ]*)$", "%1") return str } for k,v in pairs(string) do string2[k] or= v +for test in *{"", "_", " ", "return", "asdf", "one two", "one_two", "Hex2Dec", "He-ec", "\3"} + lua_id = string2.as_lua_id(test) + assert is_lua_id(lua_id), "failed to convert '#{test}' to a valid Lua identifier (got '#{lua_id}')" + roundtrip = string2.from_lua_id(lua_id) + assert roundtrip == test, "Failed lua_id roundtrip: '#{test}' -> '#{lua_id}' -> '#{roundtrip}'" + return string2 diff --git a/syntax_tree.lua b/syntax_tree.lua index 72b510e..fe4f6dc 100644 --- a/syntax_tree.lua +++ b/syntax_tree.lua @@ -1,5 +1,3 @@ -local repr -repr = require('utils').repr local insert, remove, concat do local _obj_0 = table @@ -8,82 +6,104 @@ end local Source Source = require("code_obj").Source local unpack = unpack or table.unpack -local AST = { } -AST.is_syntax_tree = function(n, t) - if t == nil then - t = nil +local as_lua +as_lua = function(self) + if type(self) == 'number' then + return tostring(self) end - return type(n) == 'table' and getmetatable(n) and AST[n.type] == getmetatable(n) and (t == nil or n.type == t) -end -local types = { - "Number", - "Var", - "Block", - "EscapedNomsu", - "Text", - "List", - "Dict", - "DictEntry", - "IndexChain", - "Action", - "FileChunks", - "Error", - "Comment" -} -for _index_0 = 1, #types do - local name = types[_index_0] - local cls = { } do - cls.__class = cls - cls.__index = cls - cls.__name = name - cls.type = name - cls.is_instance = function(self, x) - return getmetatable(x) == self - end - cls.__tostring = function(self) - return tostring(self.type) .. tostring(repr(self, (function() end))) - end - cls.__repr = function(self) - return tostring(self.type) .. tostring(repr(self, (function() end))) - end - cls.source_code_for_tree = setmetatable({ }, { - __index = function(self, t) - local s = t.source - local Files = require('files') - local f = Files.read(s.filename) - return f + local mt = getmetatable(self) + if mt then + do + local _as_lua = mt.as_lua + if _as_lua then + return _as_lua(self) + end end - }) - cls.get_source_code = function(self) - return self.source_code_for_tree[self] end - cls.map = function(self, fn) + end + return error("Not supported: " .. tostring(self)) +end +local SyntaxTree +do + local _class_0 + local _base_0 = { + __tostring = function(self) + local bits + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #self do + local b = self[_index_0] + _accum_0[_len_0] = tostring(b) + _len_0 = _len_0 + 1 + end + bits = _accum_0 + end + for k, v in pairs(self) do + if not (bits[k]) then + table.insert(bits, "[ " .. tostring(tostring(k)) .. "]=" .. tostring(tostring(v))) + end + end + return "SyntaxTree{" .. tostring(table.concat(bits, ", ")) .. "}" + end, + __eq = function(self, other) + if type(self) ~= type(other) or #self ~= #other or getmetatable(self) ~= getmetatable(other) then + return false + end + for i = 1, #self do + if self[i] ~= other[i] then + return false + end + end + if self.target ~= other.target then + return false + end + return true + end, + as_lua = function(self) + local bits + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #self do + local b = self[_index_0] + _accum_0[_len_0] = as_lua(b) + _len_0 = _len_0 + 1 + end + bits = _accum_0 + end + for k, v in pairs(self) do + if not (bits[k]) then + table.insert(bits, "[ " .. tostring(as_lua(k)) .. "]=" .. tostring(as_lua(v))) + end + end + return "SyntaxTree{" .. tostring(table.concat(bits, ", ")) .. "}" + end, + get_source_code = function(self) + return self.__class.source_code_for_tree[self] + end, + map = function(self, fn) local replacement = fn(self) if replacement == false then return nil end if replacement then - if AST.is_syntax_tree(replacement) then - replacement = setmetatable((function() + if SyntaxTree:is_instance(replacement) then + do local _tbl_0 = { } for k, v in pairs(replacement) do _tbl_0[k] = v end - return _tbl_0 - end)(), getmetatable(replacement)) + replacement = _tbl_0 + end replacement.source = self.source if self.comments then replacement.comments = { unpack(self.comments) } end - do - local init = replacement.__init - if init then - init(replacement) - end - end + replacement = SyntaxTree(replacement) end else replacement = { @@ -97,7 +117,7 @@ for _index_0 = 1, #types do local _continue_0 = false repeat replacement[k] = v - if AST.is_syntax_tree(v) then + if SyntaxTree:is_instance(v) then local r = v:map(fn) if r == v or r == nil then _continue_0 = true @@ -115,76 +135,78 @@ for _index_0 = 1, #types do if not (changes) then return self end - replacement = setmetatable(replacement, getmetatable(self)) - do - local init = replacement.__init - if init then - init(replacement) - end - end + replacement = SyntaxTree(replacement) end return replacement - end - cls.__eq = function(self, other) - if type(self) ~= type(other) or #self ~= #other or getmetatable(self) ~= getmetatable(other) then - return false - end - for i = 1, #self do - if self[i] ~= other[i] then - return false + end, + get_args = function(self) + assert(self.type == "Action", "Only actions have arguments") + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #self do + local tok = self[_index_0] + if type(tok) ~= 'string' then + _accum_0[_len_0] = tok + _len_0 = _len_0 + 1 end end - if self.target ~= other.target then - return false - end - return true - end - end - AST[name] = setmetatable(cls, { - __tostring = function(self) - return self.__name + return _accum_0 end, - __call = function(self, t) - if type(t.source) == 'string' then - t.source = Source:from_string(t.source) - else - assert(Source:is_instance(t.source)) - end - setmetatable(t, self) - do - local init = t.__init - if init then - init(t) + get_stub = function(self) + local stub_bits = { } + local arg_i = 1 + for _index_0 = 1, #self do + local a = self[_index_0] + if type(a) == 'string' then + stub_bits[#stub_bits + 1] = a + else + stub_bits[#stub_bits + 1] = arg_i + arg_i = arg_i + 1 end end - return t + while type(stub_bits[#stub_bits]) == 'number' do + stub_bits[#stub_bits] = nil + end + return concat(stub_bits, " ") + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function() end, + __base = _base_0, + __name = "SyntaxTree" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 end }) -end -AST.Action.__init = function(self) - local stub_bits = { } - local arg_i = 1 - for _index_0 = 1, #self do - local a = self[_index_0] - if type(a) == 'string' then - stub_bits[#stub_bits + 1] = a - else - stub_bits[#stub_bits + 1] = tostring(arg_i) - arg_i = arg_i + 1 + _base_0.__class = _class_0 + local self = _class_0 + self.__type = "Syntax Tree" + self.source_code_for_tree = setmetatable({ }, { + __index = function(self, t) + local s = t.source + local Files = require('files') + local f = Files.read(s.filename) + return f end + }) + self.is_instance = function(self, t) + return type(t) == 'table' and getmetatable(t) == self.__base end - self.stub = concat(stub_bits, " ") + SyntaxTree = _class_0 end -AST.Action.get_args = function(self) - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #self do - local tok = self[_index_0] - if type(tok) ~= 'string' then - _accum_0[_len_0] = tok - _len_0 = _len_0 + 1 - end +getmetatable(SyntaxTree).__call = function(self, t) + if type(t.source) == 'string' then + t.source = Source:from_string(t.source) + end + setmetatable(t, self.__base) + if t.type == 'Action' then + t.stub = t:get_stub() end - return _accum_0 + return t end -return AST +return SyntaxTree diff --git a/syntax_tree.moon b/syntax_tree.moon index 305a1ee..03596f8 100644 --- a/syntax_tree.moon +++ b/syntax_tree.moon @@ -1,87 +1,101 @@ -- This file contains the datastructures used to represent parsed Nomsu syntax trees, -- as well as the logic for converting them to Lua code. -{:repr} = require 'utils' {:insert, :remove, :concat} = table {:Source} = require "code_obj" unpack or= table.unpack -AST = {} -AST.is_syntax_tree = (n, t=nil)-> - type(n) == 'table' and getmetatable(n) and AST[n.type] == getmetatable(n) and (t == nil or n.type == t) +as_lua = => + if type(@) == 'number' + return tostring(@) + if mt = getmetatable(@) + if _as_lua = mt.as_lua + return _as_lua(@) + error("Not supported: #{@}") -types = {"Number", "Var", "Block", "EscapedNomsu", "Text", "List", "Dict", "DictEntry", - "IndexChain", "Action", "FileChunks", "Error", "Comment"} -for name in *types - cls = {} - with cls - .__class = cls - .__index = cls - .__name = name - .type = name - .is_instance = (x)=> getmetatable(x) == @ - .__tostring = => "#{@type}#{repr @, (->)}" - .__repr = => "#{@type}#{repr @, (->)}" - .source_code_for_tree = setmetatable({}, {__index:(t)=> - s = t.source - Files = require 'files' - f = Files.read(s.filename) - return f - }) - .get_source_code = => @source_code_for_tree[@] - .map = (fn)=> - replacement = fn(@) - if replacement == false then return nil - if replacement - -- Clone the replacement, so we can give it a proper source/comments - if AST.is_syntax_tree(replacement) - replacement = setmetatable {k,v for k,v in pairs replacement}, getmetatable(replacement) - replacement.source = @source - replacement.comments = {unpack(@comments)} if @comments - if init = replacement.__init then init(replacement) - else - replacement = {source:@source, comments:@comments and {unpack(@comments)}} - changes = false - for k,v in pairs(@) - replacement[k] = v - if AST.is_syntax_tree(v) - r = v\map(fn) - continue if r == v or r == nil - changes = true - replacement[k] = r - return @ unless changes - replacement = setmetatable replacement, getmetatable(@) - if init = replacement.__init then init(replacement) - return replacement - .__eq = (other)=> - return false if type(@) != type(other) or #@ != #other or getmetatable(@) != getmetatable(other) - for i=1,#@ - return false if @[i] != other[i] - return false if @target != other.target - return true +--types = {"Number", "Var", "Block", "EscapedNomsu", "Text", "List", "Dict", "DictEntry", +-- "IndexChain", "Action", "FileChunks", "Error", "Comment"} +class SyntaxTree + @__type: "Syntax Tree" + + __tostring: => + bits = [tostring(b) for b in *@] + for k,v in pairs(@) + unless bits[k] + table.insert(bits, "[ #{tostring(k)}]=#{tostring(v)}") + return "SyntaxTree{#{table.concat(bits, ", ")}}" - AST[name] = setmetatable cls, - __tostring: => @__name - __call: (t)=> - if type(t.source) == 'string' - t.source = Source\from_string(t.source) - else - assert(Source\is_instance(t.source)) - setmetatable(t, @) - if init = t.__init then init(t) - return t + __eq: (other)=> + return false if type(@) != type(other) or #@ != #other or getmetatable(@) != getmetatable(other) + for i=1,#@ + return false if @[i] != other[i] + return false if @target != other.target + return true -AST.Action.__init = => - stub_bits = {} - arg_i = 1 - for a in *@ - if type(a) == 'string' - stub_bits[#stub_bits+1] = a + as_lua: => + bits = [as_lua(b) for b in *@] + for k,v in pairs(@) + unless bits[k] + table.insert(bits, "[ #{as_lua(k)}]=#{as_lua(v)}") + return "SyntaxTree{#{table.concat(bits, ", ")}}" + + @source_code_for_tree: setmetatable({}, {__index:(t)=> + s = t.source + Files = require 'files' + f = Files.read(s.filename) + return f + }) + get_source_code: => @@source_code_for_tree[@] + map: (fn)=> + replacement = fn(@) + if replacement == false then return nil + if replacement + -- Clone the replacement, so we can give it a proper source/comments + if SyntaxTree\is_instance(replacement) + replacement = {k,v for k,v in pairs replacement} + replacement.source = @source + replacement.comments = {unpack(@comments)} if @comments + replacement = SyntaxTree(replacement) else - stub_bits[#stub_bits+1] = tostring(arg_i) - arg_i += 1 - @stub = concat stub_bits, " " + replacement = {source:@source, comments:@comments and {unpack(@comments)}} + changes = false + for k,v in pairs(@) + replacement[k] = v + if SyntaxTree\is_instance(v) + r = v\map(fn) + continue if r == v or r == nil + changes = true + replacement[k] = r + return @ unless changes + replacement = SyntaxTree(replacement) + return replacement + + get_args: => + assert(@type == "Action", "Only actions have arguments") + return [tok for tok in *@ when type(tok) != 'string'] + + get_stub: => + stub_bits = {} + arg_i = 1 + for a in *@ + if type(a) == 'string' + stub_bits[#stub_bits+1] = a + else + stub_bits[#stub_bits+1] = arg_i + arg_i += 1 + while type(stub_bits[#stub_bits]) == 'number' + stub_bits[#stub_bits] = nil + return concat stub_bits, " " + + @is_instance: (t)=> + type(t) == 'table' and getmetatable(t) == @__base + -AST.Action.get_args = => - [tok for tok in *@ when type(tok) != 'string'] +getmetatable(SyntaxTree).__call = (t)=> + if type(t.source) == 'string' + t.source = Source\from_string(t.source) + setmetatable(t, @__base) + if t.type == 'Action' + t.stub = t\get_stub! + return t -return AST +return SyntaxTree diff --git a/tools/autoformat.nom b/tools/autoformat.nom index a915fa4..13f54ef 100755 --- a/tools/autoformat.nom +++ b/tools/autoformat.nom @@ -1,7 +1,7 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # Auto-format Nomsu code. Usage: - nomsu tools/autoformat.nom [-i] file1 file2 directory1 ... + nomsu tools/autoformat.nom [-i] file1 file2 directory1 ... If the first argument is "-i", modifications will be performed in-place. Otherwise, the formatted code will be printed. diff --git a/tools/find_action.nom b/tools/find_action.nom index e6fe90a..02abdbc 100755 --- a/tools/find_action.nom +++ b/tools/find_action.nom @@ -1,7 +1,7 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # Find an action by its stub. Usage: - nomsu tools/find_action.nom "foo %" file1 file2 directory1 ... + nomsu tools/find_action.nom "foo %" file1 file2 directory1 ... Will print all the code locations and code that uses the stub. use "lib/os.nom" @@ -14,13 +14,25 @@ for %path in %files: for file %filename in %path: unless (%filename::matches "%.nom$") (do next %filename) %file = (read file %filename) - %tree = (parse %file from %filename) + try: + %tree = (parse %file from %filename) + ..and if it barfs: + say (red "\%filename failed to parse") + %tree = (nil) + unless %tree: do next %filename + %results = [] for %t in recursive %tree: if (%t is "Action" syntax tree): if (%t.stub is %stub): %line_num = (line number of %t.source.start in %file) - say (blue "\%filename:\%line_num:") - say (yellow (source lines of %t)) + %results::add {..} + line: %line_num + text: "\ + ..\(blue "\%filename:\%line_num:") + \(yellow (source lines of %t))" if (%t is syntax tree): for %sub in %t: recurse %t on %sub + + sort %results by % -> %.line + for % in %results: say %.text diff --git a/tools/parse.nom b/tools/parse.nom index 9d728e8..de6aff4 100755 --- a/tools/parse.nom +++ b/tools/parse.nom @@ -1,11 +1,11 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # Tool to print out a parse tree of files in an easy-to-read format. Usage: - nomsu tools/parse.nom file1 file2 directory1 ... + nomsu tools/parse.nom file1 file2 directory1 ... use "lib/os.nom" -action [print tree %t at indent %indent]: +externally (print tree %t at indent %indent) means: if %t.type is: "Action": say "\(%indent)Action (\(%t.stub)):" diff --git a/tools/replace.nom b/tools/replace.nom index c55793a..25ae0ae 100755 --- a/tools/replace.nom +++ b/tools/replace.nom @@ -1,7 +1,7 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # Tool to find and replace one tree with another. - nomsu tools/replace.nom [-i] tree_to_replace replacement file1 file2 directory1 ... + nomsu tools/replace.nom [-i] tree_to_replace replacement file1 file2 directory1 ... If "-i" is the first argument, replacements will be performed in-place. Otherwise, the upgraded code will be printed. diff --git a/tools/test.nom b/tools/test.nom index 6d557ea..fb70d1c 100755 --- a/tools/test.nom +++ b/tools/test.nom @@ -1,7 +1,7 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # Tool to run all tests in a file (i.e. the code block inside a call to 'test %'). Usage: - nomsu tools/test.nom file1 file2 directory1 ... + nomsu tools/test.nom file1 file2 directory1 ... use "lib/os.nom" use "lib/consolecolor.nom" @@ -17,7 +17,6 @@ for %path in (command line args): if (%filename::matches "%.nom$"): use %filename for %path in (command line args): use %path - %tests = ((=lua "Source:from_string(\%s)") = %t for %s = %t in (tests)) for %path in (command line args): for file %filename in %path: diff --git a/tools/upgrade.nom b/tools/upgrade.nom index cd3fbbb..575646e 100755 --- a/tools/upgrade.nom +++ b/tools/upgrade.nom @@ -1,7 +1,7 @@ -#!/usr/bin/env nomsu -V4.8.8.6 +#!/usr/bin/env nomsu -V4.8.10 # Tool to automatically update code from old versions of Nomsu. Usage: - nomsu tools/upgrade.nom [-i] file1 file2 directory1 ... + nomsu tools/upgrade.nom [-i] file1 file2 directory1 ... If "-i" is the first argument, upgrades will be performed in-place. Otherwise, the upgraded code will be printed. @@ -10,22 +10,39 @@ use "lib/os.nom" %args = (command line args) %inplace = (no) -if (%args.1 is "-i"): - %inplace = (yes) - %args::remove index 1 - -if (%args.1 is "-t"): - use "lib/consolecolor.nom" - %test = (yes) - %args::remove index 1 +%start_version = (nil) +%version = (Nomsu version) +repeat: + if %args.1 is: + "-i": + %inplace = (yes) + %args::remove index 1 + + "-t": + use "lib/consolecolor.nom" + %test = (yes) + %args::remove index 1 + + "-V": + %version = %args.2 + %args::remove index 1 + %args::remove index 1 + + "-S": + %start_version = %args.2 + %args::remove index 1 + %args::remove index 1 + + else: stop for %path in %args: for file %filename in %path: unless (%filename::matches "%.nom$"): do next %filename %tree = (parse (read file %filename) from %filename) - %uptree = (%tree upgraded) + %uptree = (..) + %tree upgraded from (%start_version or (%tree.version or (Nomsu version))) to %version %text = "\ - ..#!/usr/bin/env nomsu -V\(Nomsu version) + ..#!/usr/bin/env nomsu -V\%version \(%uptree as nomsu)" if: |
