From 810ae220bc2b1dfa07593b77f391e4da3b57a6bb Mon Sep 17 00:00:00 2001
From: Bruce Hill <bitbucket@bruce-hill.com>
Date: Wed, 6 Jun 2018 13:25:01 -0700
Subject: [PATCH] Added list/dict metatables to make comparison and string
 representations simpler. Also deleted Counters.

---
 core/collections.nom      |  4 ---
 core/metaprogramming.nom  | 13 +++++-----
 core/operators.nom        | 18 ++------------
 nomsu.lua                 | 51 +++++++++++++++++++++++++++++++++------
 nomsu.moon                | 24 +++++++++++++-----
 tests/collections.nom     |  4 ---
 tests/metaprogramming.nom |  6 ++---
 tests/text.nom            |  2 +-
 utils.lua                 | 15 ++++++------
 9 files changed, 80 insertions(+), 57 deletions(-)

diff --git a/core/collections.nom b/core/collections.nom
index 0bf5cef..a8a28b2 100644
--- a/core/collections.nom
+++ b/core/collections.nom
@@ -134,10 +134,6 @@ immediately
                 self[\(%key as lua expr)] = value
                 return value
             end})
-    
-immediately
-    parse [new counter] as: {} with fallback % -> 0
-    parse [new default dict] as: {} with fallback % -> {}
 
 # Sorting
 immediately
diff --git a/core/metaprogramming.nom b/core/metaprogramming.nom
index c54a72f..8ad9448 100644
--- a/core/metaprogramming.nom
+++ b/core/metaprogramming.nom
@@ -132,7 +132,7 @@ immediately
         lua> ".."
             local lua = nomsu:tree_to_lua(\%tree)
             if not lua.is_value then
-                error("Invalid thing to convert to lua expr: "..\%tree)
+                error("Invalid thing to convert to lua expr: "..tostring(\%tree))
             end
             return lua
 
@@ -164,8 +164,9 @@ immediately
     parse [to %var write %code] as: lua> "\%var:append(\%code);"
 
 immediately
-    compile [repr %obj] to: Lua value "repr(\(%obj as lua expr))"
-    compile [%obj as text] to: Lua value "tostring(\(%obj as lua expr))"
+    compile [quote %s] to
+        Lua value ".."
+            ('"'..\(%s as lua expr):gsub("\\\\", "\\\\\\\\"):gsub("\n","\\\\n"):gsub('"', '\\\\"')..'"')
     compile [type of %obj] to: Lua value "type(\(%obj as lua expr))"
 
 immediately
@@ -175,7 +176,7 @@ immediately
 # Compiler tools
 immediately
     compile [run %code] to
-        Lua "nomsu:run(Nomsu(\"\(%code.source as text)\", \(%code as lua expr)))"
+        Lua value "nomsu:run(Nomsu(\(quote "\(%code.source)"), \(%code as lua expr)))"
 
 immediately
     compile [show lua %block] to
@@ -189,7 +190,7 @@ immediately
             if \%message.type == "Text" then
                 return Lua(tree.source, "print(", \(%message as lua expr), ");");
             else
-                return Lua(tree.source, "print(stringify(", \(%message as lua expr), "));");
+                return Lua(tree.source, "print(tostring(", \(%message as lua expr), "));");
             end
 
 # Return
@@ -208,7 +209,7 @@ immediately
         return
             Lua ".."
                 if not \(%condition as lua expr) then
-                    error(\(repr %assumption), 0);
+                    error(\(quote "\%assumption"), 0);
                 end
     
     compile [assume %condition or barf %message] to
diff --git a/core/operators.nom b/core/operators.nom
index dd57cea..4ec8ff1 100644
--- a/core/operators.nom
+++ b/core/operators.nom
@@ -23,23 +23,9 @@ immediately
     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, %a == %b] to
