l-table.lua /size: 41 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['l-table'] = {
2    version   = 1.001,
3    comment   = "companion to luat-lib.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9local type, next, tostring, tonumber, select, rawget = type, next, tostring, tonumber, select, rawget
10local table, string = table, string
11local concat, sort = table.concat, table.sort
12local format, lower, dump = string.format, string.lower, string.dump
13local getmetatable, setmetatable = getmetatable, setmetatable
14local lpegmatch, patterns = lpeg.match, lpeg.patterns
15local floor = math.floor
16
17-- extra functions, some might go (when not used)
18--
19-- we could serialize using %a but that won't work well is in the code we mostly use
20-- floats and as such we get unequality e.g. in version comparisons
21
22local stripper = patterns.stripper
23
24function table.getn(t)
25    return t and #t -- for very old times sake
26end
27
28function table.strip(tab)
29    local lst = { }
30    local l   = 0
31    for i=1,#tab do
32        local s = lpegmatch(stripper,tab[i]) or ""
33        if s == "" then
34            -- skip this one
35        else
36            l = l + 1
37            lst[l] = s
38        end
39    end
40    return lst
41end
42
43function table.keys(t)
44    if t then
45        local keys = { }
46        local k    = 0
47        for key in next, t do
48            k = k + 1
49            keys[k] = key
50        end
51        return keys
52    else
53        return { }
54    end
55end
56
57-- local function compare(a,b)
58--     local ta = type(a) -- needed, else 11 < 2
59--     local tb = type(b) -- needed, else 11 < 2
60--     if ta == tb and ta == "number" then
61--         return a < b
62--     else
63--         return tostring(a) < tostring(b) -- not that efficient
64--     end
65-- end
66
67-- local function compare(a,b)
68--     local ta = type(a) -- needed, else 11 < 2
69--     local tb = type(b) -- needed, else 11 < 2
70--     if ta == tb and (ta == "number" or ta == "string") then
71--         return a < b
72--     else
73--         return tostring(a) < tostring(b) -- not that efficient
74--     end
75-- end
76
77-- local function sortedkeys(tab)
78--     if tab then
79--         local srt, category, s = { }, 0, 0 -- 0=unknown 1=string, 2=number 3=mixed
80--         for key in next, tab do
81--             s = s + 1
82--             srt[s] = key
83--             if category == 3 then
84--                 -- no further check
85--             else
86--                 local tkey = type(key)
87--                 if tkey == "string" then
88--                     category = (category == 2 and 3) or 1
89--                 elseif tkey == "number" then
90--                     category = (category == 1 and 3) or 2
91--                 else
92--                     category = 3
93--                 end
94--             end
95--         end
96--         if category == 0 or category == 3 then
97--             sort(srt,compare)
98--         else
99--             sort(srt)
100--         end
101--         return srt
102--     else
103--         return { }
104--     end
105-- end
106
107-- local function compare(a,b)
108--     local ta = type(a) -- needed, else 11 < 2
109--     local tb = type(b) -- needed, else 11 < 2
110--     if ta == tb and (ta == "number" or ta == "string") then
111--         return a < b
112--     else
113--         return tostring(a) < tostring(b) -- not that efficient
114--     end
115-- end
116
117-- local function compare(a,b)
118--     local ta = type(a) -- needed, else 11 < 2
119--     if ta == "number" or ta == "string" then
120--         local tb = type(b) -- needed, else 11 < 2
121--         if ta == tb then
122--             return a < b
123--         end
124--     end
125--     return tostring(a) < tostring(b) -- not that efficient
126-- end
127
128local function compare(a,b)
129    local ta = type(a) -- needed, else 11 < 2
130    if ta == "number" then
131        local tb = type(b) -- needed, else 11 < 2
132        if ta == tb then
133            return a < b
134        elseif tb == "string" then
135            return tostring(a) < b
136        end
137    elseif ta == "string" then
138        local tb = type(b) -- needed, else 11 < 2
139        if ta == tb then
140            return a < b
141        else
142            return a < tostring(b)
143        end
144    end
145    return tostring(a) < tostring(b) -- not that efficient
146end
147
148local function sortedkeys(tab)
149    if tab then
150        local srt      = { }
151        local category = 0 -- 0=unknown 1=string, 2=number 3=mixed
152        local s        = 0
153        for key in next, tab do
154            s = s + 1
155            srt[s] = key
156            if category ~= 3 then
157                local tkey = type(key)
158                if category == 1 then
159                    if tkey ~= "string" then
160                        category = 3
161                    end
162                elseif category == 2 then
163                    if tkey ~= "number" then
164                        category = 3
165                    end
166                else
167                    if tkey == "string" then
168                        category = 1
169                    elseif tkey == "number" then
170                        category = 2
171                    else
172                        category = 3
173                    end
174                end
175            end
176        end
177        if s < 2 then
178            -- nothing to sort
179        elseif category == 3 then
180            sort(srt,compare)
181        else
182            sort(srt)
183        end
184        return srt
185    else
186        return { }
187    end
188end
189
190local function sortedhashonly(tab)
191    if tab then
192        local srt = { }
193        local s   = 0
194        for key in next, tab do
195            if type(key) == "string" then
196                s = s + 1
197                srt[s] = key
198            end
199        end
200        if s > 1 then
201            sort(srt)
202        end
203        return srt
204    else
205        return { }
206    end
207end
208
209local function sortedindexonly(tab)
210    if tab then
211        local srt = { }
212        local s   = 0
213        for key in next, tab do
214            if type(key) == "number" then
215                s = s + 1
216                srt[s] = key
217            end
218        end
219        if s > 1 then
220            sort(srt)
221        end
222        return srt
223    else
224        return { }
225    end
226end
227
228local function sortedhashkeys(tab,cmp) -- fast one
229    if tab then
230        local srt = { }
231        local s   = 0
232        for key in next, tab do
233            if key then
234                s= s + 1
235                srt[s] = key
236            end
237        end
238        if s > 1 then
239            sort(srt,cmp)
240        end
241        return srt
242    else
243        return { }
244    end
245end
246
247function table.allkeys(t)
248    local keys = { }
249    for k, v in next, t do
250        for k in next, v do
251            keys[k] = true
252        end
253    end
254    return sortedkeys(keys)
255end
256
257table.sortedkeys      = sortedkeys
258table.sortedhashonly  = sortedhashonly
259table.sortedindexonly = sortedindexonly
260table.sortedhashkeys  = sortedhashkeys
261
262local function nothing() end
263
264local function sortedhash(t,cmp)
265    if t then
266        local s
267        if cmp then
268            -- it would be nice if the sort function would accept a third argument (or nicer, an optional first)
269            s = sortedhashkeys(t,function(a,b) return cmp(t,a,b) end)
270        else
271            s = sortedkeys(t) -- the robust one
272        end
273        local m = #s
274        if m == 1 then
275            return next, t
276        elseif m > 0 then
277            local n = 0
278            return function()
279                if n < m then
280                    n = n + 1
281                    local k = s[n]
282                    return k, t[k]
283                end
284            end
285        end
286    end
287    return nothing
288end
289
290-- local function iterate(t,i)
291--     local i = i + 1
292--     if i <= t.n then
293--         local k = t[i]
294--         return i, k, t.t[k]
295--     end
296-- end
297--
298-- local function indexedhash(t,cmp)
299--     if t then
300--         local s
301--         if cmp then
302--             -- it would be nice if the sort function would accept a third argument (or nicer, an optional first)
303--             s = sortedhashkeys(t,function(a,b) return cmp(t,a,b) end)
304--         else
305--             s = sortedkeys(t) -- the robust one
306--         end
307--         local m = #s
308--         if m == 1 then
309--             return next, t
310--         elseif m > 0 then
311--             s.n = m
312--             s.t = t
313--             return iterate, s, 0
314--         end
315--     end
316--     return nothing
317-- end
318--
319-- -- for i, k, v in indexedhash(t) do print(k,v,s) end
320
321table.sortedhash  = sortedhash
322table.sortedpairs = sortedhash -- obsolete
323
324function table.append(t,list)
325    local n = #t
326    for i=1,#list do
327        n = n + 1
328        t[n] = list[i]
329    end
330    return t
331end
332
333function table.prepend(t, list)
334    local nl = #list
335    local nt = nl + #t
336    for i=#t,1,-1 do
337        t[nt] = t[i]
338        nt = nt - 1
339    end
340    for i=1,#list do
341        t[i] = list[i]
342    end
343    return t
344end
345
346-- function table.merge(t, ...) -- first one is target
347--     t = t or { }
348--     local lst = { ... }
349--     for i=1,#lst do
350--         for k, v in next, lst[i] do
351--             t[k] = v
352--         end
353--     end
354--     return t
355-- end
356
357function table.merge(t, ...) -- first one is target
358    if not t then
359        t = { }
360    end
361    for i=1,select("#",...) do
362        for k, v in next, (select(i,...)) do
363            t[k] = v
364        end
365    end
366    return t
367end
368
369-- function table.merged(...)
370--     local tmp, lst = { }, { ... }
371--     for i=1,#lst do
372--         for k, v in next, lst[i] do
373--             tmp[k] = v
374--         end
375--     end
376--     return tmp
377-- end
378
379function table.merged(...)
380    local t = { }
381    for i=1,select("#",...) do
382        for k, v in next, (select(i,...)) do
383            t[k] = v
384        end
385    end
386    return t
387end
388
389-- function table.imerge(t, ...)
390--     local lst, nt = { ... }, #t
391--     for i=1,#lst do
392--         local nst = lst[i]
393--         for j=1,#nst do
394--             nt = nt + 1
395--             t[nt] = nst[j]
396--         end
397--     end
398--     return t
399-- end
400
401function table.imerge(t, ...)
402    local nt = #t
403    for i=1,select("#",...) do
404        local nst = select(i,...)
405        for j=1,#nst do
406            nt = nt + 1
407            t[nt] = nst[j]
408        end
409    end
410    return t
411end
412
413-- function table.imerged(...)
414--     local tmp, ntmp, lst = { }, 0, {...}
415--     for i=1,#lst do
416--         local nst = lst[i]
417--         for j=1,#nst do
418--             ntmp = ntmp + 1
419--             tmp[ntmp] = nst[j]
420--         end
421--     end
422--     return tmp
423-- end
424
425function table.imerged(...)
426    local tmp  = { }
427    local ntmp = 0
428    for i=1,select("#",...) do
429        local nst = select(i,...)
430        for j=1,#nst do
431            ntmp = ntmp + 1
432            tmp[ntmp] = nst[j]
433        end
434    end
435    return tmp
436end
437
438local function fastcopy(old,metatabletoo) -- fast one
439    if old then
440        local new = { }
441        for k, v in next, old do
442            if type(v) == "table" then
443                new[k] = fastcopy(v,metatabletoo) -- was just table.copy
444            else
445                new[k] = v
446            end
447        end
448        if metatabletoo then
449            -- optional second arg
450            local mt = getmetatable(old)
451            if mt then
452                setmetatable(new,mt)
453            end
454        end
455        return new
456    else
457        return { }
458    end
459end
460
461-- todo : copy without metatable
462
463local function copy(t,tables) -- taken from lua wiki, slightly adapted
464    if not tables then
465        tables = { }
466    end
467    local tcopy = { }
468    if not tables[t] then
469        tables[t] = tcopy
470    end
471    for i,v in next, t do -- brrr, what happens with sparse indexed
472        local k
473        if type(i) == "table" then
474            if tables[i] then
475                k = tables[i]
476            else
477                k = copy(i,tables)
478            end
479        else
480            k = i
481        end
482        if type(v) ~= "table" then
483            tcopy[k] = v
484        elseif tables[v] then
485            tcopy[k] = tables[v]
486        else
487            tcopy[k] = copy(v,tables)
488        end
489    end
490    local mt = getmetatable(t)
491    if mt then
492        setmetatable(tcopy,mt)
493    end
494    return tcopy
495end
496
497table.fastcopy = fastcopy
498table.copy     = copy
499
500function table.derive(parent) -- for the moment not public
501    local child = { }
502    if parent then
503        setmetatable(child,{ __index = parent })
504    end
505    return child
506end
507
508function table.tohash(t,value)
509    local h = { }
510    if t then
511        if value == nil then value = true end
512        for _, v in next, t do
513            h[v] = value
514        end
515    end
516    return h
517end
518
519function table.fromhash(t)
520    local hsh = { }
521    local h   = 0
522    for k, v in next, t do
523        if v then
524            h = h + 1
525            hsh[h] = k
526        end
527    end
528    return hsh
529end
530
531local noquotes, hexify, handle, compact, inline, functions, metacheck, accurate
532
533local reserved = table.tohash { -- intercept a language inconvenience: no reserved words as key
534    'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
535    'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
536    'NaN', 'goto', 'const',
537}
538
539local function is_simple_table(t,hexify,accurate) -- also used in util-tab so maybe public
540    local nt = #t
541    if nt > 0 then
542        local n = 0
543        for _, v in next, t do
544            n = n + 1
545            if type(v) == "table" then
546                return nil
547            end
548        end
549     -- local haszero = t[0]
550        local haszero = rawget(t,0) -- don't trigger meta
551        if n == nt then
552            local tt = { }
553            for i=1,nt do
554                local v = t[i]
555                local tv = type(v)
556                if tv == "number" then
557                 -- tt[i] = v -- not needed tostring(v)
558                    if hexify then
559                        tt[i] = format("0x%X",v)
560                    elseif accurate then
561                        tt[i] = format("%q",v)
562                    else
563                        tt[i] = v -- not needed tostring(v)
564                    end
565                elseif tv == "string" then
566                    tt[i] = format("%q",v) -- f_string(v)
567                elseif tv == "boolean" then
568                    tt[i] = v and "true" or "false"
569                else
570                    return nil
571                end
572            end
573            return tt
574        elseif haszero and (n == nt + 1) then
575            local tt = { }
576            for i=0,nt do
577                local v = t[i]
578                local tv = type(v)
579                if tv == "number" then
580                 -- tt[i+1] = v -- not needed tostring(v)
581                    if hexify then
582                        tt[i+1] = format("0x%X",v)
583                    elseif accurate then
584                        tt[i+1] = format("%q",v)
585                    else
586                        tt[i+1] = v -- not needed tostring(v)
587                    end
588                elseif tv == "string" then
589                    tt[i+1] = format("%q",v) -- f_string(v)
590                elseif tv == "boolean" then
591                    tt[i+1] = v and "true" or "false"
592                else
593                    return nil
594                end
595            end
596            tt[1] = "[0] = " .. tt[1]
597            return tt
598        end
599    end
600    return nil
601end
602
603table.is_simple_table = is_simple_table
604
605-- Because this is a core function of mkiv I moved some function calls
606-- inline.
607--
608-- twice as fast in a test:
609--
610-- local propername = lpeg.P(lpeg.R("AZ","az","__") * lpeg.R("09","AZ","az", "__")^0 * lpeg.P(-1) )
611
612-- problem: there no good number_to_string converter with the best resolution
613
614-- probably using .. is faster than format
615-- maybe split in a few cases (yes/no hexify)
616
617-- todo: %g faster on numbers than %s
618
619-- we can speed this up with repeaters and formatters but we haven't defined them
620-- yet
621
622local propername = patterns.propername -- was find(name,"^%a[%w%_]*$")
623
624local function dummy() end
625
626local function do_serialize(root,name,depth,level,indexed)
627    if level > 0 then
628        depth = depth .. " "
629        if indexed then
630            handle(format("%s{",depth))
631        else
632            local tn = type(name)
633            if tn == "number" then
634                if hexify then
635                    handle(format("%s[0x%X]={",depth,name))
636                else
637                    handle(format("%s[%s]={",depth,name))
638                end
639            elseif tn == "string" then
640                if noquotes and not reserved[name] and lpegmatch(propername,name) then
641                    handle(format("%s%s={",depth,name))
642                else
643                    handle(format("%s[%q]={",depth,name))
644                end
645            elseif tn == "boolean" then
646                handle(format("%s[%s]={",depth,name and "true" or "false"))
647            else
648                handle(format("%s{",depth))
649            end
650        end
651    end
652    -- we could check for k (index) being number (cardinal)
653    if root and next(root) ~= nil then
654        local first = nil
655        local last  = 0
656        if compact then
657            last = #root
658            for k=1,last do
659             -- if root[k] == nil then
660                if rawget(root,k) == nil then
661                    last = k - 1
662                    break
663                end
664            end
665            if last > 0 then
666                first = 1
667            end
668        end
669        local sk = sortedkeys(root)
670        for i=1,#sk do
671            local k  = sk[i]
672            local v  = root[k]
673            local tv = type(v)
674            local tk = type(k)
675            if compact and first and tk == "number" and k >= first and k <= last then
676                if tv == "number" then
677                    if hexify then
678                        handle(format("%s 0x%X,",depth,v))
679                    elseif accurate then
680                        handle(format("%s %q,",depth,v))
681                    else
682                        handle(format("%s %s,",depth,v)) -- %.99g
683                    end
684                elseif tv == "string" then
685                    handle(format("%s %q,",depth,v))
686                elseif tv == "table" then
687                    if next(v) == nil then
688                        handle(format("%s {},",depth))
689                    elseif inline then -- and #t > 0
690                        local st = is_simple_table(v,hexify,accurate)
691                        if st then
692                            handle(format("%s { %s },",depth,concat(st,", ")))
693                        else
694                            do_serialize(v,k,depth,level+1,true)
695                        end
696                    else
697                        do_serialize(v,k,depth,level+1,true)
698                    end
699                elseif tv == "boolean" then
700                    handle(format("%s %s,",depth,v and "true" or "false"))
701                elseif tv == "function" then
702                    if functions then
703                        handle(format('%s load(%q),',depth,dump(v))) -- maybe strip
704                    else
705                        handle(format('%s "function",',depth))
706                    end
707                else
708                    handle(format("%s %q,",depth,tostring(v)))
709                end
710            elseif k == "__p__" then -- parent
711                if false then
712                    handle(format("%s __p__=nil,",depth))
713                end
714            elseif tv == "number" then
715                if tk == "number" then
716                    if hexify then
717                        if accurate then
718                            handle(format("%s [0x%X]=%q,",depth,k,v))
719                        else
720                            handle(format("%s [0x%X]=%s,",depth,k,v))
721                        end
722                    elseif accurate then
723                        handle(format("%s [%s]=%q,",depth,k,v))
724                    else
725                        handle(format("%s [%s]=%s,",depth,k,v)) -- %.99g
726                    end
727                elseif tk == "boolean" then
728                    if hexify then
729                        if accurate then
730                            handle(format("%s [%s]=%q,",depth,k and "true" or "false",v))
731                        else
732                            handle(format("%s [%s]=%s,",depth,k and "true" or "false",v))
733                        end
734                    elseif accurate then
735                        handle(format("%s [%s]=%q,",depth,k and "true" or "false",v))
736                    else
737                        handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) -- %.99g
738                    end
739                elseif tk ~= "string" then
740                    -- ignore
741                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
742                    if hexify then
743                        if accurate then
744                            handle(format("%s %s=%q,",depth,k,v))
745                        else
746--                             handle(format("%s %s=%s,",depth,k,v))
747                            handle(format("%s %s=0x%X,",depth,k,v))
748                        end
749                    elseif accurate then
750                        handle(format("%s %s=%q,",depth,k,v))
751                    else
752                        handle(format("%s %s=%s,",depth,k,v)) -- %.99g
753                    end
754                else
755                    if hexify then
756                        if accurate then
757                            handle(format("%s [%q]=%q,",depth,k,v))
758                        else
759--                             handle(format("%s [%q]=%s,",depth,k,v))
760                            handle(format("%s [%q]=0x%X,",depth,k,v))
761                        end
762                    elseif accurate then
763                        handle(format("%s [%q]=%q,",depth,k,v))
764                    else
765                        handle(format("%s [%q]=%s,",depth,k,v)) -- %.99g
766                    end
767                end
768            elseif tv == "string" then
769                if tk == "number" then
770                    if hexify then
771                        handle(format("%s [0x%X]=%q,",depth,k,v))
772                    elseif accurate then
773                        handle(format("%s [%q]=%q,",depth,k,v))
774                    else
775                        handle(format("%s [%s]=%q,",depth,k,v))
776                    end
777                elseif tk == "boolean" then
778                    handle(format("%s [%s]=%q,",depth,k and "true" or "false",v))
779                elseif tk ~= "string" then
780                    -- ignore
781                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
782                    handle(format("%s %s=%q,",depth,k,v))
783                else
784                    handle(format("%s [%q]=%q,",depth,k,v))
785                end
786            elseif tv == "table" then
787                if next(v) == nil then
788                    if tk == "number" then
789                        if hexify then
790                            handle(format("%s [0x%X]={},",depth,k))
791                        elseif accurate then
792                            handle(format("%s [%q]={},",depth,k))
793                        else
794                            handle(format("%s [%s]={},",depth,k))
795                        end
796                    elseif tk == "boolean" then
797                        handle(format("%s [%s]={},",depth,k and "true" or "false"))
798                    elseif tk ~= "string" then
799                        -- ignore
800                    elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
801                        handle(format("%s %s={},",depth,k))
802                    else
803                        handle(format("%s [%q]={},",depth,k))
804                    end
805                elseif inline then
806                    local st = is_simple_table(v,hexify,accurate)
807                    if st then
808                        if tk == "number" then
809                            if hexify then
810                                handle(format("%s [0x%X]={ %s },",depth,k,concat(st,", ")))
811                            elseif accurate then
812                                handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
813                            else
814                                handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
815                            end
816                        elseif tk == "boolean" then
817                            handle(format("%s [%s]={ %s },",depth,k and "true" or "false",concat(st,", ")))
818                        elseif tk ~= "string" then
819                            -- ignore
820                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
821                            handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
822                        else
823                            handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
824                        end
825                    else
826                        do_serialize(v,k,depth,level+1)
827                    end
828                else
829                    do_serialize(v,k,depth,level+1)
830                end
831            elseif tv == "boolean" then
832                if tk == "number" then
833                    if hexify then
834                        handle(format("%s [0x%X]=%s,",depth,k,v and "true" or "false"))
835                    elseif accurate then
836                        handle(format("%s [%q]=%s,",depth,k,v and "true" or "false"))
837                    else
838                        handle(format("%s [%s]=%s,",depth,k,v and "true" or "false"))
839                    end
840                elseif tk == "boolean" then
841                    handle(format("%s [%s]=%s,",depth,tostring(k),v and "true" or "false"))
842                elseif tk ~= "string" then
843                    -- ignore
844                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
845                    handle(format("%s %s=%s,",depth,k,v and "true" or "false"))
846                else
847                    handle(format("%s [%q]=%s,",depth,k,v and "true" or "false"))
848                end
849            elseif tv == "function" then
850                if functions then
851                    local getinfo = debug and debug.getinfo
852                    if getinfo then
853                        local f = getinfo(v).what == "C" and dump(dummy) or dump(v) -- maybe strip
854                     -- local f = getinfo(v).what == "C" and dump(function(...) return v(...) end) or dump(v) -- maybe strip
855                        if tk == "number" then
856                            if hexify then
857                                handle(format("%s [0x%X]=load(%q),",depth,k,f))
858                            elseif accurate then
859                                handle(format("%s [%q]=load(%q),",depth,k,f))
860                            else
861                                handle(format("%s [%s]=load(%q),",depth,k,f))
862                            end
863                        elseif tk == "boolean" then
864                            handle(format("%s [%s]=load(%q),",depth,k and "true" or "false",f))
865                        elseif tk ~= "string" then
866                            -- ignore
867                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
868                            handle(format("%s %s=load(%q),",depth,k,f))
869                        else
870                            handle(format("%s [%q]=load(%q),",depth,k,f))
871                        end
872                    end
873                end
874            else
875                if tk == "number" then
876                    if hexify then
877                        handle(format("%s [0x%X]=%q,",depth,k,tostring(v)))
878                    elseif accurate then
879                        handle(format("%s [%q]=%q,",depth,k,tostring(v)))
880                    else
881                        handle(format("%s [%s]=%q,",depth,k,tostring(v)))
882                    end
883                elseif tk == "boolean" then
884                    handle(format("%s [%s]=%q,",depth,k and "true" or "false",tostring(v)))
885                elseif tk ~= "string" then
886                    -- ignore
887                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
888                    handle(format("%s %s=%q,",depth,k,tostring(v)))
889                else
890                    handle(format("%s [%q]=%q,",depth,k,tostring(v)))
891                end
892            end
893        end
894    end
895    if level > 0 then
896        handle(format("%s},",depth))
897    end
898end
899
900-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
901-- faster (0.03 on 1.00 for zapfino.tma)
902
903local function serialize(_handle,root,name,specification) -- handle wins
904    local tname = type(name)
905    if type(specification) == "table" then
906        noquotes  = specification.noquotes
907        hexify    = specification.hexify
908        accurate  = specification.accurate
909        handle    = _handle or specification.handle or print
910        functions = specification.functions
911        compact   = specification.compact
912        inline    = specification.inline and compact
913        metacheck = specification.metacheck
914        if functions == nil then
915            functions = true
916        end
917        if compact == nil then
918            compact = true
919        end
920        if inline == nil then
921            inline = compact
922        end
923        if metacheck == nil then
924            metacheck = true
925        end
926    else
927        noquotes  = false
928        hexify    = false
929        handle    = _handle or print
930        compact   = true
931        inline    = true
932        functions = true
933        metacheck = true
934    end
935    if tname == "string" then
936        if name == "return" then
937            handle("return {")
938        else
939            handle(name .. "={")
940        end
941    elseif tname == "number" then
942        if hexify then
943            handle(format("[0x%X]={",name))
944        else
945            handle("[" .. name .. "]={")
946        end
947    elseif tname == "boolean" then
948        if name then
949            handle("return {")
950        else
951            handle("{")
952        end
953    else
954        handle("t={")
955    end
956    if root then
957        -- The dummy access will initialize a table that has a delayed initialization
958        -- using a metatable. (maybe explicitly test for metatable). This can crash on
959        -- metatables that check the index against a number.
960        if metacheck and getmetatable(root) then
961            local dummy = root._w_h_a_t_e_v_e_r_
962            root._w_h_a_t_e_v_e_r_ = nil
963        end
964        -- Let's forget about empty tables.
965        if next(root) ~= nil then
966            do_serialize(root,name,"",0)
967        end
968    end
969    handle("}")
970end
971
972-- A version with formatters is some 20% faster than using format (because formatters are
973-- much faster) but of course, inlining the format using .. is then again faster .. anyway,
974-- as we do some pretty printing as well there is not that much to gain unless we make a
975-- 'fast' ugly variant as well. But, we would have to move the formatter to l-string then.
976
977-- name:
978--
979-- true     : return     { }
980-- false    :            { }
981-- nil      : t        = { }
982-- string   : string   = { }
983-- "return" : return     { }
984-- number   : [number] = { }
985
986function table.serialize(root,name,specification)
987    local t = { }
988    local n = 0
989    local function flush(s)
990        n = n + 1
991        t[n] = s
992    end
993    serialize(flush,root,name,specification)
994    return concat(t,"\n")
995end
996
997--   local a = { e = { 1,2,3,4,5,6}, a = 1, b = 2, c = "ccc", d = { a = 1, b = 2, c = "ccc", d = { a = 1, b = 2, c = "ccc" } } }
998--   local t = os.clock()
999--   for i=1,10000 do
1000--       table.serialize(a)
1001--   end
1002--   print(os.clock()-t,table.serialize(a))
1003
1004table.tohandle = serialize
1005
1006local maxtab = 2*1024
1007
1008function table.tofile(filename,root,name,specification)
1009    local f = io.open(filename,'w')
1010    if f then
1011        if maxtab > 1 then
1012            local t = { }
1013            local n = 0
1014            local function flush(s)
1015                n = n + 1
1016                t[n] = s
1017                if n > maxtab then
1018                    f:write(concat(t,"\n"),"\n") -- hm, write(sometable) should be nice
1019                    t = { } -- we could recycle t if needed
1020                    n = 0
1021                end
1022            end
1023            serialize(flush,root,name,specification)
1024            f:write(concat(t,"\n"),"\n")
1025        else
1026            local function flush(s)
1027                f:write(s,"\n")
1028            end
1029            serialize(flush,root,name,specification)
1030        end
1031        f:close()
1032        io.flush()
1033    end
1034end
1035
1036local function flattened(t,f,depth) -- also handles { nil, 1, nil, 2 }
1037    if f == nil then
1038        f     = { }
1039        depth = 0xFFFF
1040    elseif tonumber(f) then
1041        -- assume that only two arguments are given
1042        depth = f
1043        f     = { }
1044    elseif not depth then
1045        depth = 0xFFFF
1046    end
1047    for k, v in next, t do
1048        if type(k) ~= "number" then
1049            if depth > 0 and type(v) == "table" then
1050                flattened(v,f,depth-1)
1051            else
1052                f[#f+1] = v
1053            end
1054        end
1055    end
1056    for k=1,#t do
1057        local v = t[k]
1058        if depth > 0 and type(v) == "table" then
1059            flattened(v,f,depth-1)
1060        else
1061            f[#f+1] = v
1062        end
1063    end
1064    return f
1065end
1066
1067table.flattened = flattened
1068
1069local function collapsed(t,f,h)
1070    if f == nil then
1071        f = { }
1072        h = { }
1073    end
1074    for k=1,#t do
1075        local v = t[k]
1076        if type(v) == "table" then
1077            collapsed(v,f,h)
1078        elseif not h[v] then
1079            f[#f+1] = v
1080            h[v] = true
1081        end
1082    end
1083    return f
1084end
1085
1086local function collapsedhash(t,h)
1087    if h == nil then
1088        h = { }
1089    end
1090    for k=1,#t do
1091        local v = t[k]
1092        if type(v) == "table" then
1093            collapsedhash(v,h)
1094        else
1095            h[v] = true
1096        end
1097    end
1098    return h
1099end
1100
1101table.collapsed     = collapsed     -- 20% faster than unique(collapsed(t))
1102table.collapsedhash = collapsedhash
1103
1104local function unnest(t,f) -- only used in mk, for old times sake
1105    if not f then          -- and only relevant for token lists
1106        f = { }            -- this one can become obsolete
1107    end
1108    for i=1,#t do
1109        local v = t[i]
1110        if type(v) == "table" then
1111            if type(v[1]) == "table" then
1112                unnest(v,f)
1113            else
1114                f[#f+1] = v
1115            end
1116        else
1117            f[#f+1] = v
1118        end
1119    end
1120    return f
1121end
1122
1123function table.unnest(t) -- bad name
1124    return unnest(t)
1125end
1126
1127local function are_equal(a,b,n,m) -- indexed
1128    if a == b then
1129        return true
1130    elseif a and b and #a == #b then
1131        if not n then
1132            n = 1
1133        end
1134        if not m then
1135            m = #a
1136        end
1137        for i=n,m do
1138            local ai, bi = a[i], b[i]
1139            if ai==bi then
1140                -- same
1141            elseif type(ai) == "table" and type(bi) == "table" then
1142                if not are_equal(ai,bi) then
1143                    return false
1144                end
1145            else
1146                return false
1147            end
1148        end
1149        return true
1150    else
1151        return false
1152    end
1153end
1154
1155local function identical(a,b) -- assumes same structure
1156    if a ~= b then
1157        for ka, va in next, a do
1158            local vb = b[ka]
1159            if va == vb then
1160                -- same
1161            elseif type(va) == "table" and  type(vb) == "table" then
1162                if not identical(va,vb) then
1163                    return false
1164                end
1165            else
1166                return false
1167            end
1168        end
1169    end
1170    return true
1171end
1172
1173table.identical = identical
1174table.are_equal = are_equal
1175
1176local function sparse(old,nest,keeptables)
1177    local new  = { }
1178    for k, v in next, old do
1179        if not (v == "" or v == false) then
1180            if nest and type(v) == "table" then
1181                v = sparse(v,nest)
1182                if keeptables or next(v) ~= nil then
1183                    new[k] = v
1184                end
1185            else
1186                new[k] = v
1187            end
1188        end
1189    end
1190    return new
1191end
1192
1193table.sparse = sparse
1194
1195function table.compact(t)
1196    return sparse(t,true,true)
1197end
1198
1199function table.contains(t, v)
1200    if t then
1201        for i=1, #t do
1202            if t[i] == v then
1203                return i
1204            end
1205        end
1206    end
1207    return false
1208end
1209
1210function table.count(t)
1211    local n = 0
1212    for k, v in next, t do
1213        n = n + 1
1214    end
1215    return n
1216end
1217
1218function table.swapped(t,s) -- hash, we need to make sure we don't mess up next
1219    local n = { }
1220    if s then
1221        for k, v in next, s do
1222            n[k] = v
1223        end
1224    end
1225    for k, v in next, t do
1226        n[v] = k
1227    end
1228    return n
1229end
1230
1231function table.hashed(t) -- list, add hash to index (save because we are not yet mixed
1232    for i=1,#t do
1233        t[t[i]] = i
1234    end
1235    return t
1236end
1237
1238function table.mirrored(t) -- hash, we need to make sure we don't mess up next
1239    local n = { }
1240    for k, v in next, t do
1241        n[v] = k
1242        n[k] = v
1243    end
1244    return n
1245end
1246
1247function table.reversed(t)
1248    if t then
1249        local tt = { }
1250        local tn = #t
1251        if tn > 0 then
1252            local ttn = 0
1253            for i=tn,1,-1 do
1254                ttn = ttn + 1
1255                tt[ttn] = t[i]
1256            end
1257        end
1258        return tt
1259    end
1260end
1261
1262function table.reverse(t) -- check with 5.3 ?
1263    if t then
1264        local n = #t
1265        local m = n + 1
1266        for i=1,floor(n/2) do -- maybe just n//2
1267            local j = m - i
1268            t[i], t[j] = t[j], t[i]
1269        end
1270        return t
1271    end
1272end
1273
1274-- This one is for really simple cases where need a hash from a table.
1275
1276local function sequenced(t,sep,simple)
1277    if not t then
1278        return ""
1279    elseif type(t) ~= "table" then
1280        return t -- handy fallback
1281    end
1282    local n = #t
1283    local s = { }
1284    if n > 0 then
1285        -- indexed
1286        for i=1,n do
1287            local v = t[i]
1288            if type(v) == "table" then
1289                s[i] = "{" .. sequenced(v,sep,simple) .. "}"
1290            else
1291                s[i] = tostring(t[i])
1292            end
1293        end
1294    else
1295        -- hashed
1296        n = 0
1297        for k, v in sortedhash(t) do
1298            if simple then
1299                if v == true then
1300                    n = n + 1
1301                    s[n] = k
1302                elseif v and v~= "" then
1303                    n = n + 1
1304                    if type(v) == "table" then
1305                        s[n] = k .. "={" .. sequenced(v,sep,simple) .. "}"
1306                    else
1307                        s[n] = k .. "=" .. tostring(v)
1308                    end
1309                end
1310            else
1311                n = n + 1
1312                if type(v) == "table" then
1313                    s[n] = k .. "={" .. sequenced(v,sep,simple) .. "}"
1314                else
1315                    s[n] = k .. "=" .. tostring(v)
1316                end
1317            end
1318        end
1319    end
1320    if sep == true then
1321        return "{ " .. concat(s,", ") .. " }"
1322    else
1323        return concat(s,sep or " | ")
1324    end
1325end
1326
1327table.sequenced = sequenced
1328
1329function table.print(t,...)
1330    if type(t) ~= "table" then
1331        print(tostring(t))
1332    else
1333        serialize(print,t,...)
1334    end
1335end
1336
1337if setinspector then
1338    setinspector("table",function(v) if type(v) == "table" then serialize(print,v,"table") return true end end)
1339end
1340
1341-- -- -- obsolete but we keep them for a while and might comment them later -- -- --
1342
1343-- roughly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
1344
1345function table.sub(t,i,j)
1346    return { unpack(t,i,j) }
1347end
1348
1349-- slower than #t on indexed tables (#t only returns the size of the numerically indexed slice)
1350
1351function table.is_empty(t)
1352    return not t or next(t) == nil
1353end
1354
1355function table.has_one_entry(t)
1356    return t and next(t,next(t)) == nil
1357end
1358
1359-- new (rather basic, not indexed and nested)
1360
1361function table.loweredkeys(t) -- maybe utf
1362    local l = { }
1363    for k, v in next, t do
1364        l[lower(k)] = v
1365    end
1366    return l
1367end
1368
1369-- new, might move (maybe duplicate)
1370
1371function table.unique(old)
1372    local hash = { }
1373    local new  = { }
1374    local n    = 0
1375    for i=1,#old do
1376        local oi = old[i]
1377        if not hash[oi] then
1378            n = n + 1
1379            new[n] = oi
1380            hash[oi] = true
1381        end
1382    end
1383    return new
1384end
1385
1386function table.sorted(t,...)
1387    sort(t,...)
1388    return t -- still sorts in-place
1389end
1390
1391--
1392
1393function table.values(t,s) -- optional sort flag
1394    if t then
1395        local values = { }
1396        local keys   = { }
1397        local v      = 0
1398        for key, value in next, t do
1399            if not keys[value] then
1400                v = v + 1
1401                values[v] = value
1402                keys[k]   = key
1403            end
1404        end
1405        if s then
1406            sort(values)
1407        end
1408        return values
1409    else
1410        return { }
1411    end
1412end
1413
1414-- maybe this will move to util-tab.lua
1415
1416-- for k, v in table.filtered(t,pattern)          do ... end
1417-- for k, v in table.filtered(t,pattern,true)     do ... end
1418-- for k, v in table.filtered(t,pattern,true,cmp) do ... end
1419
1420function table.filtered(t,pattern,sort,cmp)
1421    if t and type(pattern) == "string" then
1422        if sort then
1423            local s
1424            if cmp then
1425                -- it would be nice if the sort function would accept a third argument (or nicer, an optional first)
1426                s = sortedhashkeys(t,function(a,b) return cmp(t,a,b) end)
1427            else
1428                s = sortedkeys(t) -- the robust one
1429            end
1430            local n = 0
1431            local m = #s
1432            local function kv(s)
1433                while n < m do
1434                    n = n + 1
1435                    local k = s[n]
1436                    if find(k,pattern) then
1437                        return k, t[k]
1438                    end
1439                end
1440            end
1441            return kv, s
1442        else
1443            local n = next(t)
1444            local function iterator()
1445                while n ~= nil do
1446                    local k = n
1447                    n = next(t,k)
1448                    if find(k,pattern) then
1449                        return k, t[k]
1450                    end
1451                end
1452            end
1453            return iterator, t
1454        end
1455    else
1456        return nothing
1457    end
1458end
1459
1460-- lua 5.3:
1461
1462if not table.move then
1463
1464    function table.move(a1,f,e,t,a2)
1465        if a2 and a1 ~= a2 then
1466            for i=f,e do
1467                a2[t] = a1[i]
1468                t = t + 1
1469            end
1470            return a2
1471        else
1472            t = t + e - f
1473            for i=e,f,-1 do
1474                a1[t] = a1[i]
1475                t = t - 1
1476            end
1477            return a1
1478        end
1479    end
1480
1481end
1482