(278 lines)
1 -- This file contains container classes, i.e. Lists and Dicts, plus some extended string functionality2 local List, Dict, Undict, _undict_mt, _dict_mt3 {:insert,:remove,:concat} = table5 as_nomsu = =>6 if type(@) == 'number'7 return tostring(@)8 if mt = getmetatable(@)9 if _as_nomsu = mt.as_nomsu10 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_lua18 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 tables24 -- 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 false31 for i,x in ipairs(@)32 return false unless x == other[i]33 return true34 -- Could consider adding a __newindex to enforce list-ness, but would hurt performance35 __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 true45 elseif @[i] and not other[i] then return false46 elseif @[i] < other[i] then return true47 elseif @[i] > other[i] then return false48 return false49 __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 true53 elseif @[i] and not other[i] then return false54 elseif @[i] < other[i] then return true55 elseif @[i] > other[i] then return false56 return true57 __add: (other)=>58 ret = List[x for x in *@]59 for x in *other60 insert(ret, x)61 return ret62 __index:63 add: insert, append: insert64 add_1_at_index: (t,x,i)-> insert(t,i,x)65 at_index_1_add: insert66 pop: remove, remove_last: remove, remove_index: remove67 last: (=> @[#@]), first: (=> @[1])68 _1_st_to_last:nth_to_last, _1_nd_to_last:nth_to_last69 _1_rd_to_last:nth_to_last, _1_th_to_last:nth_to_last70 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 == item75 return true76 return false77 remove: (item)=>78 for i,x in ipairs @79 if x == item80 remove(@, i)81 index_of: (item)=>82 for i,x in ipairs @83 if x == item84 return i85 return nil86 from_1_to: (start, stop)=>87 n = #@88 start = (n+1-start) if start < 089 stop = (n+1-stop) if stop < 090 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] = key105 return key106 table.sort(@, (a,b)->keys[a] <= keys[b])107 sorted: =>108 c = @copy!109 c\sort!110 return c111 sorted_by: (fn)=>112 c = @copy!113 c\sort_by(fn)114 return c115 filter_by: (keep)=>116 deleted = 0117 for i=1,#@118 unless keep(@[i])119 deleted += 1120 elseif deleted > 0121 @[i-deleted] = @[i]122 for i=#@-deleted+1,#@123 @[i] = nil125 filtered_by: (keep)=>126 c = @copy!127 c\filter_by(keep)128 return c130 -- 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_lua135 _list_mt.__index.as_nomsu = _list_mt.as_nomsu137 List = (t,...)->138 local l139 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] = val150 else error("Unsupported List type: "..type(t))151 if select(1,...)152 for x in *List(...)153 l[#l+1] = x154 return l157 compliments = setmetatable({}, {__mode:'k'})158 _undict_mt =159 __type: "an Inverse Dict"160 __index: (k)=> not compliments[@][k] and true or nil161 __newindex: (k,v)=>162 if k163 compliments[@][k] = nil164 else165 compliments[@][k] = true166 __eq: (other)=>167 unless type(other) == 'table' and getmetatable(other) == getmetatable(@)168 return false169 return compliments[@] == compliments[other]170 __len: => math.huge171 __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_mt176 -- ~{x,y} & ~{y,z} == ~{x,y,z} == ~({x,y} | {y,z})177 Undict(_dict_mt.__bor(compliments[@], compliments[other]))178 else179 -- ~{x,y} & {y,z} == {z} == {y,z} & ~{x,y}180 _dict_mt.__band(other, @)181 __bor: (other)=>182 if getmetatable(other) == _undict_mt183 -- ~{x,y} | ~{y,z} == ~{y} = ~({x,y} & {y,z})184 Undict(_dict_mt.__band(compliments[@], compliments[other]))185 else186 -- ~{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_mt190 -- ~{x,y} ^ ~{y,z} == {x,z} = {x,y} ^ {y,z}191 _dict_mt.__bxor(compliments[@], compliments[other])192 else193 -- ~{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 u202 _dict_mt =203 __type: "a Dict"204 __eq: (other)=>205 unless type(other) == 'table' and getmetatable(other) == getmetatable(@)206 return false207 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 true212 __len: =>213 n = 0214 for _ in pairs(@) do n += 1215 return n216 __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 != nil225 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_mt230 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] = v234 return ret235 __bxor: (other)=>236 if getmetatable(other) == _undict_mt237 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] = v241 else ret[k] = nil242 return ret243 __bnot: Undict245 __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] = v249 else ret[k] += v250 return ret251 __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] = -v255 else ret[k] -= v256 return ret258 Dict = (t,more,...)->259 local d260 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,...)] = true266 add_1_eq_2 = (k, v)-> d[k] = v267 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] = v272 else error("Unsupported Dict type: "..type(t))273 if more274 for k,v in pairs(Dict(more,...))275 d[k] = v276 return d278 return {:List, :Dict}