-        lua> ".."
-            local safe = {Text=true, Number=true}
-            local a_lua, b_lua = \(%a as lua), \(%b as lua)
-            if safe[\%a.type] or safe[\%b.type] then
-                return Lua.Value(tree.source, "(", a_lua, " == ", b_lua, ")")
-            else
-                return Lua.Value(tree.source, "utils.equivalent(", a_lua, ", ", b_lua, ")")
-            end
+        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
-        lua> ".."
-            local safe = {Text=true, Number=true}
-            local a_lua, b_lua = \(%a as lua), \(%b as lua)
-            if safe[\%a.type] or safe[\%b.type] then
-                return Lua.Value(tree.source, "(", a_lua, " ~= ", b_lua, ")")
-            else
-                return Lua.Value(tree.source, "(not utils.equivalent(", a_lua, ", ", b_lua, "))")
-            end
+        Lua value "(\(%a as lua expr) ~= \(%b as lua expr))"
     # For strict identity checking, use (%x's id) is (%y's id)
     compile [%'s id, id of %] to: Lua value "nomsu.ids[\(% as lua expr)]"
 
diff --git a/nomsu.lua b/nomsu.lua
index bf644c4..dc1a5d8 100644
--- a/nomsu.lua
+++ b/nomsu.lua
@@ -677,7 +677,7 @@ do
         end
         return lua
       elseif "List" == _exp_0 then
-        local lua = Lua.Value(tree.source, "{")
+        local lua = Lua.Value(tree.source, "list{")
         local line_length = 0
         for i, item in ipairs(tree) do
           local item_lua = self:tree_to_lua(item)
@@ -707,7 +707,7 @@ do
         lua:append("}")
         return lua
       elseif "Dict" == _exp_0 then
-        local lua = Lua.Value(tree.source, "{")
+        local lua = Lua.Value(tree.source, "dict{")
         local line_length = 0
         for i, entry in ipairs(tree) do
           local entry_lua = self:tree_to_lua(entry)
@@ -1216,6 +1216,43 @@ do
         end
       })
       self.source_map = { }
+      local _list_mt = {
+        __eq = utils.equivalent,
+        __tostring = 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] = repr(b)
+              _len_0 = _len_0 + 1
+            end
+            return _accum_0
+          end)(), ", ") .. "]"
+        end
+      }
+      local list
+      list = function(t)
+        return setmetatable(t, _list_mt)
+      end
+      local _dict_mt = {
+        __eq = utils.equivalent,
+        __tostring = function(self)
+          return "{" .. concat((function()
+            local _accum_0 = { }
+            local _len_0 = 1
+            for k, v in pairs(self) do
+              _accum_0[_len_0] = tostring(repr(k)) .. ": " .. tostring(repr(v))
+              _len_0 = _len_0 + 1
+            end
+            return _accum_0
+          end)(), ", ") .. "}"
+        end
+      }
+      local dict
+      dict = function(t)
+        return setmetatable(t, _dict_mt)
+      end
       self.environment = {
         nomsu = self,
         repr = repr,
@@ -1256,9 +1293,9 @@ do
         debug = debug,
         math = math,
         io = io,
-        pairs = pairs,
         load = load,
-        ipairs = ipairs
+        list = list,
+        dict = dict
       }
       if jit then
         self.environment.len = function(x)
@@ -1289,11 +1326,10 @@ do
               if mt.__ipairs then
                 return mt.__ipairs(x)
               end
-            else
-              return _ipairs(x)
             end
           end
         end
+        return _ipairs(x)
       end
       self.environment.pairs = function(x)
         if type(x) == 'function' then
@@ -1307,11 +1343,10 @@ do
               if mt.__pairs then
                 return mt.__pairs(x)
               end
-            else
-              return _pairs(x)
             end
           end
         end
+        return _pairs(x)
       end
       for k, v in pairs(Types) do
         self.environment[k] = v
diff --git a/nomsu.moon b/nomsu.moon
index b9ff559..b47b32a 100755
--- a/nomsu.moon
+++ b/nomsu.moon
@@ -245,6 +245,17 @@ class NomsuCompiler
         })
         @source_map = {}
 
+        _list_mt =
+            __eq:utils.equivalent
+            -- Could consider adding a __newindex to enforce list-ness, but would hurt performance
+            __tostring: =>
+                "["..concat([repr(b) for b in *@], ", ").."]"
+        list = (t)-> setmetatable(t, _list_mt)
+        _dict_mt =
+            __eq:utils.equivalent
+            __tostring: =>
+                "{"..concat(["#{repr(k)}: #{repr(v)}" for k,v in pairs @], ", ").."}"
+        dict = (t)-> setmetatable(t, _dict_mt)
         @environment = {
             -- Discretionary/convenience stuff
             nomsu:self, repr:repr, stringify:stringify, utils:utils, lpeg:lpeg, re:re,
@@ -252,8 +263,9 @@ class NomsuCompiler
             :next, :unpack, :setmetatable, :coroutine, :rawequal, :getmetatable, :pcall,
             :error, :package, :os, :require, :tonumber, :tostring, :string, :xpcall, :module,
             :print, :loadfile, :rawset, :_VERSION, :collectgarbage, :rawget, :bit32, :rawlen,
-            :table, :assert, :dofile, :loadstring, :type, :select, :debug, :math, :io, :pairs,
-            :load, :ipairs,
+            :table, :assert, :dofile, :loadstring, :type, :select, :debug, :math, :io, :load,
+            -- Nomsu types:
+            :list, :dict,
         }
         @environment.len = if jit
             (x)->
