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
.boolean() acc (adt
and "true" or "false") end
285 if nested
[adt
] then acc(tostring(adt
)); return end
289 local has_tag
= HANDLE_TAG
and valid_id(adt
.tag)
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
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?
306 if not FIX_INDENT
then indent
= current_offset
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
318 if is_id
then acc(k
); acc
" = "
319 else acc
"["; rec (k
, nested
, indent
+(FIX_INDENT
or 0)); acc
"] = " end
322 rec (v
, nested
, indent
+(FIX_INDENT
or 0))
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
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))
340 elseif not has_hash
then
341 -- Braces required, but not opened by hash-part handler yet
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))
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
366 return table.concat (acc_list
)
369 function table.print(...) return print(table.tostring(...)) end