aboutsummaryrefslogtreecommitdiff
path: root/string2.moon
diff options
context:
space:
mode:
authorBruce Hill <bitbucket@bruce-hill.com>2018-09-12 15:31:59 -0700
committerBruce Hill <bitbucket@bruce-hill.com>2018-09-12 15:32:04 -0700
commitea310306d73e0bc6542f7133825549ae4471b06a (patch)
treefcc2a5a868fe09fb599d8261f66dad96ac69bb0b /string2.moon
parent9e10c8bf006f42e90a011b8f9284e3ffa52a5859 (diff)
Initial working version.
Diffstat (limited to 'string2.moon')
-rw-r--r--string2.moon79
1 files changed, 79 insertions, 0 deletions
diff --git a/string2.moon b/string2.moon
new file mode 100644
index 0000000..735a2cb
--- /dev/null
+++ b/string2.moon
@@ -0,0 +1,79 @@
+-- Expand the capabilities of the built-in strings
+{:reverse, :upper, :lower, :find, :byte, :match, :gmatch, :gsub, :sub, :format, :rep, :char} = string
+
+isplit = (sep='%s+')=>
+ step = (i)=>
+ start = @pos
+ return unless start
+ i += 1
+ nl = find(@str, @sep, start)
+ @pos = nl and (nl+1) or nil
+ line = sub(@str, start, nl and (nl-1) or #@str)
+ return i, line, start, (nl and (nl-1) or #@str)
+ return step, {str:@, pos:1, :sep}, 0
+
+string2 = {
+ :isplit, uppercase:upper, lowercase:lower, reversed:reverse
+ 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)]
+ lines: => [line for i,line in isplit(@, '\n')]
+ line: (line_num)=>
+ for i, line, start in isplit(@, '\n')
+ return line if i == line_num
+
+ line_at: (pos)=>
+ assert(type(pos) == 'number', "Invalid string position")
+ return if pos < 1 or pos > #@
+ for i, line, start, stop in isplit(@, '\n')
+ if stop >= pos
+ return line, i, (pos-start+1)
+
+ wrap: (maxlen=80, buffer=8)=>
+ 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)
+ lines[#lines+1] = chunk
+ lines[#lines+1] = line
+ return table.concat(lines, "\n")
+
+ -- 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.
+ -- 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"
+ if str.from_lua_id
+ re_orig = str\from_lua_id!
+ require('ldt').breakpoint! if re_orig != orig
+ 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)->
+ str = gsub(str, "^_(_*%d.*)", "%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
+
+return string2