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
= 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
220 -- optim: no need to compute lengths if I'm not going to use them
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
234 local has_tag
= HANDLE_TAG
and valid_id(adt
.tag)
236 local has_arr
= alen
>0
237 local has_hash
= false
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!
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
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
283 if nested
[adt
] then acc(tostring(adt
)); return end
287 local has_tag
= HANDLE_TAG
and valid_id(adt
.tag)
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
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?
305 if not FIX_INDENT
then indent
= current_offset
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
317 if is_id
then acc(k
); acc
" = "
318 else acc
"["; rec (k
, nested
, indent
+(FIX_INDENT
or 0)); acc
"] = " end
321 rec (v
, nested
, indent
+(FIX_INDENT
or 0))
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
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))
339 elseif not has_hash
then
340 -- Braces required, but not opened by hash-part handler yet
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))
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
365 return table.concat (acc_list
)
368 function table.print(...) return print(table.tostring(...)) end