1 ----------------------------------------------------------------------
2 ----------------------------------------------------------------------
4 -- Table module extension
6 ----------------------------------------------------------------------
7 ----------------------------------------------------------------------
9 -- todo: table.scan (scan1?) fold1? flip?
11 function table.transpose(t)
13 for a, b in pairs(t) do tt[b] = a end
17 function table.iforeach(f, ...)
18 -- assert (type (f) == "function") [wouldn't allow metamethod __call]
19 local nargs = select("#", ...)
20 if nargs==1 then -- Quick iforeach (most common case), just one table arg
22 assert (type (t) == "table")
24 local result = f (t[i])
25 -- If the function returns non-false, stop iteration
26 if result then return result end
28 else -- advanced case: boundaries and/or multiple tables
29 -- 1 - find boundaries if any
30 local args, fargs, first, last, arg1 = {...}, { }
31 if type(args[1]) ~= "number" then first, arg1 = 1, 1
32 elseif type(args[2]) ~= "number" then first, last, arg1 = 1, args[1], 2
33 else first, last, i = args[1], args[2], 3 end
35 -- 2 - determine upper boundary if not given
36 if not last then for i = arg1, nargs do
37 assert (type (args[i]) == "table")
38 last = max (#args[i], last)
40 -- 3 - perform the iteration
41 for i = first, last do
42 for j = arg1, nargs do fargs[j] = args[j][i] end -- build args list
43 local result = f (unpack (fargs)) -- here is the call
44 -- If the function returns non-false, stop iteration
45 if result then return result end
50 function table.imap (f, ...)
51 local result, idx = { }, 1
52 local function g(...) result[idx] = f(...); idx=idx+1 end
53 table.iforeach(g, ...)
57 function table.ifold (f, acc, ...)
58 local function g(...) acc = f (acc,...) end
59 table.iforeach (g, ...)
63 -- function table.ifold1 (f, ...)
64 -- return table.ifold (f, acc, 2, false, ...)
67 function table.izip(...)
68 local function g(...) return {...} end
69 return table.imap(g, ...)
72 function table.ifilter(f, t)
73 local yes, no = { }, { }
74 for i=1,#t do table.insert (f(t[i]) and yes or no, t[i]) end
78 function table.icat(...)
80 for t in values {...} do
81 for x in values (t) do
82 table.insert (result, x)
88 function table.iflatten (x) return table.icat (unpack (x)) end
90 function table.irev (t)
91 local result, nt = { }, #t
92 for i=0, nt-1 do result[nt-i] = t[i+1] end
96 function table.isub (t, ...)
97 local ti, u = table.insert, { }
98 local args, nargs = {...}, select("#", ...)
100 local a, b = args[2*i-1], args[2*i]
101 for i=a, b, a<=b and 1 or -1 do ti(u, t[i]) end
107 function table.iall (f, ...)
109 local function g(...) return not f(...) end
110 table.iforeach(g, ...)
114 function table.iany (f, ...)
115 local function g(...) return not f(...) end
116 return table.iall(g, ...)
120 function table.inverse (t)
122 for a, b in pairs(t) do i[b]=a end
126 function table.shallow_copy(x)
128 for k, v in pairs(x) do y[k]=v end
132 -- Warning, this is implementation dependent: it relies on
133 -- the fact the [next()] enumerates the array-part before the hash-part.
134 function table.cat(...)
136 for x in values{...} do
138 for _, v in ipairs(x) do table.insert(y,v) end
141 if lx>0 then k=next(x,lx) else k=next(x) end
142 while k do y[k]=x[k]; k=next(x,k) end
147 function table.deep_copy(x)
149 local function aux (x)
150 if type(x) == "table" then
152 if y then return y end
153 y = { }; tracker[x] = y
154 setmetatable (y, getmetatable (x))
155 for k,v in pairs(x) do y[aux(k)] = aux(v) end
162 function table.override(dst, src)
163 for k, v in pairs(src) do dst[k] = v end
164 for i = #src+1, #dst do dst[i] = nil end
169 function table.range(a,b,c)
170 if not b then assert(not(c)); b=a; a=1
171 elseif not c then c = (b>=a) and 1 or -1 end
173 for i=a, b, c do table.insert(result, i) end
177 function table.tostring(t, ...)
178 local PRINT_HASH, LINE_MAX, INITIAL_INDENT = true
179 for _, x in ipairs {...} do
180 if type(x) == "number" then
181 if not LINE_MAX then LINE_MAX = x
182 else INITIAL_INDENT = x end
183 elseif x=="nohash" then PRINT_HASH = false
186 LINE_MAX = LINE_MAX or math.huge
187 INITIAL_INDENT = INITIAL_INDENT or 1
190 local current_offset = 0 -- indentation level
191 local xlen_cache = { } -- cached results for xlen()
192 local acc_list = { } -- Generated bits of string
193 local function acc(...) -- Accumulate a bit of string
194 local x = table.concat{...}
195 current_offset = current_offset + #x
196 table.insert(acc_list, x)
198 local function valid_id(x)
199 -- FIXME: we should also reject keywords.
200 return type(x) == "string" and x:strmatch "[a-zA-Z_][a-zA-Z0-9_]*"
203 -- Compute the number of chars it would require to display the table
204 -- as a single line. Helps to decide whether some carriage returns are
205 -- required. Since the size of each sub-table is required many times,
206 -- it's cached in [xlen_cache].
207 local xlen_type = { }
208 local function xlen(x, tracker, nested)
209 tracker = tracker or { }
210 nested = nested or { }
211 if x==nil then return #"nil" end
212 --if nested[x] then return #tostring(x) end -- already done in table
213 local len = xlen_cache[x]
214 if len then return len end
215 local f = xlen_type[type(x)]
216 if not f then return #tostring(x) end
217 len = f (x, tracker, nested)
222 -- optim: no need to compute lengths if I'm not going to use them
224 if LINE_MAX == math.huge then xlen = function() return 0 end end
226 xlen_type["nil"] = function() return 3 end
227 function xlen_type.number(x) return #tostring(x) end
228 function xlen_type.boolean(x) return x and 4 or 5 end
229 function xlen_type.string(x) return #string.format("%q",x) end
230 function xlen_type.table (adt, tracker, nested)
232 -- Circular references detection
233 if nested[adt] then return #tostring(adt) end
234 tracker = table.shallow_copy(tracker)
238 local has_tag = valid_id(adt.tag)
240 local has_arr = alen>0
241 local has_hash = false
245 -- first pass: count hash-part
246 for k, v in pairs(adt) do
247 if k=="tag" and has_tag then
248 -- this is the tag -> do nothing!
249 elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 then
250 -- array-part pair -> do nothing!
253 if valid_id(k) then x=x+#k
254 else x = x + xlen (k, tracker, nested) + 2 end -- count surrounding barckets
255 x = x + xlen (v, tracker, nested) + 5 -- count " = " and ", "
260 for i = 1, alen do x = x + xlen (adt[i], tracker, nested) + 2 end -- count ", "
262 nested[adt] = false -- No more nested calls
264 if not (has_tag or has_arr or has_hash) then return 3 end
265 if has_tag then x=x+#adt.tag+1 end
266 if not (has_arr or has_hash) then return x end
267 if not has_hash and alen==1 and type(adt[1])~="table" then
268 return x-2 -- substract extraneous ", "
270 return x+2 -- count "{ " and " }", substract extraneous ", "
273 -- Recursively print a (sub) table at given indentation level.
274 -- [newline] indicates whether newlines should be inserted.
275 local function rec (adt, indent, tracker, nested)
276 local function acc_newline()
277 acc ("\n"); acc (string.rep (" ", indent))
278 current_offset = indent
281 x["nil"] = function() acc "nil" end
282 function x.number() acc (tostring (adt)) end
283 function x.string() acc (string.format ("%q", adt)) end
284 function x.boolean() acc (adt and "true" or "false") end
286 if nested[adt] then acc(tostring(adt)); return end
291 local has_tag = valid_id(adt.tag)
293 local has_arr = alen>0
294 local has_hash = false
296 if has_tag then acc("`"); acc(adt.tag) end
298 -- First pass: handle hash-part
300 for k, v in pairs(adt) do
301 if k=="tag" and has_tag then -- this is the tag -> do nothing!
302 elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 then
303 -- nothing: this an array-part pair, parsed later
304 else -- hash-part pair
306 -- Is it the first time we parse a hash pair?
307 if not has_hash then acc "{ "; indent = current_offset
310 -- Determine whether a newline is required
311 local is_id, expected_len = valid_id(k)
312 if is_id then expected_len = #k + xlen (v, tracker, nested) + #" = , "
313 else expected_len = xlen (k, tracker, nested) +
314 xlen (v, tracker, nested) + #"[] = , " end
315 if has_hash and expected_len + current_offset > LINE_MAX
316 then acc_newline() end
319 if is_id then acc(k); acc " = "
320 else acc "["; rec (k, current_offset, tracker, nested); acc "] = " end
323 rec (v, current_offset, tracker, nested)
329 -- now we know whether there's a hash-part, an array-part, and a tag.
330 -- Tag and hash-part are already printed if they're present.
331 if not has_tag and not has_hash and not has_arr then acc "{ }";
332 elseif has_tag and not has_hash and not has_arr then -- nothing, tag already in acc
333 else -- has_hash or has_arr
334 local no_brace = false
335 if has_hash and has_arr then acc ", "
336 elseif has_tag and not has_hash and alen==1 and type(adt[1])~="table" then
337 -- No brace required; don't print "{", remember not to print "}"
338 acc (" "); rec (adt[1], new_indent, tracker, nested)
340 elseif not has_hash then
341 -- Braces required, but not opened by hash-part handler yet
342 acc "{ "; indent = current_offset
345 -- 2nd pass: array-part
346 if not no_brace and has_arr then
347 rec (adt[1], new_indent, tracker, nested)
350 if current_offset + xlen (adt[i], { }) > LINE_MAX
351 then acc_newline() end
352 rec (adt[i], new_indent, tracker, nested)
355 if not no_brace then acc " }" end
357 nested[adt] = false -- No more nested calls
359 local y = x[type(adt)]
360 if y then y() else acc(tostring(adt)) end
362 --printf("INITIAL_INDENT = %i", INITIAL_INDENT)
363 rec(t, INITIAL_INDENT, { }, { })
364 return table.concat (acc_list)
367 function table.print(...) return print(table.tostring(...)) end