l-table.lua /size: 40 Kb    last modification: 2020-07-01 14:35
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        if type(i) == "table" then
473            if tables[i] then
474                i = tables[i]
475            else
476                i = copy(i,tables)
477            end
478        end
479        if type(v) ~= "table" then
480            tcopy[i] = v
481        elseif tables[v] then
482            tcopy[i] = tables[v]
483        else
484            tcopy[i] = copy(v,tables)
485        end
486    end
487    local mt = getmetatable(t)
488    if mt then
489        setmetatable(tcopy,mt)
490    end
491    return tcopy
492end
493
494table.fastcopy = fastcopy
495table.copy     = copy
496
497function table.derive(parent) -- for the moment not public
498    local child = { }
499    if parent then
500        setmetatable(child,{ __index = parent })
501    end
502    return child
503end
504
505function table.tohash(t,value)
506    local h = { }
507    if t then
508        if value == nil then value = true end
509        for _, v in next, t do
510            h[v] = value
511        end
512    end
513    return h
514end
515
516function table.fromhash(t)
517    local hsh = { }
518    local h   = 0
519    for k, v in next, t do
520        if v then
521            h = h + 1
522            hsh[h] = k
523        end
524    end
525    return hsh
526end
527
528local noquotes, hexify, handle, compact, inline, functions, metacheck, accurate
529
530local reserved = table.tohash { -- intercept a language inconvenience: no reserved words as key
531    'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
532    'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
533    'NaN', 'goto', 'const',
534}
535
536local function is_simple_table(t,hexify,accurate) -- also used in util-tab so maybe public
537    local nt = #t
538    if nt > 0 then
539        local n = 0
540        for _, v in next, t do
541            n = n + 1
542            if type(v) == "table" then
543                return nil
544            end
545        end
546     -- local haszero = t[0]
547        local haszero = rawget(t,0) -- don't trigger meta
548        if n == nt then
549            local tt = { }
550            for i=1,nt do
551                local v = t[i]
552                local tv = type(v)
553                if tv == "number" then
554                 -- tt[i] = v -- not needed tostring(v)
555                    if hexify then
556                        tt[i] = format("0x%X",v)
557                    elseif accurate then
558                        tt[i] = format("%q",v)
559                    else
560                        tt[i] = v -- not needed tostring(v)
561                    end
562                elseif tv == "string" then
563                    tt[i] = format("%q",v) -- f_string(v)
564                elseif tv == "boolean" then
565                    tt[i] = v and "true" or "false"
566                else
567                    return nil
568                end
569            end
570            return tt
571        elseif haszero and (n == nt + 1) then
572            local tt = { }
573            for i=0,nt do
574                local v = t[i]
575                local tv = type(v)
576                if tv == "number" then
577                 -- tt[i+1] = v -- not needed tostring(v)
578                    if hexify then
579                        tt[i+1] = format("0x%X",v)
580                    elseif accurate then
581                        tt[i+1] = format("%q",v)
582                    else
583                        tt[i+1] = v -- not needed tostring(v)
584                    end
585                elseif tv == "string" then
586                    tt[i+1] = format("%q",v) -- f_string(v)
587                elseif tv == "boolean" then
588                    tt[i+1] = v and "true" or "false"
589                else
590                    return nil
591                end
592            end
593            tt[1] = "[0] = " .. tt[1]
594            return tt
595        end
596    end
597    return nil
598end
599
600table.is_simple_table = is_simple_table
601
602-- Because this is a core function of mkiv I moved some function calls
603-- inline.
604--
605-- twice as fast in a test:
606--
607-- local propername = lpeg.P(lpeg.R("AZ","az","__") * lpeg.R("09","AZ","az", "__")^0 * lpeg.P(-1) )
608
609-- problem: there no good number_to_string converter with the best resolution
610
611-- probably using .. is faster than format
612-- maybe split in a few cases (yes/no hexify)
613
614-- todo: %g faster on numbers than %s
615
616-- we can speed this up with repeaters and formatters but we haven't defined them
617-- yet
618
619local propername = patterns.propername -- was find(name,"^%a[%w%_]*$")
620
621local function dummy() end
622
623local function do_serialize(root,name,depth,level,indexed)
624    if level > 0 then
625        depth = depth .. " "
626        if indexed then
627            handle(format("%s{",depth))
628        else
629            local tn = type(name)
630            if tn == "number" then
631                if hexify then
632                    handle(format("%s[0x%X]={",depth,name))
633                else
634                    handle(format("%s[%s]={",depth,name))
635                end
636            elseif tn == "string" then
637                if noquotes and not reserved[name] and lpegmatch(propername,name) then
638                    handle(format("%s%s={",depth,name))
639                else
640                    handle(format("%s[%q]={",depth,name))
641                end
642            elseif tn == "boolean" then
643                handle(format("%s[%s]={",depth,name and "true" or "false"))
644            else
645                handle(format("%s{",depth))
646            end
647        end
648    end
649    -- we could check for k (index) being number (cardinal)
650    if root and next(root) ~= nil then
651        local first = nil
652        local last  = 0
653        if compact then
654            last = #root
655            for k=1,last do
656             -- if root[k] == nil then
657                if rawget(root,k) == nil then
658                    last = k - 1
659                    break
660                end
661            end
662            if last > 0 then
663                first = 1
664            end
665        end
666        local sk = sortedkeys(root)
667        for i=1,#sk do
668            local k  = sk[i]
669            local v  = root[k]
670            local tv = type(v)
671            local tk = type(k)
672            if compact and first and tk == "number" and k >= first and k <= last then
673                if tv == "number" then
674                    if hexify then
675                        handle(format("%s 0x%X,",depth,v))
676                    elseif accurate then
677                        handle(format("%s %q,",depth,v))
678                    else
679                        handle(format("%s %s,",depth,v)) -- %.99g
680                    end
681                elseif tv == "string" then
682                    handle(format("%s %q,",depth,v))
683                elseif tv == "table" then
684                    if next(v) == nil then
685                        handle(format("%s {},",depth))
686                    elseif inline then -- and #t > 0
687                        local st = is_simple_table(v,hexify,accurate)
688                        if st then
689                            handle(format("%s { %s },",depth,concat(st,", ")))
690                        else
691                            do_serialize(v,k,depth,level+1,true)
692                        end
693                    else
694                        do_serialize(v,k,depth,level+1,true)
695                    end
696                elseif tv == "boolean" then
697                    handle(format("%s %s,",depth,v and "true" or "false"))
698                elseif tv == "function" then
699                    if functions then
700                        handle(format('%s load(%q),',depth,dump(v))) -- maybe strip
701                    else
702                        handle(format('%s "function",',depth))
703                    end
704                else
705                    handle(format("%s %q,",depth,tostring(v)))
706                end
707            elseif k == "__p__" then -- parent
708                if false then
709                    handle(format("%s __p__=nil,",depth))
710                end
711            elseif tv == "number" then
712                if tk == "number" then
713                    if hexify then
714                        handle(format("%s [0x%X]=0x%X,",depth,k,v))
715                    elseif accurate then
716                        handle(format("%s [%s]=%q,",depth,k,v))
717                    else
718                        handle(format("%s [%s]=%s,",depth,k,v)) -- %.99g
719                    end
720                elseif tk == "boolean" then
721                    if hexify then
722                        handle(format("%s [%s]=0x%X,",depth,k and "true" or "false",v))
723                    elseif accurate then
724                        handle(format("%s [%s]=%q,",depth,k and "true" or "false",v))
725                    else
726                        handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) -- %.99g
727                    end
728                elseif tk ~= "string" then
729                    -- ignore
730                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
731                    if hexify then
732                        handle(format("%s %s=0x%X,",depth,k,v))
733                    elseif accurate then
734                        handle(format("%s %s=%q,",depth,k,v))
735                    else
736                        handle(format("%s %s=%s,",depth,k,v)) -- %.99g
737                    end
738                else
739                    if hexify then
740                        handle(format("%s [%q]=0x%X,",depth,k,v))
741                    elseif accurate then
742                        handle(format("%s [%q]=%q,",depth,k,v))
743                    else
744                        handle(format("%s [%q]=%s,",depth,k,v)) -- %.99g
745                    end
746                end
747            elseif tv == "string" then
748                if tk == "number" then
749                    if hexify then
750                        handle(format("%s [0x%X]=%q,",depth,k,v))
751                    elseif accurate then
752                        handle(format("%s [%q]=%q,",depth,k,v))
753                    else
754                        handle(format("%s [%s]=%q,",depth,k,v))
755                    end
756                elseif tk == "boolean" then
757                    handle(format("%s [%s]=%q,",depth,k and "true" or "false",v))
758                elseif tk ~= "string" then
759                    -- ignore
760                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
761                    handle(format("%s %s=%q,",depth,k,v))
762                else
763                    handle(format("%s [%q]=%q,",depth,k,v))
764                end
765            elseif tv == "table" then
766                if next(v) == nil then
767                    if tk == "number" then
768                        if hexify then
769                            handle(format("%s [0x%X]={},",depth,k))
770                        elseif accurate then
771                            handle(format("%s [%q]={},",depth,k))
772                        else
773                            handle(format("%s [%s]={},",depth,k))
774                        end
775                    elseif tk == "boolean" then
776                        handle(format("%s [%s]={},",depth,k and "true" or "false"))
777                    elseif tk ~= "string" then
778                        -- ignore
779                    elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
780                        handle(format("%s %s={},",depth,k))
781                    else
782                        handle(format("%s [%q]={},",depth,k))
783                    end
784                elseif inline then
785                    local st = is_simple_table(v,hexify,accurate)
786                    if st then
787                        if tk == "number" then
788                            if hexify then
789                                handle(format("%s [0x%X]={ %s },",depth,k,concat(st,", ")))
790                            elseif accurate then
791                                handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
792                            else
793                                handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
794                            end
795                        elseif tk == "boolean" then
796                            handle(format("%s [%s]={ %s },",depth,k and "true" or "false",concat(st,", ")))
797                        elseif tk ~= "string" then
798                            -- ignore
799                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
800                            handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
801                        else
802                            handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
803                        end
804                    else
805                        do_serialize(v,k,depth,level+1)
806                    end
807                else
808                    do_serialize(v,k,depth,level+1)
809                end
810            elseif tv == "boolean" then
811                if tk == "number" then
812                    if hexify then
813                        handle(format("%s [0x%X]=%s,",depth,k,v and "true" or "false"))
814                    elseif accurate then
815                        handle(format("%s [%q]=%s,",depth,k,v and "true" or "false"))
816                    else
817                        handle(format("%s [%s]=%s,",depth,k,v and "true" or "false"))
818                    end
819                elseif tk == "boolean" then
820                    handle(format("%s [%s]=%s,",depth,tostring(k),v and "true" or "false"))
821                elseif tk ~= "string" then
822                    -- ignore
823                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
824                    handle(format("%s %s=%s,",depth,k,v and "true" or "false"))
825                else
826                    handle(format("%s [%q]=%s,",depth,k,v and "true" or "false"))
827                end
828            elseif tv == "function" then
829                if functions then
830                    local getinfo = debug and debug.getinfo
831                    if getinfo then
832                        local f = getinfo(v).what == "C" and dump(dummy) or dump(v) -- maybe strip
833                     -- local f = getinfo(v).what == "C" and dump(function(...) return v(...) end) or dump(v) -- maybe strip
834                        if tk == "number" then
835                            if hexify then
836                                handle(format("%s [0x%X]=load(%q),",depth,k,f))
837                            elseif accurate then
838                                handle(format("%s [%q]=load(%q),",depth,k,f))
839                            else
840                                handle(format("%s [%s]=load(%q),",depth,k,f))
841                            end
842                        elseif tk == "boolean" then
843                            handle(format("%s [%s]=load(%q),",depth,k and "true" or "false",f))
844                        elseif tk ~= "string" then
845                            -- ignore
846                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
847                            handle(format("%s %s=load(%q),",depth,k,f))
848                        else
849                            handle(format("%s [%q]=load(%q),",depth,k,f))
850                        end
851                    end
852                end
853            else
854                if tk == "number" then
855                    if hexify then
856                        handle(format("%s [0x%X]=%q,",depth,k,tostring(v)))
857                    elseif accurate then
858                        handle(format("%s [%q]=%q,",depth,k,tostring(v)))
859                    else
860                        handle(format("%s [%s]=%q,",depth,k,tostring(v)))
861                    end
862                elseif tk == "boolean" then
863                    handle(format("%s [%s]=%q,",depth,k and "true" or "false",tostring(v)))
864                elseif tk ~= "string" then
865                    -- ignore
866                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
867                    handle(format("%s %s=%q,",depth,k,tostring(v)))
868                else
869                    handle(format("%s [%q]=%q,",depth,k,tostring(v)))
870                end
871            end
872        end
873    end
874    if level > 0 then
875        handle(format("%s},",depth))
876    end
877end
878
879-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
880-- faster (0.03 on 1.00 for zapfino.tma)
881
882local function serialize(_handle,root,name,specification) -- handle wins
883    local tname = type(name)
884    if type(specification) == "table" then
885        noquotes  = specification.noquotes
886        hexify    = specification.hexify
887        accurate  = specification.accurate
888        handle    = _handle or specification.handle or print
889        functions = specification.functions
890        compact   = specification.compact
891        inline    = specification.inline and compact
892        metacheck = specification.metacheck
893        if functions == nil then
894            functions = true
895        end
896        if compact == nil then
897            compact = true
898        end
899        if inline == nil then
900            inline = compact
901        end
902        if metacheck == nil then
903            metacheck = true
904        end
905    else
906        noquotes  = false
907        hexify    = false
908        handle    = _handle or print
909        compact   = true
910        inline    = true
911        functions = true
912        metacheck = true
913    end
914    if tname == "string" then
915        if name == "return" then
916            handle("return {")
917        else
918            handle(name .. "={")
919        end
920    elseif tname == "number" then
921        if hexify then
922            handle(format("[0x%X]={",name))
923        else
924            handle("[" .. name .. "]={")
925        end
926    elseif tname == "boolean" then
927        if name then
928            handle("return {")
929        else
930            handle("{")
931        end
932    else
933        handle("t={")
934    end
935    if root then
936        -- The dummy access will initialize a table that has a delayed initialization
937        -- using a metatable. (maybe explicitly test for metatable). This can crash on
938        -- metatables that check the index against a number.
939        if metacheck and getmetatable(root) then
940            local dummy = root._w_h_a_t_e_v_e_r_
941            root._w_h_a_t_e_v_e_r_ = nil
942        end
943        -- Let's forget about empty tables.
944        if next(root) ~= nil then
945            do_serialize(root,name,"",0)
946        end
947    end
948    handle("}")
949end
950
951-- A version with formatters is some 20% faster than using format (because formatters are
952-- much faster) but of course, inlining the format using .. is then again faster .. anyway,
953-- as we do some pretty printing as well there is not that much to gain unless we make a
954-- 'fast' ugly variant as well. But, we would have to move the formatter to l-string then.
955
956-- name:
957--
958-- true     : return     { }
959-- false    :            { }
960-- nil      : t        = { }
961-- string   : string   = { }
962-- "return" : return     { }
963-- number   : [number] = { }
964
965function table.serialize(root,name,specification)
966    local t = { }
967    local n = 0
968    local function flush(s)
969        n = n + 1
970        t[n] = s
971    end
972    serialize(flush,root,name,specification)
973    return concat(t,"\n")
974end
975
976--   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" } } }
977--   local t = os.clock()
978--   for i=1,10000 do
979--       table.serialize(a)
980--   end
981--   print(os.clock()-t,table.serialize(a))
982
983table.tohandle = serialize
984
985local maxtab = 2*1024
986
987function table.tofile(filename,root,name,specification)
988    local f = io.open(filename,'w')
989    if f then
990        if maxtab > 1 then
991            local t = { }
992            local n = 0
993            local function flush(s)
994                n = n + 1
995                t[n] = s
996                if n > maxtab then
997                    f:write(concat(t,"\n"),"\n") -- hm, write(sometable) should be nice
998                    t = { } -- we could recycle t if needed
999                    n = 0
1000                end
1001            end
1002            serialize(flush,root,name,specification)
1003            f:write(concat(t,"\n"),"\n")
1004        else
1005            local function flush(s)
1006                f:write(s,"\n")
1007            end
1008            serialize(flush,root,name,specification)
1009        end
1010        f:close()
1011        io.flush()
1012    end
1013end
1014
1015local function flattened(t,f,depth) -- also handles { nil, 1, nil, 2 }
1016    if f == nil then
1017        f     = { }
1018        depth = 0xFFFF
1019    elseif tonumber(f) then
1020        -- assume that only two arguments are given
1021        depth = f
1022        f     = { }
1023    elseif not depth then
1024        depth = 0xFFFF
1025    end
1026    for k, v in next, t do
1027        if type(k) ~= "number" then
1028            if depth > 0 and type(v) == "table" then
1029                flattened(v,f,depth-1)
1030            else
1031                f[#f+1] = v
1032            end
1033        end
1034    end
1035    for k=1,#t do
1036        local v = t[k]
1037        if depth > 0 and type(v) == "table" then
1038            flattened(v,f,depth-1)
1039        else
1040            f[#f+1] = v
1041        end
1042    end
1043    return f
1044end
1045
1046table.flattened = flattened
1047
1048local function collapsed(t,f,h)
1049    if f == nil then
1050        f = { }
1051        h = { }
1052    end
1053    for k=1,#t do
1054        local v = t[k]
1055        if type(v) == "table" then
1056            collapsed(v,f,h)
1057        elseif not h[v] then
1058            f[#f+1] = v
1059            h[v] = true
1060        end
1061    end
1062    return f
1063end
1064
1065local function collapsedhash(t,h)
1066    if h == nil then
1067        h = { }
1068    end
1069    for k=1,#t do
1070        local v = t[k]
1071        if type(v) == "table" then
1072            collapsedhash(v,h)
1073        else
1074            h[v] = true
1075        end
1076    end
1077    return h
1078end
1079
1080table.collapsed     = collapsed     -- 20% faster than unique(collapsed(t))
1081table.collapsedhash = collapsedhash
1082
1083local function unnest(t,f) -- only used in mk, for old times sake
1084    if not f then          -- and only relevant for token lists
1085        f = { }            -- this one can become obsolete
1086    end
1087    for i=1,#t do
1088        local v = t[i]
1089        if type(v) == "table" then
1090            if type(v[1]) == "table" then
1091                unnest(v,f)
1092            else
1093                f[#f+1] = v
1094            end
1095        else
1096            f[#f+1] = v
1097        end
1098    end
1099    return f
1100end
1101
1102function table.unnest(t) -- bad name
1103    return unnest(t)
1104end
1105
1106local function are_equal(a,b,n,m) -- indexed
1107    if a == b then
1108        return true
1109    elseif a and b and #a == #b then
1110        if not n then
1111            n = 1
1112        end
1113        if not m then
1114            m = #a
1115        end
1116        for i=n,m do
1117            local ai, bi = a[i], b[i]
1118            if ai==bi then
1119                -- same
1120            elseif type(ai) == "table" and type(bi) == "table" then
1121                if not are_equal(ai,bi) then
1122                    return false
1123                end
1124            else
1125                return false
1126            end
1127        end
1128        return true
1129    else
1130        return false
1131    end
1132end
1133
1134local function identical(a,b) -- assumes same structure
1135    if a ~= b then
1136        for ka, va in next, a do
1137            local vb = b[ka]
1138            if va == vb then
1139                -- same
1140            elseif type(va) == "table" and  type(vb) == "table" then
1141                if not identical(va,vb) then
1142                    return false
1143                end
1144            else
1145                return false
1146            end
1147        end
1148    end
1149    return true
1150end
1151
1152table.identical = identical
1153table.are_equal = are_equal
1154
1155local function sparse(old,nest,keeptables)
1156    local new  = { }
1157    for k, v in next, old do
1158        if not (v == "" or v == false) then
1159            if nest and type(v) == "table" then
1160                v = sparse(v,nest)
1161                if keeptables or next(v) ~= nil then
1162                    new[k] = v
1163                end
1164            else
1165                new[k] = v
1166            end
1167        end
1168    end
1169    return new
1170end
1171
1172table.sparse = sparse
1173
1174function table.compact(t)
1175    return sparse(t,true,true)
1176end
1177
1178function table.contains(t, v)
1179    if t then
1180        for i=1, #t do
1181            if t[i] == v then
1182                return i
1183            end
1184        end
1185    end
1186    return false
1187end
1188
1189function table.count(t)
1190    local n = 0
1191    for k, v in next, t do
1192        n = n + 1
1193    end
1194    return n
1195end
1196
1197function table.swapped(t,s) -- hash, we need to make sure we don't mess up next
1198    local n = { }
1199    if s then
1200        for k, v in next, s do
1201            n[k] = v
1202        end
1203    end
1204    for k, v in next, t do
1205        n[v] = k
1206    end
1207    return n
1208end
1209
1210function table.hashed(t) -- list, add hash to index (save because we are not yet mixed
1211    for i=1,#t do
1212        t[t[i]] = i
1213    end
1214    return t
1215end
1216
1217function table.mirrored(t) -- hash, we need to make sure we don't mess up next
1218    local n = { }
1219    for k, v in next, t do
1220        n[v] = k
1221        n[k] = v
1222    end
1223    return n
1224end
1225
1226function table.reversed(t)
1227    if t then
1228        local tt = { }
1229        local tn = #t
1230        if tn > 0 then
1231            local ttn = 0
1232            for i=tn,1,-1 do
1233                ttn = ttn + 1
1234                tt[ttn] = t[i]
1235            end
1236        end
1237        return tt
1238    end
1239end
1240
1241function table.reverse(t) -- check with 5.3 ?
1242    if t then
1243        local n = #t
1244        local m = n + 1
1245        for i=1,floor(n/2) do -- maybe just n//2
1246            local j = m - i
1247            t[i], t[j] = t[j], t[i]
1248        end
1249        return t
1250    end
1251end
1252
1253-- This one is for really simple cases where need a hash from a table.
1254
1255local function sequenced(t,sep,simple)
1256    if not t then
1257        return ""
1258    elseif type(t) ~= "table" then
1259        return t -- handy fallback
1260    end
1261    local n = #t
1262    local s = { }
1263    if n > 0 then
1264        -- indexed
1265        for i=1,n do
1266            local v = t[i]
1267            if type(v) == "table" then
1268                s[i] = "{" .. sequenced(v,sep,simple) .. "}"
1269            else
1270                s[i] = tostring(t[i])
1271            end
1272        end
1273    else
1274        -- hashed
1275        n = 0
1276        for k, v in sortedhash(t) do
1277            if simple then
1278                if v == true then
1279                    n = n + 1
1280                    s[n] = k
1281                elseif v and v~= "" then
1282                    n = n + 1
1283                    if type(v) == "table" then
1284                        s[n] = k .. "={" .. sequenced(v,sep,simple) .. "}"
1285                    else
1286                        s[n] = k .. "=" .. tostring(v)
1287                    end
1288                end
1289            else
1290                n = n + 1
1291                if type(v) == "table" then
1292                    s[n] = k .. "={" .. sequenced(v,sep,simple) .. "}"
1293                else
1294                    s[n] = k .. "=" .. tostring(v)
1295                end
1296            end
1297        end
1298    end
1299    if sep == true then
1300        return "{ " .. concat(s,", ") .. " }"
1301    else
1302        return concat(s,sep or " | ")
1303    end
1304end
1305
1306table.sequenced = sequenced
1307
1308function table.print(t,...)
1309    if type(t) ~= "table" then
1310        print(tostring(t))
1311    else
1312        serialize(print,t,...)
1313    end
1314end
1315
1316if setinspector then
1317    setinspector("table",function(v) if type(v) == "table" then serialize(print,v,"table") return true end end)
1318end
1319
1320-- -- -- obsolete but we keep them for a while and might comment them later -- -- --
1321
1322-- roughly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
1323
1324function table.sub(t,i,j)
1325    return { unpack(t,i,j) }
1326end
1327
1328-- slower than #t on indexed tables (#t only returns the size of the numerically indexed slice)
1329
1330function table.is_empty(t)
1331    return not t or next(t) == nil
1332end
1333
1334function table.has_one_entry(t)
1335    return t and next(t,next(t)) == nil
1336end
1337
1338-- new (rather basic, not indexed and nested)
1339
1340function table.loweredkeys(t) -- maybe utf
1341    local l = { }
1342    for k, v in next, t do
1343        l[lower(k)] = v
1344    end
1345    return l
1346end
1347
1348-- new, might move (maybe duplicate)
1349
1350function table.unique(old)
1351    local hash = { }
1352    local new  = { }
1353    local n    = 0
1354    for i=1,#old do
1355        local oi = old[i]
1356        if not hash[oi] then
1357            n = n + 1
1358            new[n] = oi
1359            hash[oi] = true
1360        end
1361    end
1362    return new
1363end
1364
1365function table.sorted(t,...)
1366    sort(t,...)
1367    return t -- still sorts in-place
1368end
1369
1370--
1371
1372function table.values(t,s) -- optional sort flag
1373    if t then
1374        local values = { }
1375        local keys   = { }
1376        local v      = 0
1377        for key, value in next, t do
1378            if not keys[value] then
1379                v = v + 1
1380                values[v] = value
1381                keys[k]   = key
1382            end
1383        end
1384        if s then
1385            sort(values)
1386        end
1387        return values
1388    else
1389        return { }
1390    end
1391end
1392
1393-- maybe this will move to util-tab.lua
1394
1395-- for k, v in table.filtered(t,pattern)          do ... end
1396-- for k, v in table.filtered(t,pattern,true)     do ... end
1397-- for k, v in table.filtered(t,pattern,true,cmp) do ... end
1398
1399function table.filtered(t,pattern,sort,cmp)
1400    if t and type(pattern) == "string" then
1401        if sort then
1402            local s
1403            if cmp then
1404                -- it would be nice if the sort function would accept a third argument (or nicer, an optional first)
1405                s = sortedhashkeys(t,function(a,b) return cmp(t,a,b) end)
1406            else
1407                s = sortedkeys(t) -- the robust one
1408            end
1409            local n = 0
1410            local m = #s
1411            local function kv(s)
1412                while n < m do
1413                    n = n + 1
1414                    local k = s[n]
1415                    if find(k,pattern) then
1416                        return k, t[k]
1417                    end
1418                end
1419            end
1420            return kv, s
1421        else
1422            local n = next(t)
1423            local function iterator()
1424                while n ~= nil do
1425                    local k = n
1426                    n = next(t,k)
1427                    if find(k,pattern) then
1428                        return k, t[k]
1429                    end
1430                end
1431            end
1432            return iterator, t
1433        end
1434    else
1435        return nothing
1436    end
1437end
1438
1439-- lua 5.3:
1440
1441if not table.move then
1442
1443    function table.move(a1,f,e,t,a2)
1444        if a2 and a1 ~= a2 then
1445            for i=f,e do
1446                a2[t] = a1[i]
1447                t = t + 1
1448            end
1449            return a2
1450        else
1451            t = t + e - f
1452            for i=e,f,-1 do
1453                a1[t] = a1[i]
1454                t = t - 1
1455            end
1456            return a1
1457        end
1458    end
1459
1460end
1461