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
106 function table.iall (f, ...)
108 local function g(...) return not f(...) end
109 return not table.iforeach(g, ...)
113 function table.iany (f, ...)
114 local function g(...) return not f(...) end
115 return not table.iall(g, ...)
118 function table.shallow_copy(x)
120 for k, v in pairs(x) do y[k]=v end
124 -- Warning, this is implementation dependent: it relies on
125 -- the fact the [next()] enumerates the array-part before the hash-part.
126 function table.cat(...)
128 for x in values{...} do
130 for _, v in ipairs(x) do table.insert(y,v) end
133 if lx>0 then k=next(x,lx) else k=next(x) end
134 while k do y[k]=x[k]; k=next(x,k) end
139 function table.deep_copy(x)
141 local function aux (x)
142 if type(x) == "table" then
144 if y then return y end
145 y = { }; tracker[x] = y
146 setmetatable (y, getmetatable (x))
147 for k,v in pairs(x) do y[aux(k)] = aux(v) end
154 function table.override(dst, src)
155 for k, v in pairs(src) do dst[k] = v end
156 for i = #src+1, #dst do dst[i] = nil end
161 function table.range(a,b,c)
162 if not b then assert(not(c)); b=a; a=1
163 elseif not c then c = (b>=a) and 1 or -1 end
165 for i=a, b, c do table.insert(result, i) end
169 -- FIXME: new_indent seems to be always nil?!
170 -- FIXME: accumulator function should be configurable,
171 -- so that print() doesn't need to bufferize the whole string
172 -- before starting to print.
173 function table.tostring(t, ...)
174 local PRINT_HASH, HANDLE_TAG, FIX_INDENT, LINE_MAX, INITIAL_INDENT = true, true
175 for _, x in ipairs {...} do
176 if type(x) == "number" then
177 if not LINE_MAX then LINE_MAX = x
178 else INITIAL_INDENT = x end
179 elseif x=="nohash" then PRINT_HASH = false
180 elseif x=="notag" then HANDLE_TAG = false
182 local n = string['match'](x, "^indent%s*(%d*)$")
183 if n then FIX_INDENT = tonumber(n) or 3 end
186 LINE_MAX = LINE_MAX or math.huge
187 INITIAL_INDENT = INITIAL_INDENT or 1
189 local current_offset = 0 -- indentation level
190 local xlen_cache = { } -- cached results for xlen()
191 local acc_list = { } -- Generated bits of string
192 local function acc(...) -- Accumulate a bit of string
193 local x = table.concat{...}
194 current_offset = current_offset + #x
195 table.insert(acc_list, x)
197 local function valid_id(x)
198 -- FIXME: we should also reject keywords; but the list of
199 -- current keywords is not fixed in metalua...
200 return type(x) == "string"
201 and string['match'](x, "^[a-zA-Z_][a-zA-Z0-9_]*$")
204 -- Compute the number of chars it would require to display the table
205 -- on a single line. Helps to decide whether some carriage returns are
206 -- required. Since the size of each sub-table is required many times,
207 -- it's cached in [xlen_cache].
208 local xlen_type = { }
209 local function xlen(x, nested)
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
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, nested)
232 -- Circular references detection
233 if nested [adt] then return #tostring(adt) end
236 local has_tag = HANDLE_TAG and valid_id(adt.tag)
238 local has_arr = alen>0
239 local has_hash = false
243 -- first pass: count hash-part
244 for k, v in pairs(adt) do
245 if k=="tag" and has_tag then
246 -- this is the tag -> do nothing!
247 elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 then
248 -- array-part pair -> do nothing!
251 if valid_id(k) then x=x+#k
252 else x = x + xlen (k, nested) + 2 end -- count surrounding brackets
253 x = x + xlen (v, nested) + 5 -- count " = " and ", "
258 for i = 1, alen do x = x + xlen (adt[i], nested) + 2 end -- count ", "
260 nested[adt] = false -- No more nested calls
262 if not (has_tag or has_arr or has_hash) then return 3 end
263 if has_tag then x=x+#adt.tag+1 end
264 if not (has_arr or has_hash) then return x end
265 if not has_hash and alen==1 and type(adt[1])~="table" then
266 return x-2 -- substract extraneous ", "
268 return x+2 -- count "{ " and " }", substract extraneous ", "
271 -- Recursively print a (sub) table at given indentation level.
272 -- [newline] indicates whether newlines should be inserted.
273 local function rec (adt, nested, indent)
274 if not FIX_INDENT then indent = current_offset end
275 local function acc_newline()
276 acc ("\n"); acc (string.rep (" ", indent))
277 current_offset = indent
280 x["nil"] = function() acc "nil" end
281 function x.number() acc (tostring (adt)) end
282 --function x.string() acc (string.format ("%q", adt)) end
283 function x.string() acc ((string.format ("%q", adt):gsub("\\\n", "\\n"))) end
284 function x.boolean() acc (adt and "true" or "false") end
286 if nested[adt] then acc(tostring(adt)); return end
290 local has_tag = HANDLE_TAG and valid_id(adt.tag)
292 local has_arr = alen>0
293 local has_hash = false
295 if has_tag then acc("`"); acc(adt.tag) end
297 -- First pass: handle hash-part
299 for k, v in pairs(adt) do
300 -- pass if the key belongs to the array-part or is the "tag" field
301 if not (k=="tag" and HANDLE_TAG) and
302 not (type(k)=="number" and k<=alen and math.fmod(k,1)==0) then
304 -- Is it the first time we parse a hash pair?
307 if not FIX_INDENT then indent = current_offset end
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, nested) + #" = , "
313 else expected_len = xlen (k, nested) +
314 xlen (v, 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, nested, indent+(FIX_INDENT or 0)); acc "] = " end
323 rec (v, nested, indent+(FIX_INDENT or 0))
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
334 assert (has_hash or has_arr)
335 local no_brace = false
336 if has_hash and has_arr then acc ", "
337 elseif has_tag and not has_hash and alen==1 and type(adt[1])~="table" then
338 -- No brace required; don't print "{", remember not to print "}"
339 acc (" "); rec (adt[1], nested, indent+(FIX_INDENT or 0))
341 elseif not has_hash then
342 -- Braces required, but not opened by hash-part handler yet
344 if not FIX_INDENT then indent = current_offset end
347 -- 2nd pass: array-part
348 if not no_brace and has_arr then
349 rec (adt[1], nested, indent+(FIX_INDENT or 0))
352 if current_offset + xlen (adt[i], { }) > LINE_MAX
353 then acc_newline() end
354 rec (adt[i], nested, indent+(FIX_INDENT or 0))
357 if not no_brace then acc " }" end
359 nested[adt] = false -- No more nested calls
361 local y = x[type(adt)]
362 if y then y() else acc(tostring(adt)) end
364 --printf("INITIAL_INDENT = %i", INITIAL_INDENT)
365 current_offset = INITIAL_INDENT or 0
367 return table.concat (acc_list)
370 function table.print(...) return print(table.tostring(...)) end