aboutsummaryrefslogtreecommitdiff
path: root/containers.moon
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2018-09-10 15:55:34 -0700
committerBruce Hill <bitbucket@bruce-hill.com>2018-09-10 15:56:00 -0700
commit43e6523fd42f736acfcd0c32d82246c262e07a1d (patch)
tree5a2d6b5250528dc42ce7433374cac6c0c65859a9 /containers.moon
parent603c5b12451d6c68b1e41906e10117da4d99e362 (diff)
Shifting towards more text methods instead of text global functions.
Also fixed a bug with method call parenthesizing.
Diffstat (limited to 'containers.moon')
-rw-r--r--containers.moon101
1 files changed, 100 insertions, 1 deletions
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}