@@ -270,7 +282,7 @@ class NomsuCompiler
             elseif mt = getmetatable(x)
                 if mt.__ipairs
                     return mt.__ipairs(x)
-            else return _ipairs(x)
+            return _ipairs(x)
         @environment.pairs = (x)->
             if type(x) == 'function'
                 return coroutine.wrap(x)
@@ -279,7 +291,7 @@ class NomsuCompiler
             elseif mt = getmetatable(x)
                 if mt.__pairs
                     return mt.__pairs(x)
-            else return _pairs(x)
+            return _pairs(x)
         for k,v in pairs(Types) do @environment[k] = v
         @environment.Tuple = Tuple
         @environment.Lua = Lua
@@ -558,7 +570,7 @@ class NomsuCompiler
                 return lua
 
             when "List"
-                lua = Lua.Value tree.source, "{"
+                lua = Lua.Value tree.source, "list{"
                 line_length = 0
                 for i, item in ipairs tree
                     item_lua = @tree_to_lua(item)
@@ -583,7 +595,7 @@ class NomsuCompiler
                 return lua
 
             when "Dict"
-                lua = Lua.Value tree.source, "{"
+                lua = Lua.Value tree.source, "dict{"
                 line_length = 0
                 for i, entry in ipairs tree
                     entry_lua = @tree_to_lua(entry)
diff --git a/tests/collections.nom b/tests/collections.nom
index 6329d00..2cc6adf 100644
--- a/tests/collections.nom
+++ b/tests/collections.nom
@@ -37,9 +37,5 @@ assume (%x = [3,2,1])
 sort %x by % = %keys.%
 assume (%x = [2,3,1])
 assume ((unique [1,2,1,3,2,3]) = [1,2,3])
-%c <- (new counter)
-for % in ["x","y","x","x","y"]
-    %c.% +<- 1
-assume (%c = {x:3,y:2})
 
 say "Collections test passed."
diff --git a/tests/metaprogramming.nom b/tests/metaprogramming.nom
index 26a750a..b9b97af 100644
--- a/tests/metaprogramming.nom
+++ b/tests/metaprogramming.nom
@@ -55,15 +55,13 @@ try: foo 99
 
 assume ((\(5 + 5) as value) = 10) or barf "%tree as value failed."
 
-assume (((\(foo %x) as nomsu) as text) = "foo %x") or barf "source code failed."
-
-assume ((repr [1,2]) = "{1, 2}") or barf "repr failed."
+assume ("\(\(foo %x) as nomsu)" = "foo %x") or barf "source code failed."
 
 assume ((type of {}) = "table") or barf "type of failed."
 
 assume ((nomsu) = (=lua "nomsu")) or barf "nomsu failed"
 
-assume (((\%x as lua identifier) as text) = "_x") or barf "converting to identifier failed."
+assume ("\(\%x as lua identifier)" = "_x") or barf "converting to identifier failed."
 
 assume ((run "return 99") = 99) or barf "run % failed."
 
diff --git a/tests/text.nom b/tests/text.nom
index f2ef225..600cce4 100644
--- a/tests/text.nom
+++ b/tests/text.nom
@@ -30,7 +30,7 @@ assume (%s = "one two\\nthreefour")
     list:\[..]
         1,2,3
     ..
-assume (%s = "list:{1, 2, 3}")
+assume (%s = "list:[1, 2, 3]")
 
 assume
     ".."
diff --git a/utils.lua b/utils.lua
index c78a0c8..ac43757 100644
--- a/utils.lua
+++ b/utils.lua
@@ -241,30 +241,29 @@ local function sort(list, keyFn, reverse)
 end
 
 local function equivalent(x, y, depth)
-    depth = depth or -1
-    if x == y then
+    depth = depth or 0
+    if rawequal(x, y) then
         return true
     end
     if type(x) ~= type(y) then
         return false
     end
-    if type(x) ~= 'table' then
+    if type(x) ~= 'table' then return false end
+    if getmetatable(x) ~= getmetatable(y) then
         return false
     end
-    if depth == 0 then
-        return false
-    elseif depth < -999 then
+    if depth >= 99 then
         error("Exceeded maximum comparison depth")
     end
     local checked = {}
     for k, v in pairs(x) do
-        if not equivalent(y[k], v, depth - 1) then
+        if not equivalent(y[k], v, depth + 1) then
             return false
         end
         checked[k] = true
     end
     for k, v in pairs(y) do
-        if not checked[k] and not equivalent(x[k], v, depth - 1) then
+        if not checked[k] and not equivalent(x[k], v, depth + 1) then
             return false
         end
     end