code / nomsu

Lines6.6K Lua5.1K PEG1.3K make117
2 others 83
Markdown60 Bourne Again Shell23
(278 lines)
1 -- This file contains container classes, i.e. Lists and Dicts, plus some extended string functionality
2 local List, Dict, Undict, _undict_mt, _dict_mt
3 {:insert,:remove,:concat} = table
5 as_nomsu = =>
6 if type(@) == 'number'
7 return tostring(@)
8 if mt = getmetatable(@)
9 if _as_nomsu = mt.as_nomsu
10 return _as_nomsu(@)
11 return tostring(@)
13 as_lua = =>
14 if type(@) == 'number'
15 return tostring(@)
16 if mt = getmetatable(@)
17 if _as_lua = mt.as_lua
18 return _as_lua(@)
19 return tostring(@)
21 nth_to_last = (n)=> @[#@-n+1]
23 -- List and Dict classes to provide basic equality/tostring functionality for the tables
24 -- used in Nomsu. This way, they retain a notion of whether they were originally lists or dicts.
26 _list_mt =
27 __type: "a List"
28 __eq: (other)=>
29 unless type(other) == 'table' and getmetatable(other) == getmetatable(@) and #other == #@
30 return false
31 for i,x in ipairs(@)
32 return false unless x == other[i]
33 return true
34 -- Could consider adding a __newindex to enforce list-ness, but would hurt performance
35 __tostring: =>
36 "["..concat([as_nomsu(b) for b in *@], ", ").."]"
37 as_nomsu: =>
38 "["..concat([as_nomsu(b) for b in *@], ", ").."]"
39 as_lua: =>
40 "a_List{"..concat([as_lua(b) for b in *@], ", ").."}"
41 __lt: (other)=>
42 assert type(@) == 'table' and type(other) == 'table', "Incompatible types for comparison"
43 for i=1,math.max(#@, #other)
44 if not @[i] and other[i] then return true
45 elseif @[i] and not other[i] then return false
46 elseif @[i] < other[i] then return true
47 elseif @[i] > other[i] then return false
48 return false
49 __le: (other)=>
50 assert type(@) == 'table' and type(other) == 'table', "Incompatible types for comparison"
51 for i=1,math.max(#@, #other)
52 if not @[i] and other[i] then return true
53 elseif @[i] and not other[i] then return false
54 elseif @[i] < other[i] then return true
55 elseif @[i] > other[i] then return false
56 return true
57 __add: (other)=>
58 ret = List[x for x in *@]
59 for x in *other
60 insert(ret, x)
61 return ret
62 __index:
63 add: insert, append: insert
64 add_1_at_index: (t,x,i)-> insert(t,i,x)
65 at_index_1_add: insert
66 pop: remove, remove_last: remove, remove_index: remove
67 last: (=> @[#@]), first: (=> @[1])
68 _1_st_to_last:nth_to_last, _1_nd_to_last:nth_to_last
69 _1_rd_to_last:nth_to_last, _1_th_to_last:nth_to_last
70 joined: => table.concat([tostring(x) for x in *@]),
71 joined_with: (glue)=> table.concat([tostring(x) for x in *@], glue),
72 has: (item)=>
73 for x in *@
74 if x == item
75 return true
76 return false
77 remove: (item)=>
78 for i,x in ipairs @
79 if x == item
80 remove(@, i)
81 index_of: (item)=>
82 for i,x in ipairs @
83 if x == item
84 return i
85 return nil
86 from_1_to: (start, stop)=>
87 n = #@
88 start = (n+1-start) if start < 0
89 stop = (n+1-stop) if stop < 0
90 return List[@[i] for i=start,stop]
91 from: (start)=> @from_1_to(start, -1)
92 up_to: (stop)=> @from_1_to(1, stop)
93 copy: => List[@[i] for i=1,#@]
94 reverse: =>
95 n = #@
96 for i=1,math.floor(n/2)
97 @[i], @[n-i+1] = @[n-i+1], @[i]
98 reversed: => List[@[i] for i=#@,1,-1]
99 sort: => table.sort(@)
100 sort_by: (fn)=>
101 keys = setmetatable {},
102 __index: (k)=>
103 key = fn(k)
104 @[k] = key
105 return key
106 table.sort(@, (a,b)->keys[a] <= keys[b])
107 sorted: =>
108 c = @copy!
109 c\sort!
110 return c
111 sorted_by: (fn)=>
112 c = @copy!
113 c\sort_by(fn)
114 return c
115 filter_by: (keep)=>
116 deleted = 0
117 for i=1,#@
118 unless keep(@[i])
119 deleted += 1
120 elseif deleted > 0
121 @[i-deleted] = @[i]
122 for i=#@-deleted+1,#@
123 @[i] = nil
125 filtered_by: (keep)=>
126 c = @copy!
127 c\filter_by(keep)
128 return c
130 -- TODO: remove this safety check to get better performance?
131 __newindex: (k,v)=>
132 assert type(k) == 'number', "List indices must be numbers"
133 rawset(@, k, v)
134 _list_mt.__index.as_lua = _list_mt.as_lua
135 _list_mt.__index.as_nomsu = _list_mt.as_nomsu
137 List = (t,...)->
138 local l
139 if type(t) == 'table'
140 l = setmetatable(t, _list_mt)
141 elseif type(t) == 'function'
142 l = setmetatable({}, _list_mt)
143 add = (...)->
144 for i=1,select('#',...) do l[#l+1] = select(i,...)
145 t(add)
146 elseif type(t) == 'thread'
147 l = setmetatable({}, _list_mt)
148 for val in coroutine.wrap(t)
149 l[#l+1] = val
150 else error("Unsupported List type: "..type(t))
151 if select(1,...)
152 for x in *List(...)
153 l[#l+1] = x
154 return l
157 compliments = setmetatable({}, {__mode:'k'})
158 _undict_mt =
159 __type: "an Inverse Dict"
160 __index: (k)=> not compliments[@][k] and true or nil
161 __newindex: (k,v)=>
162 if k
163 compliments[@][k] = nil
164 else
165 compliments[@][k] = true
166 __eq: (other)=>
167 unless type(other) == 'table' and getmetatable(other) == getmetatable(@)
168 return false
169 return compliments[@] == compliments[other]
170 __len: => math.huge
171 __tostring: => "~".._dict_mt.__tostring(compliments[@])
172 as_nomsu: => "~".._dict_mt.as_nomsu(compliments[@])
173 as_lua: => "~"..__dict_mt.as_lua(compliments[@])
174 __band: (other)=>
175 if getmetatable(other) == _undict_mt
176 -- ~{x,y} & ~{y,z} == ~{x,y,z} == ~({x,y} | {y,z})
177 Undict(_dict_mt.__bor(compliments[@], compliments[other]))
178 else
179 -- ~{x,y} & {y,z} == {z} == {y,z} & ~{x,y}
180 _dict_mt.__band(other, @)
181 __bor: (other)=>
182 if getmetatable(other) == _undict_mt
183 -- ~{x,y} | ~{y,z} == ~{y} = ~({x,y} & {y,z})
184 Undict(_dict_mt.__band(compliments[@], compliments[other]))
185 else
186 -- ~{x,y} | {y,z} == ~{z} = ~({y,z} & ~{x,y})
187 Undict{k,v for k,v in pairs(compliments[@]) when not other[k]}
188 __bxor: (other)=>
189 if getmetatable(other) == _undict_mt
190 -- ~{x,y} ^ ~{y,z} == {x,z} = {x,y} ^ {y,z}
191 _dict_mt.__bxor(compliments[@], compliments[other])
192 else
193 -- ~{x,y} ^ {y,z} == ~{x} = ~({x,y} & ~{y,z})
194 Undict(_dict_mt.__band(other, @))
195 __bnot: => Dict{k,v for k,v in pairs(compliments[@])}
197 Undict = (d)->
198 u = setmetatable({}, _undict_mt)
199 compliments[u] = Dict{k,true for k,v in pairs(d) when v}
200 return u
202 _dict_mt =
203 __type: "a Dict"
204 __eq: (other)=>
205 unless type(other) == 'table' and getmetatable(other) == getmetatable(@)
206 return false
207 for k,v in pairs(@)
208 return false unless v == other[k]
209 for k,v in pairs(other)
210 return false unless v == @[k]
211 return true
212 __len: =>
213 n = 0
214 for _ in pairs(@) do n += 1
215 return n
216 __tostring: =>
217 "{"..concat([v == true and "."..as_nomsu(k) or ".#{k} = #{v}" for k,v in pairs @], ", ").."}"
218 as_nomsu: =>
219 "{"..concat([v == true and "."..as_nomsu(k) or ".#{as_nomsu(k)} = #{as_nomsu(v)}" for k,v in pairs @], ", ").."}"
220 as_lua: =>
221 "a_Dict{"..concat(["[ #{as_lua(k)}]= #{as_lua(v)}" for k,v in pairs @], ", ").."}"
222 __inext: (key)=>
223 nextkey, value = next(@, key)
224 if nextkey != nil
225 return nextkey, Dict{key:nextkey, value:value}
226 __band: (other)=>
227 Dict{k,v for k,v in pairs(@) when other[k]}
228 __bor: (other)=>
229 if getmetatable(other) == _undict_mt
230 return _undict_mt.__bor(other, @)
231 ret = Dict{k,v for k,v in pairs(@)}
232 for k,v in pairs(other)
233 if ret[k] == nil then ret[k] = v
234 return ret
235 __bxor: (other)=>
236 if getmetatable(other) == _undict_mt
237 return _undict_mt.__bxor(other, @)
238 ret = Dict{k,v for k,v in pairs(@)}
239 for k,v in pairs(other)
240 if ret[k] == nil then ret[k] = v
241 else ret[k] = nil
242 return ret
243 __bnot: Undict
245 __add: (other)=>
246 ret = Dict{k,v for k,v in pairs(@)}
247 for k,v in pairs(other)
248 if ret[k] == nil then ret[k] = v
249 else ret[k] += v
250 return ret
251 __sub: (other)=>
252 ret = Dict{k,v for k,v in pairs(@)}
253 for k,v in pairs(other)
254 if ret[k] == nil then ret[k] = -v
255 else ret[k] -= v
256 return ret
258 Dict = (t,more,...)->
259 local d
260 if type(t) == 'table'
261 d = setmetatable(t, _dict_mt)
262 elseif type(t) == 'function'
263 d = setmetatable({}, _dict_mt)
264 add = (...)->
265 for i=1,select('#',...) do d[select(i,...)] = true
266 add_1_eq_2 = (k, v)-> d[k] = v
267 t(add, add_1_eq_2)
268 elseif type(t) == 'thread'
269 d = setmetatable({}, _dict_mt)
270 for k,v in coroutine.wrap(t)
271 d[k] = v
272 else error("Unsupported Dict type: "..type(t))
273 if more
274 for k,v in pairs(Dict(more,...))
275 d[k] = v
276 return d
278 return {:List, :Dict}