Big table.tostring() overhaul: removed some dead cruft (new_indent...) and added...
[metalua.git] / src / lib / table2.lua
blobff06ab921769f232f22fadd197d5035181374eab
1 ----------------------------------------------------------------------
2 ----------------------------------------------------------------------
3 --
4 -- Table module extension
5 --
6 ----------------------------------------------------------------------
7 ----------------------------------------------------------------------
9 -- todo: table.scan (scan1?) fold1? flip?
11 function table.transpose(t)
12 local tt = { }
13 for a, b in pairs(t) do tt[b] = a end
14 return tt
15 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
21 local t = ...
22 assert (type (t) == "table")
23 for i = 1, #t do
24 local result = f (t[i])
25 -- If the function returns non-false, stop iteration
26 if result then return result end
27 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
34 assert (nargs > arg1)
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)
39 end end
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
46 end
47 end
48 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, ...)
54 return result
55 end
57 function table.ifold (f, acc, ...)
58 local function g(...) acc = f (acc,...) end
59 table.iforeach (g, ...)
60 return acc
61 end
63 -- function table.ifold1 (f, ...)
64 -- return table.ifold (f, acc, 2, false, ...)
65 -- end
67 function table.izip(...)
68 local function g(...) return {...} end
69 return table.imap(g, ...)
70 end
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
75 return yes, no
76 end
78 function table.icat(...)
79 local result = { }
80 for t in values {...} do
81 for x in values (t) do
82 table.insert (result, x)
83 end
84 end
85 return result
86 end
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
93 return result
94 end
96 function table.isub (t, ...)
97 local ti, u = table.insert, { }
98 local args, nargs = {...}, select("#", ...)
99 for i=1, nargs/2 do
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
103 return u
106 function table.iall (f, ...)
107 local result = true
108 local function g(...) return not f(...) end
109 return not table.iforeach(g, ...)
110 --return result
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)
119 local y={ }
120 for k, v in pairs(x) do y[k]=v end
121 return y
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(...)
127 local y={ }
128 for x in values{...} do
129 -- cat array-part
130 for _, v in ipairs(x) do table.insert(y,v) end
131 -- cat hash-part
132 local lx, k = #x
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
136 return y
139 function table.deep_copy(x)
140 local tracker = { }
141 local function aux (x)
142 if type(x) == "table" then
143 local y=tracker[x]
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
148 return y
149 else return x end
151 return aux(x)
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
157 return dst
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
164 local result = { }
165 for i=a, b, c do table.insert(result, i) end
166 return result
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
181 else
182 local n = x :strmatch "^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.
199 return type(x) == "string" and x:strmatch "[a-zA-Z_][a-zA-Z0-9_]*"
202 -- Compute the number of chars it would require to display the table
203 -- on a single line. Helps to decide whether some carriage returns are
204 -- required. Since the size of each sub-table is required many times,
205 -- it's cached in [xlen_cache].
206 local xlen_type = { }
207 local function xlen(x, nested)
208 nested = nested or { }
209 if x==nil then return #"nil" end
210 --if nested[x] then return #tostring(x) end -- already done in table
211 local len = xlen_cache[x]
212 if len then return len end
213 local f = xlen_type[type(x)]
214 if not f then return #tostring(x) end
215 len = f (x, nested)
216 xlen_cache[x] = len
217 return len
220 -- optim: no need to compute lengths if I'm not going to use them
221 -- anyway.
222 if LINE_MAX == math.huge then xlen = function() return 0 end end
224 xlen_type["nil"] = function () return 3 end
225 function xlen_type.number (x) return #tostring(x) end
226 function xlen_type.boolean (x) return x and 4 or 5 end
227 function xlen_type.string (x) return #string.format("%q",x) end
228 function xlen_type.table (adt, nested)
230 -- Circular references detection
231 if nested [adt] then return #tostring(adt) end
232 nested [adt] = true
234 local has_tag = HANDLE_TAG and valid_id(adt.tag)
235 local alen = #adt
236 local has_arr = alen>0
237 local has_hash = false
238 local x = 0
240 if PRINT_HASH then
241 -- first pass: count hash-part
242 for k, v in pairs(adt) do
243 if k=="tag" and has_tag then
244 -- this is the tag -> do nothing!
245 elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 then
246 -- array-part pair -> do nothing!
247 else
248 has_hash = true
249 if valid_id(k) then x=x+#k
250 else x = x + xlen (k, nested) + 2 end -- count surrounding brackets
251 x = x + xlen (v, nested) + 5 -- count " = " and ", "
256 for i = 1, alen do x = x + xlen (adt[i], nested) + 2 end -- count ", "
258 nested[adt] = false -- No more nested calls
260 if not (has_tag or has_arr or has_hash) then return 3 end
261 if has_tag then x=x+#adt.tag+1 end
262 if not (has_arr or has_hash) then return x end
263 if not has_hash and alen==1 and type(adt[1])~="table" then
264 return x-2 -- substract extraneous ", "
266 return x+2 -- count "{ " and " }", substract extraneous ", "
269 -- Recursively print a (sub) table at given indentation level.
270 -- [newline] indicates whether newlines should be inserted.
271 local function rec (adt, nested, indent)
272 if not FIX_INDENT then indent = current_offset end
273 local function acc_newline()
274 acc ("\n"); acc (string.rep (" ", indent))
275 current_offset = indent
277 local x = { }
278 x["nil"] = function() acc "nil" end
279 function x.number() acc (tostring (adt)) end
280 function x.string() acc (string.format ("%q", adt)) end
281 function x.boolean() acc (adt and "true" or "false") end
282 function x.table()
283 if nested[adt] then acc(tostring(adt)); return end
284 nested[adt] = true
287 local has_tag = HANDLE_TAG and valid_id(adt.tag)
288 local alen = #adt
289 local has_arr = alen>0
290 local has_hash = false
292 if has_tag then acc("`"); acc(adt.tag) end
294 -- First pass: handle hash-part
295 if PRINT_HASH then
296 for k, v in pairs(adt) do
297 if k=="tag" and has_tag then -- this is the tag -> do nothing!
298 elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 then
299 -- nothing: this an array-part pair, parsed later
300 else -- hash-part pair
302 -- Is it the first time we parse a hash pair?
303 if not has_hash then
304 acc "{ "
305 if not FIX_INDENT then indent = current_offset end
306 else acc ", " end
308 -- Determine whether a newline is required
309 local is_id, expected_len = valid_id(k)
310 if is_id then expected_len = #k + xlen (v, nested) + #" = , "
311 else expected_len = xlen (k, nested) +
312 xlen (v, nested) + #"[] = , " end
313 if has_hash and expected_len + current_offset > LINE_MAX
314 then acc_newline() end
316 -- Print the key
317 if is_id then acc(k); acc " = "
318 else acc "["; rec (k, nested, indent+(FIX_INDENT or 0)); acc "] = " end
320 -- Print the value
321 rec (v, nested, indent+(FIX_INDENT or 0))
322 has_hash = true
327 -- Now we know whether there's a hash-part, an array-part, and a tag.
328 -- Tag and hash-part are already printed if they're present.
329 if not has_tag and not has_hash and not has_arr then acc "{ }";
330 elseif has_tag and not has_hash and not has_arr then -- nothing, tag already in acc
331 else
332 assert (has_hash or has_arr)
333 local no_brace = false
334 if has_hash and has_arr then acc ", "
335 elseif has_tag and not has_hash and alen==1 and type(adt[1])~="table" then
336 -- No brace required; don't print "{", remember not to print "}"
337 acc (" "); rec (adt[1], nested, indent+(FIX_INDENT or 0))
338 no_brace = true
339 elseif not has_hash then
340 -- Braces required, but not opened by hash-part handler yet
341 acc "{ "
342 if not FIX_INDENT then indent = current_offset end
345 -- 2nd pass: array-part
346 if not no_brace and has_arr then
347 rec (adt[1], nested, indent+(FIX_INDENT or 0))
348 for i=2, alen do
349 acc ", ";
350 if current_offset + xlen (adt[i], { }) > LINE_MAX
351 then acc_newline() end
352 rec (adt[i], nested, indent+(FIX_INDENT or 0))
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 current_offset = INITIAL_INDENT or 0
364 rec(t, { }, 0)
365 return table.concat (acc_list)
368 function table.print(...) return print(table.tostring(...)) end
370 return table