l-table.lua /size: 41 Kb    last modification: 2023-12-21 09:44
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                        if accurate then
715                            handle(format("%s [0x%X]=%q,",depth,k,v))
716                        else
717                            handle(format("%s [0x%X]=%s,",depth,k,v))
718                        end
719                    elseif accurate then
720                        handle(format("%s [%s]=%q,",depth,k,v))
721                    else
722                        handle(format("%s [%s]=%s,",depth,k,v)) -- %.99g
723                    end
724                elseif tk == "boolean" then
725                    if hexify then
726                        if accurate then
727                            handle(format("%s [%s]=%q,",depth,k and "true" or "false",v))
728                        else
729                            handle(format("%s [%s]=%s,",depth,k and "true" or "false",v))
730                        end
731                    elseif accurate then
732                        handle(format("%s [%s]=%q,",depth,k and "true" or "false",v))
733                    else
734                        handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) -- %.99g
735                    end
736                elseif tk ~= "string" then
737                    -- ignore
738                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
739                    if hexify then
740                        if accurate then
741                            handle(format("%s %s=%q,",depth,k,v))
742                        else
743--                             handle(format("%s %s=%s,",depth,k,v))
744                            handle(format("%s %s=0x%X,",depth,k,v))
745                        end
746                    elseif accurate then
747                        handle(format("%s %s=%q,",depth,k,v))
748                    else
749                        handle(format("%s %s=%s,",depth,k,v)) -- %.99g
750                    end
751                else
752                    if hexify then
753                        if accurate then
754                            handle(format("%s [%q]=%q,",depth,k,v))
755                        else
756--                             handle(format("%s [%q]=%s,",depth,k,v))
757                            handle(format("%s [%q]=0x%X,",depth,k,v))
758                        end
759                    elseif accurate then
760                        handle(format("%s [%q]=%q,",depth,k,v))
761                    else
762                        handle(format("%s [%q]=%s,",depth,k,v)) -- %.99g
763                    end
764                end
765            elseif tv == "string" then
766                if tk == "number" then
767                    if hexify then
768                        handle(format("%s [0x%X]=%q,",depth,k,v))
769                    elseif accurate then
770                        handle(format("%s [%q]=%q,",depth,k,v))
771                    else
772                        handle(format("%s [%s]=%q,",depth,k,v))
773                    end
774                elseif tk == "boolean" then
775                    handle(format("%s [%s]=%q,",depth,k and "true" or "false",v))
776                elseif tk ~= "string" then
777                    -- ignore
778                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
779                    handle(format("%s %s=%q,",depth,k,v))
780                else
781                    handle(format("%s [%q]=%q,",depth,k,v))
782                end
783            elseif tv == "table" then
784                if next(v) == nil then
785                    if tk == "number" then
786                        if hexify then
787                            handle(format("%s [0x%X]={},",depth,k))
788                        elseif accurate then
789                            handle(format("%s [%q]={},",depth,k))
790                        else
791                            handle(format("%s [%s]={},",depth,k))
792                        end
793                    elseif tk == "boolean" then
794                        handle(format("%s [%s]={},",depth,k and "true" or "false"))
795                    elseif tk ~= "string" then
796                        -- ignore
797                    elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
798                        handle(format("%s %s={},",depth,k))
799                    else
800                        handle(format("%s [%q]={},",depth,k))
801                    end
802                elseif inline then
803                    local st = is_simple_table(v,hexify,accurate)
804                    if st then
805                        if tk == "number" then
806                            if hexify then
807                                handle(format("%s [0x%X]={ %s },",depth,k,concat(st,", ")))
808                            elseif accurate then
809                                handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
810                            else
811                                handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
812                            end
813                        elseif tk == "boolean" then
814                            handle(format("%s [%s]={ %s },",depth,k and "true" or "false",concat(st,", ")))
815                        elseif tk ~= "string" then
816                            -- ignore
817                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
818                            handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
819                        else
820                            handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
821                        end
822                    else
823                        do_serialize(v,k,depth,level+1)
824                    end
825                else
826                    do_serialize(v,k,depth,level+1)
827                end
828            elseif tv == "boolean" then
829                if tk == "number" then
830                    if hexify then
831                        handle(format("%s [0x%X]=%s,",depth,k,v and "true" or "false"))
832                    elseif accurate then
833                        handle(format("%s [%q]=%s,",depth,k,v and "true" or "false"))
834                    else
835                        handle(format("%s [%s]=%s,",depth,k,v and "true" or "false"))
836                    end
837                elseif tk == "boolean" then
838                    handle(format("%s [%s]=%s,",depth,tostring(k),v and "true" or "false"))
839                elseif tk ~= "string" then
840                    -- ignore
841                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
842                    handle(format("%s %s=%s,",depth,k,v and "true" or "false"))
843                else
844                    handle(format("%s [%q]=%s,",depth,k,v and "true" or "false"))
845                end
846            elseif tv == "function" then
847                if functions then
848                    local getinfo = debug and debug.getinfo
849                    if getinfo then
850                        local f = getinfo(v).what == "C" and dump(dummy) or dump(v) -- maybe strip
851                     -- local f = getinfo(v).what == "C" and dump(function(...) return v(...) end) or dump(v) -- maybe strip
852                        if tk == "number" then
853                            if hexify then
854                                handle(format("%s [0x%X]=load(%q),",depth,k,f))
855                            elseif accurate then
856                                handle(format("%s [%q]=load(%q),",depth,k,f))
857                            else
858                                handle(format("%s [%s]=load(%q),",depth,k,f))
859                            end
860                        elseif tk == "boolean" then
861                            handle(format("%s [%s]=load(%q),",depth,k and "true" or "false",f))
862                        elseif tk ~= "string" then
863                            -- ignore
864                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
865                            handle(format("%s %s=load(%q),",depth,k,f))
866                        else
867                            handle(format("%s [%q]=load(%q),",depth,k,f))
868                        end
869                    end
870                end
871            else
872                if tk == "number" then
873                    if hexify then
874                        handle(format("%s [0x%X]=%q,",depth,k,tostring(v)))
875                    elseif accurate then
876                        handle(format("%s [%q]=%q,",depth,k,tostring(v)))
877                    else
878                        handle(format("%s [%s]=%q,",depth,k,tostring(v)))
879                    end
880                elseif tk == "boolean" then
881                    handle(format("%s [%s]=%q,",depth,k and "true" or "false",tostring(v)))
882                elseif tk ~= "string" then
883                    -- ignore
884                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
885                    handle(format("%s %s=%q,",depth,k,tostring(v)))
886                else
887                    handle(format("%s [%q]=%q,",depth,k,tostring(v)))
888                end
889            end
890        end
891    end
892    if level > 0 then
893        handle(format("%s},",depth))
894    end
895end
896
897-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
898-- faster (0.03 on 1.00 for zapfino.tma)
899
900local function serialize(_handle,root,name,specification) -- handle wins
901    local tname = type(name)
902    if type(specification) == "table" then
903        noquotes  = specification.noquotes
904        hexify    = specification.hexify
905        accurate  = specification.accurate
906        handle    = _handle or specification.handle or print
907        functions = specification.functions
908        compact   = specification.compact
909        inline    = specification.inline and compact
910        metacheck = specification.metacheck
911        if functions == nil then
912            functions = true
913        end
914        if compact == nil then
915            compact = true
916        end
917        if inline == nil then
918            inline = compact
919        end
920        if metacheck == nil then
921            metacheck = true
922        end
923    else
924        noquotes  = false
925        hexify    = false
926        handle    = _handle or print
927        compact   = true
928        inline    = true
929        functions = true
930        metacheck = true
931    end
932    if tname == "string" then
933        if name == "return" then
934            handle("return {")
935        else
936            handle(name .. "={")
937        end
938    elseif tname == "number" then
939        if hexify then
940            handle(format("[0x%X]={",name))
941        else
942            handle("[" .. name .. "]={")
943        end
944    elseif tname == "boolean" then
945        if name then
946            handle("return {")
947        else
948            handle("{")
949        end
950    else
951        handle("t={")
952    end
953    if root then
954        -- The dummy access will initialize a table that has a delayed initialization
955        -- using a metatable. (maybe explicitly test for metatable). This can crash on
956        -- metatables that check the index against a number.
957        if metacheck and getmetatable(root) then
958            local dummy = root._w_h_a_t_e_v_e_r_
959            root._w_h_a_t_e_v_e_r_ = nil
960        end
961        -- Let's forget about empty tables.
962        if next(root) ~= nil then
963            do_serialize(root,name,"",0)
964        end
965    end
966    handle("}")
967end
968
969-- A version with formatters is some 20% faster than using format (because formatters are
970-- much faster) but of course, inlining the format using .. is then again faster .. anyway,
971-- as we do some pretty printing as well there is not that much to gain unless we make a
972-- 'fast' ugly variant as well. But, we would have to move the formatter to l-string then.
973
974-- name:
975--
976-- true     : return     { }
977-- false    :            { }
978-- nil      : t        = { }
979-- string   : string   = { }
980-- "return" : return     { }
981-- number   : [number] = { }
982
983function table.serialize(root,name,specification)
984    local t = { }
985    local n = 0
986    local function flush(s)
987        n = n + 1
988        t[n] = s
989    end
990    serialize(flush,root,name,specification)
991    return concat(t,"\n")
992end
993
994--   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" } } }
995--   local t = os.clock()
996--   for i=1,10000 do
997--       table.serialize(a)
998--   end
999--   print(os.clock()-t,table.serialize(a))
1000
1001table.tohandle = serialize
1002
1003local maxtab = 2*1024
1004
1005function table.tofile(filename,root,name,specification)
1006    local f = io.open(filename,'w')
1007    if f then
1008        if maxtab > 1 then
1009            local t = { }
1010            local n = 0
1011            local function flush(s)
1012                n = n + 1
1013                t[n] = s
1014                if n > maxtab then
1015                    f:write(concat(t,"\n"),"\n") -- hm, write(sometable) should be nice
1016                    t = { } -- we could recycle t if needed
1017                    n = 0
1018                end
1019            end
1020            serialize(flush,root,name,specification)
1021            f:write(concat(t,"\n"),"\n")
1022        else
1023            local function flush(s)
1024                f:write(s,"\n")
1025            end
1026            serialize(flush,root,name,specification)
1027        end
1028        f:close()
1029        io.flush()
1030    end
1031end
1032
1033local function flattened(t,f,depth) -- also handles { nil, 1, nil, 2 }
1034    if f == nil then
1035        f     = { }
1036        depth = 0xFFFF
1037    elseif tonumber(f) then
1038        -- assume that only two arguments are given
1039        depth = f
1040        f     = { }
1041    elseif not depth then
1042        depth = 0xFFFF
1043    end
1044    for k, v in next, t do
1045        if type(k) ~= "number" then
1046            if depth > 0 and type(v) == "table" then
1047                flattened(v,f,depth-1)
1048            else
1049                f[#f+1] = v
1050            end
1051        end
1052    end
1053    for k=1,#t do
1054        local v = t[k]
1055        if depth > 0 and type(v) == "table" then
1056            flattened(v,f,depth-1)
1057        else
1058            f[#f+1] = v
1059        end
1060    end
1061    return f
1062end
1063
1064table.flattened = flattened
1065
1066local function collapsed(t,f,h)
1067    if f == nil then
1068        f = { }
1069        h = { }
1070    end
1071    for k=1,#t do
1072        local v = t[k]
1073        if type(v) == "table" then
1074            collapsed(v,f,h)
1075        elseif not h[v] then
1076            f[#f+1] = v
1077            h[v] = true
1078        end
1079    end
1080    return f
1081end
1082
1083local function collapsedhash(t,h)
1084    if h == nil then
1085        h = { }
1086    end
1087    for k=1,#t do
1088        local v = t[k]
1089        if type(v) == "table" then
1090            collapsedhash(v,h)
1091        else
1092            h[v] = true
1093        end
1094    end
1095    return h
1096end
1097
1098table.collapsed     = collapsed     -- 20% faster than unique(collapsed(t))
1099table.collapsedhash = collapsedhash
1100
1101local function unnest(t,f) -- only used in mk, for old times sake
1102    if not f then          -- and only relevant for token lists
1103        f = { }            -- this one can become obsolete
1104    end
1105    for i=1,#t do
1106        local v = t[i]
1107        if type(v) == "table" then
1108            if type(v[1]) == "table" then
1109                unnest(v,f)
1110            else
1111                f[#f+1] = v
1112            end
1113        else
1114            f[#f+1] = v
1115        end
1116    end
1117    return f
1118end
1119
1120function table.unnest(t) -- bad name
1121    return unnest(t)
1122end
1123
1124local function are_equal(a,b,n,m) -- indexed
1125    if a == b then
1126        return true
1127    elseif a and b and #a == #b then
1128        if not n then
1129            n = 1
1130        end
1131        if not m then
1132            m = #a
1133        end
1134        for i=n,m do
1135            local ai, bi = a[i], b[i]
1136            if ai==bi then
1137                -- same
1138            elseif type(ai) == "table" and type(bi) == "table" then
1139                if not are_equal(ai,bi) then
1140                    return false
1141                end
1142            else
1143                return false
1144            end
1145        end
1146        return true
1147    else
1148        return false
1149    end
1150end
1151
1152local function identical(a,b) -- assumes same structure
1153    if a ~= b then
1154        for ka, va in next, a do
1155            local vb = b[ka]
1156            if va == vb then
1157                -- same
1158            elseif type(va) == "table" and  type(vb) == "table" then
1159                if not identical(va,vb) then
1160                    return false
1161                end
1162            else
1163                return false
1164            end
1165        end
1166    end
1167    return true
1168end
1169
1170table.identical = identical
1171table.are_equal = are_equal
1172
1173local function sparse(old,nest,keeptables)
1174    local new  = { }
1175    for k, v in next, old do
1176        if not (v == "" or v == false) then
1177            if nest and type(v) == "table" then
1178                v = sparse(v,nest)
1179                if keeptables or next(v) ~= nil then
1180                    new[k] = v
1181                end
1182            else
1183                new[k] = v
1184            end
1185        end
1186    end
1187    return new
1188end
1189
1190table.sparse = sparse
1191
1192function table.compact(t)
1193    return sparse(t,true,true)
1194end
1195
1196function table.contains(t, v)
1197    if t then
1198        for i=1, #t do
1199            if t[i] == v then
1200                return i
1201            end
1202        end
1203    end
1204    return false
1205end
1206
1207function table.count(t)
1208    local n = 0
1209    for k, v in next, t do
1210        n = n + 1
1211    end
1212    return n
1213end
1214
1215function table.swapped(t,s) -- hash, we need to make sure we don't mess up next
1216    local n = { }
1217    if s then
1218        for k, v in next, s do
1219            n[k] = v
1220        end
1221    end
1222    for k, v in next, t do
1223        n[v] = k
1224    end
1225    return n
1226end
1227
1228function table.hashed(t) -- list, add hash to index (save because we are not yet mixed
1229    for i=1,#t do
1230        t[t[i]] = i
1231    end
1232    return t
1233end
1234
1235function table.mirrored(t) -- hash, we need to make sure we don't mess up next
1236    local n = { }
1237    for k, v in next, t do
1238        n[v] = k
1239        n[k] = v
1240    end
1241    return n
1242end
1243
1244function table.reversed(t)
1245    if t then
1246        local tt = { }
1247        local tn = #t
1248        if tn > 0 then
1249            local ttn = 0
1250            for i=tn,1,-1 do
1251                ttn = ttn + 1
1252                tt[ttn] = t[i]
1253            end
1254        end
1255        return tt
1256    end
1257end
1258
1259function table.reverse(t) -- check with 5.3 ?
1260    if t then
1261        local n = #t
1262        local m = n + 1
1263        for i=1,floor(n/2) do -- maybe just n//2
1264            local j = m - i
1265            t[i], t[j] = t[j], t[i]
1266        end
1267        return t
1268    end
1269end
1270
1271-- This one is for really simple cases where need a hash from a table.
1272
1273local function sequenced(t,sep,simple)
1274    if not t then
1275        return ""
1276    elseif type(t) ~= "table" then
1277        return t -- handy fallback
1278    end
1279    local n = #t
1280    local s = { }
1281    if n > 0 then
1282        -- indexed
1283        for i=1,n do
1284            local v = t[i]
1285            if type(v) == "table" then
1286                s[i] = "{" .. sequenced(v,sep,simple) .. "}"
1287            else
1288                s[i] = tostring(t[i])
1289            end
1290        end
1291    else
1292        -- hashed
1293        n = 0
1294        for k, v in sortedhash(t) do
1295            if simple then
1296                if v == true then
1297                    n = n + 1
1298                    s[n] = k
1299                elseif v and v~= "" then
1300                    n = n + 1
1301                    if type(v) == "table" then
1302                        s[n] = k .. "={" .. sequenced(v,sep,simple) .. "}"
1303                    else
1304                        s[n] = k .. "=" .. tostring(v)
1305                    end
1306                end
1307            else
1308                n = n + 1
1309                if type(v) == "table" then
1310                    s[n] = k .. "={" .. sequenced(v,sep,simple) .. "}"
1311                else
1312                    s[n] = k .. "=" .. tostring(v)
1313                end
1314            end
1315        end
1316    end
1317    if sep == true then
1318        return "{ " .. concat(s,", ") .. " }"
1319    else
1320        return concat(s,sep or " | ")
1321    end
1322end
1323
1324table.sequenced = sequenced
1325
1326function table.print(t,...)
1327    if type(t) ~= "table" then
1328        print(tostring(t))
1329    else
1330        serialize(print,t,...)
1331    end
1332end
1333
1334if setinspector then
1335    setinspector("table",function(v) if type(v) == "table" then serialize(print,v,"table") return true end end)
1336end
1337
1338-- -- -- obsolete but we keep them for a while and might comment them later -- -- --
1339
1340-- roughly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)
1341
1342function table.sub(t,i,j)
1343    return { unpack(t,i,j) }
1344end
1345
1346-- slower than #t on indexed tables (#t only returns the size of the numerically indexed slice)
1347
1348function table.is_empty(t)
1349    return not t or next(t) == nil
1350end
1351
1352function table.has_one_entry(t)
1353    return t and next(t,next(t)) == nil
1354end
1355
1356-- new (rather basic, not indexed and nested)
1357
1358function table.loweredkeys(t) -- maybe utf
1359    local l = { }
1360    for k, v in next, t do
1361        l[lower(k)] = v
1362    end
1363    return l
1364end
1365
1366-- new, might move (maybe duplicate)
1367
1368function table.unique(old)
1369    local hash = { }
1370    local new  = { }
1371    local n    = 0
1372    for i=1,#old do
1373        local oi = old[i]
1374        if not hash[oi] then
1375            n = n + 1
1376            new[n] = oi
1377            hash[oi] = true
1378        end
1379    end
1380    return new
1381end
1382
1383function table.sorted(t,...)
1384    sort(t,...)
1385    return t -- still sorts in-place
1386end
1387
1388--
1389
1390function table.values(t,s) -- optional sort flag
1391    if t then
1392        local values = { }
1393        local keys   = { }
1394        local v      = 0
1395        for key, value in next, t do
1396            if not keys[value] then
1397                v = v + 1
1398                values[v] = value
1399                keys[k]   = key
1400            end
1401        end
1402        if s then
1403            sort(values)
1404        end
1405        return values
1406    else
1407        return { }
1408    end
1409end
1410
1411-- maybe this will move to util-tab.lua
1412
1413-- for k, v in table.filtered(t,pattern)          do ... end
1414-- for k, v in table.filtered(t,pattern,true)     do ... end
1415-- for k, v in table.filtered(t,pattern,true,cmp) do ... end
1416
1417function table.filtered(t,pattern,sort,cmp)
1418    if t and type(pattern) == "string" then
1419        if sort then
1420            local s
1421            if cmp then
1422                -- it would be nice if the sort function would accept a third argument (or nicer, an optional first)
1423                s = sortedhashkeys(t,function(a,b) return cmp(t,a,b) end)
1424            else
1425                s = sortedkeys(t) -- the robust one
1426            end
1427            local n = 0
1428            local m = #s
1429            local function kv(s)
1430                while n < m do
1431                    n = n + 1
1432                    local k = s[n]
1433                    if find(k,pattern) then
1434                        return k, t[k]
1435                    end
1436                end
1437            end
1438            return kv, s
1439        else
1440            local n = next(t)
1441            local function iterator()
1442                while n ~= nil do
1443                    local k = n
1444                    n = next(t,k)
1445                    if find(k,pattern) then
1446                        return k, t[k]
1447                    end
1448                end
1449            end
1450            return iterator, t
1451        end
1452    else
1453        return nothing
1454    end
1455end
1456
1457-- lua 5.3:
1458
1459if not table.move then
1460
1461    function table.move(a1,f,e,t,a2)
1462        if a2 and a1 ~= a2 then
1463            for i=f,e do
1464                a2[t] = a1[i]
1465                t = t + 1
1466            end
1467            return a2
1468        else
1469            t = t + e - f
1470            for i=e,f,-1 do
1471                a1[t] = a1[i]
1472                t = t - 1
1473            end
1474            return a1
1475        end
1476    end
1477
1478end
1479