code / nomsu

Lines6.6K Lua5.1K PEG1.3K make117
2 others 83
Markdown60 Bourne Again Shell23
(141 lines)
1 -- Expand the capabilities of the built-in strings
2 {:List, :Dict} = require "containers"
3 {:reverse, :upper, :lower, :find, :byte, :match, :gmatch, :gsub, :sub, :format, :rep, :char} = string
5 isplit = (sep='%s+')=>
6 step = (i)=>
7 start = @pos
8 return unless start
9 i += 1
10 nl = find(@str, @sep, start)
11 @pos = nl and (nl+1) or nil
12 line = sub(@str, start, nl and (nl-1) or #@str)
13 return i, line, start, (nl and (nl-1) or #@str)
14 return step, {str:@, pos:1, :sep}, 0
16 lua_keywords = {
17 ["and"]:true, ["break"]:true, ["do"]:true, ["else"]:true, ["elseif"]:true, ["end"]:true,
18 ["false"]:true, ["for"]:true, ["function"]:true, ["goto"]:true, ["if"]:true,
19 ["in"]:true, ["local"]:true, ["nil"]:true, ["not"]:true, ["or"]:true, ["repeat"]:true,
20 ["return"]:true, ["then"]:true, ["true"]:true, ["until"]:true, ["while"]:true
22 is_lua_id = (str)->
23 match(str, "^[_a-zA-Z][_a-zA-Z0-9]*$") and not lua_keywords[str]
25 -- Convert an arbitrary text into a valid Lua identifier. This function is injective,
26 -- but not idempotent. In logic terms: (x != y) => (as_lua_id(x) != as_lua_id(y)),
27 -- but not (as_lua_id(a) == b) => (as_lua_id(b) == b).
28 as_lua_id = (str)->
29 -- Escape 'x' (\x78) when it precedes something that looks like an uppercase hex sequence.
30 -- This way, all Lua IDs can be unambiguously reverse-engineered, but normal usage
31 -- of 'x' won't produce ugly Lua IDs.
32 -- i.e. "x" -> "x", "oxen" -> "oxen", but "Hex2Dec" -> "Hex782Dec" and "He-ec" -> "Hex2Dec"
33 str = gsub str, "x([0-9A-F][0-9A-F])", "x78%1"
34 -- Map spaces to underscores, and everything else non-alphanumeric to hex escape sequences
35 str = gsub str, "%W", (c)->
36 if c == ' ' then '_'
37 else format("x%02X", byte(c))
39 unless is_lua_id(match(str, "^_*(.*)$"))
40 str = "_"..str
41 return str
43 -- from_lua_id(as_lua_id(str)) == str, but behavior is unspecified for inputs that
44 -- did not come from as_lua_id()
45 from_lua_id = (str)->
46 unless is_lua_id(match(str, "^_*(.*)$"))
47 str = sub(str,2,-1)
48 str = gsub(str, "_", " ")
49 str = gsub(str, "x([0-9A-F][0-9A-F])", (hex)-> char(tonumber(hex, 16)))
50 return str
52 Text = {
53 :isplit, uppercase:upper, lowercase:lower, reversed:reverse, :is_lua_id
54 capitalized: => (gsub(@, '%l', upper, 1))
55 byte: byte, as_a_number: tonumber, as_a_base_1_number: tonumber,
56 bytes: (i, j)=> List{byte(@, i or 1, j or -1)}
57 split: (sep)=> List[chunk for i,chunk in isplit(@, sep)]
58 starts_with: (s)=> sub(@, 1, #s) == s
59 ends_with: (s)=> #@ >= #s and sub(@, #@-#s, -1) == s
60 lines: => List[line for i,line in isplit(@, '\n')]
61 line: (line_num)=>
62 for i, line, start in isplit(@, '\n')
63 return line if i == line_num
65 line_info_at: (pos)=>
66 assert(type(pos) == 'number', "Invalid string position")
67 for i, line, start, stop in isplit(@, '\n')
68 if stop+1 >= pos
69 return line, i, (pos-start+1)
71 indented: (indent=" ")=>
72 indent..(gsub(@, "\n", "\n"..indent))
74 as_lua: =>
75 escaped = gsub(@, "\\", "\\\\")
76 escaped = gsub(escaped, "\n", "\\n")
77 escaped = gsub(escaped, '"', '\\"')
78 escaped = gsub(escaped, "[^ %g]", (c)-> format("\\%03d", byte(c, 1)))
79 return '"'..escaped..'"'
81 as_nomsu: => @as_lua!
83 formatted_with:format, byte:byte,
84 position_of:((...)->(find(...))), position_of_1_after:((...)->(find(...))),
85 as_a_lua_identifier: as_lua_id, is_a_lua_identifier: is_lua_id,
86 as_lua_id: as_lua_id, as_a_lua_id: as_lua_id, is_a_lua_id: is_lua_id,
87 is_lua_id: is_lua_id, from_lua_id: from_lua_id,
88 bytes_1_to: (start, stop)=> List{byte(@, start, stop)}
89 [as_lua_id "with 1 ->"]: (...)-> (gsub(...))
90 bytes: => List{byte(@, 1, -1)},
91 wrapped_to: (maxlen=80, margin=8)=>
92 lines = {}
93 for line in *@lines!
94 while #line > maxlen
95 chunk = sub(line, 1, maxlen)
96 split = find(chunk, ' ', maxlen-margin, true) or maxlen
97 chunk = sub(line, 1, split)
98 line = sub(line, split+1, -1)
99 lines[#lines+1] = chunk
100 lines[#lines+1] = line
101 return table.concat(lines, "\n")
103 line_at: (i)=> (@line_info_at(i))
104 line_number_at: (i)=> select(2, @line_info_at(i))
105 line_position_at: (i)=> select(3, @line_info_at(i))
106 matches: (patt)=> match(@, patt) and true or false
107 matching: (patt)=> (match(@, patt))
108 matching_groups: (patt)=> List{match(@, patt)}
109 [as_lua_id "* 1"]: (n)=> rep(@, n)
110 all_matches_of: (patt)=>
111 result = {}
112 stepper,x,i = gmatch(@, patt)
113 while true
114 tmp = List{stepper(x,i)}
115 break if #tmp == 0
116 i = tmp[1]
117 result[#result+1] = (#tmp == 1) and tmp[1] or tmp
118 return List(result)
119 from_1_to: sub, from: sub,
120 character: (i)=> sub(@, i, i)
122 for k,v in pairs(string) do Text[k] or= v
124 _1_as_text = (x)->
125 if x == true then return "yes"
126 if x == false then return "no"
127 return tostring(x)
129 setmetatable Text,
130 __call: (...)=>
131 ret = {...}
132 for i=1,select("#", ...)
133 ret[i] = _1_as_text(ret[i])
134 return table.concat(ret)
136 debug.setmetatable "",
137 __type: "Text"
138 __index: (k)=> Text[k] or (type(k) == 'number' and sub(@, k, k) or nil)
139 __add: (x)=> _1_as_text(@).._1_as_text(x)
141 return Text