aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--containers.lua167
-rw-r--r--containers.moon101
-rw-r--r--core/errors.nom13
-rw-r--r--core/text.nom95
-rw-r--r--nomsu_compiler.lua28
-rw-r--r--nomsu_compiler.moon22
6 files changed, 318 insertions, 108 deletions
diff --git a/containers.lua b/containers.lua
index 8063252..ed0825e 100644
--- a/containers.lua
+++ b/containers.lua
@@ -8,6 +8,8 @@ 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
end
+local lpeg = require('lpeg')
+local re = require('re')
local List, Dict
local _list_mt = {
__eq = equivalent,
@@ -90,6 +92,30 @@ local _list_mt = {
_1_nd_to_last = nth_to_last,
_1_rd_to_last = nth_to_last,
_1_th_to_last = nth_to_last,
+ joined = function(self)
+ return table.concat((function()
+ local _accum_0 = { }
+ local _len_0 = 1
+ for _index_0 = 1, #self do
+ local x = self[_index_0]
+ _accum_0[_len_0] = tostring(x)
+ _len_0 = _len_0 + 1
+ end
+ return _accum_0
+ end)())
+ end,
+ joined_with_1 = function(self, glue)
+ return table.concat((function()
+ local _accum_0 = { }
+ local _len_0 = 1
+ for _index_0 = 1, #self do
+ local x = self[_index_0]
+ _accum_0[_len_0] = tostring(x)
+ _len_0 = _len_0 + 1
+ end
+ return _accum_0
+ end)(), glue)
+ end,
has_1 = function(self, item)
for _index_0 = 1, #self do
local x = self[_index_0]
@@ -238,7 +264,146 @@ for i, entry in ipairs(Dict({
})) do
assert(i == 1 and entry.key == "x" and entry.value == 99, "ipairs compatibility issue")
end
+local Text
+do
+ local reverse, upper, lower, find, byte, match, gmatch, gsub, sub, format, rep
+ do
+ local _obj_0 = string
+ 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 as_lua_id
+ as_lua_id = function(str)
+ str = gsub(str, "^\3*$", "%1\3")
+ str = gsub(str, "x([0-9A-F][0-9A-F])", "x78%1")
+ str = gsub(str, "%W", function(c)
+ if c == ' ' then
+ return '_'
+ else
+ return format("x%02X", byte(c))
+ end
+ end)
+ str = gsub(str, "^_*%d", "_%1")
+ return str
+ end
+ local line_matcher = re.compile([[
+ lines <- {| line (%nl line)* |}
+ line <- {(!%nl .)*}
+ ]], {
+ nl = lpeg.P("\r") ^ -1 * lpeg.P("\n")
+ })
+ local text_methods = {
+ reversed = function(self)
+ return reverse(tostring(self))
+ end,
+ uppercase = function(self)
+ return upper(tostring(self))
+ end,
+ lowercase = function(self)
+ return lower(tostring(self))
+ end,
+ as_lua_id = function(self)
+ return as_lua_id(tostring(self))
+ end,
+ formatted_with_1 = function(self, args)
+ return format(tostring(self), unpack(args))
+ end,
+ byte_1 = function(self, i)
+ return byte(tostring(self), i)
+ end,
+ position_of_1 = function(self)
+ return find(tostring(self))
+ end,
+ position_of_1_after_2 = function(self, i)
+ return find(tostring(self), i)
+ end,
+ bytes_1_to_2 = function(self, start, stop)
+ return List({
+ byte(tostring(self), start, stop)
+ })
+ end,
+ bytes = function(self)
+ return List({
+ byte(tostring(self), 1, #self)
+ })
+ end,
+ capitalized = function(self)
+ return gsub(tostring(self), '%l', upper, 1)
+ end,
+ lines = function(self)
+ return List(line_matcher:match(self))
+ end,
+ matches_1 = function(self, patt)
+ return match(tostring(self), patt) and true or false
+ end,
+ [as_lua_id("* 1")] = function(self, n)
+ return rep(self, n)
+ end,
+ matching_1 = function(self, patt)
+ local result = { }
+ local stepper, x, i = gmatch(tostring(self), patt)
+ while true do
+ local tmp = List({
+ stepper(x, i)
+ })
+ if #tmp == 0 then
+ break
+ end
+ i = tmp[1]
+ result[#result + 1] = tmp
+ end
+ return List(result)
+ end,
+ [as_lua_id("with 1 -> 2")] = function(self, patt, sub)
+ return gsub(tostring(self), patt, sub)
+ end,
+ _coalesce = function(self)
+ if rawlen(self) > 1 then
+ local s = table.concat(self)
+ for i = rawlen(self), 2, -1 do
+ self[i] = nil
+ end
+ self[1] = s
+ end
+ return self
+ end
+ }
+ setmetatable(text_methods, {
+ __index = string
+ })
+ getmetatable("").__index = function(self, i)
+ if type(i) == 'number' then
+ return sub(self, i, i)
+ elseif type(i) == 'table' then
+ return sub(self, i[1], i[2])
+ else
+ return text_methods[i]
+ end
+ end
+ assert(("abc"):matches_1("ab"))
+ local _ = [==[ text_metatable =
+ __mul: (other)=>
+ assert(type(other) == 'number', "Invalid type for multiplication")
+ return rep(@, other)
+ __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]
+ __tostring: => @_coalesce![1]
+ __len: => #tostring(@)
+ __concat: (other)=> tostring(@), tostring(other)
+ __len: => #tostring(@)
+ __eq: (other)=>
+ type(@) == type(other) and getmetatable(@) == getmetatable(other) and tostring(@) == tostring(other)
+ __lt: (other)=> tostring(@) < tostring(other)
+ __le: (other)=> tostring(@) <= tostring(other)
+ __newindex: => error("Cannot modify Text")
+
+ Text = (s)-> setmetatable(s, text_metatable)
+ ]==]
+end
return {
List = List,
- Dict = Dict
+ Dict = Dict,
+ Text = Text
}
diff --git a/containers.moon b/containers.moon
index 68c618b..a9f6857 100644
--- a/containers.moon
+++ b/containers.moon
@@ -2,6 +2,8 @@
{:insert,:remove,:concat} = table
{:repr, :stringify, :equivalent, :nth_to_last, :size} = require 'utils'
+lpeg = require 'lpeg'
+re = require 're'
local List, Dict
@@ -41,6 +43,9 @@ _list_mt =
last: (=> @[#@]), first: (=> @[1])
_1_st_to_last:nth_to_last, _1_nd_to_last:nth_to_last
_1_rd_to_last:nth_to_last, _1_th_to_last:nth_to_last
+ -- TODO: use stringify() to allow joining misc. objects?
+ joined: => table.concat([tostring(x) for x in *@]),
+ joined_with_1: (glue)=> table.concat([tostring(x) for x in *@], glue),
has_1: (item)=>
for x in *@
if x == item
@@ -101,4 +106,98 @@ Dict = (t)-> setmetatable(t, _dict_mt)
for i,entry in ipairs(Dict({x:99}))
assert(i == 1 and entry.key == "x" and entry.value == 99, "ipairs compatibility issue")
-return {:List, :Dict}
+local Text
+do
+ {:reverse, :upper, :lower, :find, :byte, :match, :gmatch, :gsub, :sub, :format, :rep} = string
+
+ -- Convert an arbitrary text into a valid Lua identifier. This function is injective,
+ -- but not idempotent, i.e. if (x != y) then (as_lua_id(x) != as_lua_id(y)),
+ -- but as_lua_id(x) is not necessarily equal to as_lua_id(as_lua_id(x))
+ as_lua_id = (str)->
+ -- Empty strings are not valid lua identifiers, so treat them like "\3",
+ -- and treat "\3" as "\3\3", etc. to preserve injectivity.
+ str = gsub str, "^\3*$", "%1\3"
+ -- Escape 'x' 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.
+ -- i.e. "x" -> "x", "oxen" -> "oxen", but "Hex2Dec" -> "Hex782Dec" and "He-ec" -> "Hex2Dec"
+ str = gsub str, "x([0-9A-F][0-9A-F])", "x78%1"
+ -- Map spaces to underscores, and everything else non-alphanumeric to hex escape sequences
+ 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"
+ return str
+
+ line_matcher = re.compile([[
+ lines <- {| line (%nl line)* |}
+ line <- {(!%nl .)*}
+ ]], nl:lpeg.P("\r")^-1 * lpeg.P("\n"))
+
+ text_methods =
+ reversed:=>reverse(tostring @)
+ uppercase:=>upper(tostring @)
+ lowercase:=>lower(tostring @)
+ as_lua_id:=>as_lua_id(tostring @)
+ formatted_with_1:(args)=>format(tostring(@), unpack(args))
+ byte_1:(i)=>byte(tostring(@), i)
+ position_of_1:=>find(tostring @),
+ position_of_1_after_2:(i)=> find(tostring(@), i)
+ bytes_1_to_2: (start, stop)=> List{byte(tostring(@), start, stop)}
+ bytes: => List{byte(tostring(@), 1, #@)},
+ capitalized: => gsub(tostring(@), '%l', upper, 1)
+ lines: => List(line_matcher\match(@))
+ matches_1: (patt)=> match(tostring(@), patt) and true or false
+ [as_lua_id "* 1"]: (n)=> rep(@, n)
+ matching_1: (patt)=>
+ result = {}
+ stepper,x,i = gmatch(tostring(@), patt)
+ while true
+ tmp = List{stepper(x,i)}
+ break if #tmp == 0
+ i = tmp[1]
+ result[#result+1] = tmp
+ return List(result)
+ [as_lua_id "with 1 -> 2"]: (patt, sub)=> gsub(tostring(@), patt, sub)
+ _coalesce: =>
+ if rawlen(@) > 1
+ s = table.concat(@)
+ for i=rawlen(@), 2, -1 do @[i] = nil
+ @[1] = s
+ return @
+
+ setmetatable(text_methods, {__index:string})
+
+ 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]
+
+ assert(("abc")\matches_1("ab"))
+
+ [==[
+ text_metatable =
+ __mul: (other)=>
+ assert(type(other) == 'number', "Invalid type for multiplication")
+ return rep(@, other)
+ __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]
+ __tostring: => @_coalesce![1]
+ __len: => #tostring(@)
+ __concat: (other)=> tostring(@), tostring(other)
+ __len: => #tostring(@)
+ __eq: (other)=>
+ type(@) == type(other) and getmetatable(@) == getmetatable(other) and tostring(@) == tostring(other)
+ __lt: (other)=> tostring(@) < tostring(other)
+ __le: (other)=> tostring(@) <= tostring(other)
+ __newindex: => error("Cannot modify Text")
+
+ Text = (s)-> setmetatable(s, text_metatable)
+ ]==]
+
+return {:List, :Dict, :Text}
diff --git a/core/errors.nom b/core/errors.nom
index 874f2fe..638b487 100644
--- a/core/errors.nom
+++ b/core/errors.nom
@@ -18,6 +18,19 @@ compile [assume %condition] to:
error(\(quote "\%assumption"), 0)
end
+compile [assume %a == %b] to:
+ lua> ".."
+ local \%assumption = 'Assumption failed: '..tostring(nomsu:tree_to_nomsu(\(\(%a == %b))))
+ define mangler
+ return (..)
+ Lua ".."
+ do
+ local \(mangle "a"), \(mangle "b") = \(%a as lua expr), \(%b as lua expr)
+ if \(mangle "a") ~= \(mangle "b") then
+ error(\(quote "\%assumption").."\\n"..tostring(\(mangle "a")).." != "..tostring(\(mangle "b")), 0)
+ end
+ end
+
compile [assume %condition or barf %message] to (..)
Lua ".."
if not \(%condition as lua expr) then
diff --git a/core/text.nom b/core/text.nom
index d9d85b5..bd9335a 100644
--- a/core/text.nom
+++ b/core/text.nom
@@ -6,10 +6,8 @@
use "core/metaprogramming.nom"
test:
- assume (("x" + "y") == "xy")
- %s = "list:\[1, 2, 3]"
- assume (%s == "list:[1, 2, 3]")
- assume ("foo = \(1 + 2)!" == "foo = 3!")
+ assume "\[1, 2, 3]" == "[1, 2, 3]"
+ assume "foo = \(1 + 2)!" == "foo = 3!"
assume (..)
".."
one
@@ -20,73 +18,35 @@ test:
no\
..gap
..== "nogap"
+ assume (["x", "y"]::joined with ",") == "x,y"
+ assume (["x", "y"]::joined) == "xy"
+ assume ("BAR"::byte 2) == 65
+ assume ("BAR"::bytes 1 to 2) == [66, 65]
+ assume ("asdf"::capitalized) == "Asdf"
+ assume ("asdf"::uppercase) == "ASDF"
+ assume ("asdf"::with "s" -> "X") == "aXdf"
+ assume ("one\ntwo\n"::lines) == ["one", "two", ""]
+
parse [アクション %spec %body] as (action %spec %body)
test:
%こんにちは = "こんにちは"
アクション [% と言う] "\(%)世界"
- assume ((%こんにちは と言う) == "こんにちは世界") or barf ".."
- Unicode doesn't work
+ assume (%こんにちは と言う) == "こんにちは世界"
# Text functions
-test:
- assume ((["x", "y"] joined with ",") == "x,y") or barf "joined with failed"
- assume ((["x", "y"] joined) == "xy") or barf "joined failed"
-action [%texts joined with %glue] (..)
- lua> ".."
- local text_bits = {}
- for i,bit in ipairs(\%texts) do text_bits[i] = stringify(bit) end
- return table.concat(text_bits, \%glue)
-
-parse [joined %texts, %texts joined] as (%texts joined with "")
-
-test:
- assume ((byte 2 of "BAR") == 65)
- assume ((bytes 1 to 2 of "BAR") == [66, 65])
-compile [byte %i of %text] to (..)
- Lua value "(\(%text as lua expr)):byte(\(%i as lua expr))"
-
-compile [bytes %start to %stop of %text] to (..)
- Lua value ".."
- _List{(\(%text as lua expr)):byte(\(%start as lua expr), \(%stop as lua expr))}
-
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-action [bytes of %text] (bytes 1 to (=lua "#\%text") of %text)
-
-test:
- assume (("asdf" capitalized) == "Asdf")
-compile [capitalized %text, %text capitalized] to (..)
- Lua value "(\(%text as lua expr)):gsub('%l', string.upper, 1)"
-
-test:
- assume (("asdf" uppercase) == "ASDF")
-compile [uppercase %text, %text uppercase] to (..)
- Lua value "(\(%text as lua expr)):upper()"
-
-test:
- assume (("asdf" with "X" instead of "s") == "aXdf") or barf ".."
- substitution failed
-compile [..]
+parse [%texts joined with %glue] as (%texts::joined with %glue)
+parse [%texts joined, joined %texts] as (%texts::joined)
+parse [byte %i of %text] as (%text::byte %i)
+parse [bytes %start to %stop of %text] as (%text::bytes %start to %stop)
+parse [bytes of %text] as (%text::bytes)
+parse [capitalized %text, %text capitalized] as (%text::capitalized)
+parse [uppercase %text, %text uppercase] as (%text::uppercase)
+parse [..]
%text with %sub instead of %patt, %text with %patt replaced by %sub
%text s/ %patt / %sub
-..to (..)
- Lua value ".."
- ((\(%text as lua expr)):gsub(\(%patt as lua expr), \(%sub as lua expr)))
-
-test:
- assume (..)
- (..)
- lines in ".."
- one
- two
- ..== ["one", "two"]
-action [lines in %text, lines of %text] (..)
- lua> ".."
- local result = _List{}
- for line in (\%text):gmatch('[^\\n]+') do
- result[#result+1] = line
- end
- return result
+..as (%text::with %patt -> %sub)
+parse [%text matches %pattern] as (%text::matches %pattern)
+parse [%text matching %pattern] as ((%text::matching %pattern).1)
compile [for %match in %text matching %patt %body] to (..)
Lua ".."
@@ -108,15 +68,8 @@ compile [%expr for %match in %text matching %patt] to (..)
return ret
end)()
-compile [%text matches %pattern] to (..)
- Lua value ".."
- ((\(%text as lua expr)):match(\(%pattern as lua expr)) and true or false)
-
-compile [%text matching %pattern] to (..)
- Lua value "(\(%text as lua expr)):match(\(%pattern as lua expr))"
-
test:
- assume ("\n" == (newline)) or barf "Text literals failed."
+ assume "\n" == (newline)
# Text literals
lua> ".."
diff --git a/nomsu_compiler.lua b/nomsu_compiler.lua
index 02460b3..215617f 100644
--- a/nomsu_compiler.lua
+++ b/nomsu_compiler.lua
@@ -4,10 +4,10 @@ local utils = require('utils')
local Files = require('files')
local repr, stringify, equivalent
repr, stringify, equivalent = utils.repr, utils.stringify, utils.equivalent
-local List, Dict
+local List, Dict, Text
do
local _obj_0 = require('containers')
- List, Dict = _obj_0.List, _obj_0.Dict
+ List, Dict, Text = _obj_0.List, _obj_0.Dict, _obj_0.Text
end
colors = require('consolecolors')
colored = setmetatable({ }, {
@@ -74,23 +74,6 @@ table.copy = function(t)
return _tbl_0
end)(), getmetatable(t))
end
-do
- local STRING_METATABLE = getmetatable("")
- STRING_METATABLE.__add = function(self, other)
- return self .. stringify(other)
- end
- STRING_METATABLE.__index = function(self, i)
- local ret = string[i]
- if ret ~= nil then
- return ret
- end
- if type(i) == 'number' then
- return sub(self, i, i)
- elseif type(i) == 'table' then
- return sub(self, i[1], i[2])
- end
- end
-end
local MAX_LINE = 80
local NomsuCompiler = setmetatable({
name = "Nomsu"
@@ -531,7 +514,12 @@ do
end
local lua = LuaCode.Value(tree.source)
if tree.target then
- lua:append(self:compile(tree.target), ":")
+ local target_lua = self:compile(tree.target)
+ if tostring(target_lua):match("^%(.*%)$") or tostring(target_lua):match("^[_a-zA-Z][_a-zA-Z0-9]*$") then
+ lua:append(target_lua, ":")
+ else
+ lua:append("(", target_lua, "):")
+ end
end
lua:append(string.as_lua_id(stub), "(")
local args = { }
diff --git a/nomsu_compiler.moon b/nomsu_compiler.moon
index bae3f11..7a637f4 100644
--- a/nomsu_compiler.moon
+++ b/nomsu_compiler.moon
@@ -14,7 +14,7 @@ re = require 're'
utils = require 'utils'
Files = require 'files'
{:repr, :stringify, :equivalent} = utils
-{:List, :Dict} = require 'containers'
+{:List, :Dict, :Text} = require 'containers'
export colors, colored
colors = require 'consolecolors'
colored = setmetatable({}, {__index:(_,color)-> ((msg)-> colors[color]..tostring(msg or '')..colors.reset)})
@@ -57,18 +57,6 @@ table.copy = (t)-> setmetatable({k,v for k,v in pairs(t)}, getmetatable(t))
-- consider non-linear codegen, rather than doing thunks for things like comprehensions
-- Re-implement nomsu-to-lua comment translation?
--- Use + operator for string coercive concatenation (note: "asdf" + 3 == "asdf3")
--- Use [] for accessing string characters, or s[{3,4}] for s:sub(3,4)
--- Note: This globally affects all strings in this instance of Lua!
-do
- STRING_METATABLE = getmetatable("")
- STRING_METATABLE.__add = (other)=> @ .. stringify(other)
- STRING_METATABLE.__index = (i)=>
- ret = string[i]
- if ret != nil then return ret
- if type(i) == 'number' then return sub(@, i, i)
- elseif type(i) == 'table' then return sub(@, i[1], i[2])
-
MAX_LINE = 80 -- For beautification purposes, try not to make lines much longer than this value
NomsuCompiler = setmetatable {name:"Nomsu"},
__index: (k)=> if _self = rawget(@, "self") then _self[k] else nil
@@ -339,8 +327,12 @@ with NomsuCompiler
return ret
lua = LuaCode.Value(tree.source)
- if tree.target
- lua\append @compile(tree.target), ":"
+ if tree.target -- Method call
+ target_lua = @compile tree.target
+ if tostring(target_lua)\match("^%(.*%)$") or tostring(target_lua)\match("^[_a-zA-Z][_a-zA-Z0-9]*$")
+ lua\append target_lua, ":"
+ else
+ lua\append "(", target_lua, "):"
lua\append(string.as_lua_id(stub),"(")
args = {}
for i, tok in ipairs tree