variable capture bug in match extension
[metalua.git] / src / lib / table2.lua
blobc321282673e309c8ac1892ae46ce281eb21c1b8a
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 = 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
217 len = f (x, nested)
218 xlen_cache[x] = len
219 return len
222 -- optim: no need to compute lengths if I'm not going to use them
223 -- anyway.
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
234 nested [adt] = true
236 local has_tag = HANDLE_TAG and valid_id(adt.tag)
237 local alen = #adt
238 local has_arr = alen>0
239 local has_hash = false
240 local x = 0
242 if PRINT_HASH then
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!
249 else
250 has_hash = true
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
279 local x = { }
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.boolean() acc (adt and "true" or "false") end
284 function x.table()
285 if nested[adt] then acc(tostring(adt)); return end
286 nested[adt] = true
289 local has_tag = HANDLE_TAG and valid_id(adt.tag)
290 local alen = #adt
291 local has_arr = alen>0
292 local has_hash = false
294 if has_tag then acc("`"); acc(adt.tag) end
296 -- First pass: handle hash-part
297 if PRINT_HASH then
298 for k, v in pairs(adt) do
299 -- pass if the key belongs to the array-part or is the "tag" field
300 if not (k=="tag" and HANDLE_TAG) and
301 not (type(k)=="number" and k<=alen and math.fmod(k,1)==0) then
303 -- Is it the first time we parse a hash pair?
304 if not has_hash then
305 acc "{ "
306 if not FIX_INDENT then indent = current_offset end
307 else acc ", " end
309 -- Determine whether a newline is required
310 local is_id, expected_len = valid_id(k)
311 if is_id then expected_len = #k + xlen (v, nested) + #" = , "
312 else expected_len = xlen (k, nested) +
313 xlen (v, nested) + #"[] = , " end
314 if has_hash and expected_len + current_offset > LINE_MAX
315 then acc_newline() end
317 -- Print the key
318 if is_id then acc(k); acc " = "
319 else acc "["; rec (k, nested, indent+(FIX_INDENT or 0)); acc "] = " end
321 -- Print the value
322 rec (v, nested, indent+(FIX_INDENT or 0))
323 has_hash = true
328 -- Now we know whether there's a hash-part, an array-part, and a tag.
329 -- Tag and hash-part are already printed if they're present.
330 if not has_tag and not has_hash and not has_arr then acc "{ }";
331 elseif has_tag and not has_hash and not has_arr then -- nothing, tag already in acc
332 else
333 assert (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], nested, indent+(FIX_INDENT or 0))
339 no_brace = true
340 elseif not has_hash then
341 -- Braces required, but not opened by hash-part handler yet
342 acc "{ "
343 if not FIX_INDENT then indent = current_offset end
346 -- 2nd pass: array-part
347 if not no_brace and has_arr then
348 rec (adt[1], nested, indent+(FIX_INDENT or 0))
349 for i=2, alen do
350 acc ", ";
351 if current_offset + xlen (adt[i], { }) > LINE_MAX
352 then acc_newline() end
353 rec (adt[i], nested, indent+(FIX_INDENT or 0))
356 if not no_brace then acc " }" end
358 nested[adt] = false -- No more nested calls
360 local y = x[type(adt)]
361 if y then y() else acc(tostring(adt)) end
363 --printf("INITIAL_INDENT = %i", INITIAL_INDENT)
364 current_offset = INITIAL_INDENT or 0
365 rec(t, { }, 0)
366 return table.concat (acc_list)
369 function table.print(...) return print(table.tostring(...)) end
371 return table