util-tab.lua /size: 33 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['util-tab'] = {
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
9utilities        = utilities or {}
10utilities.tables = utilities.tables or { }
11local tables     = utilities.tables
12
13local format, gmatch, gsub, sub = string.format, string.gmatch, string.gsub, string.sub
14local concat, insert, remove, sort = table.concat, table.insert, table.remove, table.sort
15local setmetatable, getmetatable, tonumber, tostring, rawget = setmetatable, getmetatable, tonumber, tostring, rawget
16local type, next, rawset, tonumber, tostring, load, select = type, next, rawset, tonumber, tostring, load, select
17local lpegmatch, P, Cs, Cc = lpeg.match, lpeg.P, lpeg.Cs, lpeg.Cc
18local sortedkeys, sortedpairs = table.sortedkeys, table.sortedpairs
19local formatters = string.formatters
20local utftoeight = utf.toeight
21
22local splitter = lpeg.tsplitat(".")
23
24function utilities.tables.definetable(target,nofirst,nolast) -- defines undefined tables
25    local composed = nil
26    local t        = { }
27    local snippets = lpegmatch(splitter,target)
28    for i=1,#snippets - (nolast and 1 or 0) do
29        local name = snippets[i]
30        if composed then
31            composed = composed .. "." .. name
32                t[#t+1] = formatters["if not %s then %s = { } end"](composed,composed)
33        else
34            composed = name
35            if not nofirst then
36                t[#t+1] = formatters["%s = %s or { }"](composed,composed)
37            end
38        end
39    end
40    if composed then
41        if nolast then
42            composed = composed .. "." .. snippets[#snippets]
43        end
44        return concat(t,"\n"), composed -- could be shortcut
45    else
46        return "", target
47    end
48end
49
50-- local t = tables.definedtable("a","b","c","d")
51
52function tables.definedtable(...)
53    local t = _G
54    for i=1,select("#",...) do
55        local li = select(i,...)
56        local tl = t[li]
57        if not tl then
58            tl = { }
59            t[li] = tl
60        end
61        t = tl
62    end
63    return t
64end
65
66function tables.accesstable(target,root)
67    local t = root or _G
68    for name in gmatch(target,"([^%.]+)") do
69        t = t[name]
70        if not t then
71            return
72        end
73    end
74    return t
75end
76
77function tables.migratetable(target,v,root)
78    local t = root or _G
79    local names = lpegmatch(splitter,target)
80    for i=1,#names-1 do
81        local name = names[i]
82        t[name] = t[name] or { }
83        t = t[name]
84        if not t then
85            return
86        end
87    end
88    t[names[#names]] = v
89end
90
91function tables.removevalue(t,value) -- todo: n
92    if value then
93        for i=1,#t do
94            if t[i] == value then
95                remove(t,i)
96                -- remove all, so no: return
97            end
98        end
99    end
100end
101
102function tables.replacevalue(t,oldvalue,newvalue)
103    if oldvalue and newvalue then
104        for i=1,#t do
105            if t[i] == oldvalue then
106                t[i] = newvalue
107                -- replace all, so no: return
108            end
109        end
110    end
111end
112
113function tables.insertbeforevalue(t,value,extra)
114    for i=1,#t do
115        if t[i] == extra then
116            remove(t,i)
117        end
118    end
119    for i=1,#t do
120        if t[i] == value then
121            insert(t,i,extra)
122            return
123        end
124    end
125    insert(t,1,extra)
126end
127
128function tables.insertaftervalue(t,value,extra)
129    for i=1,#t do
130        if t[i] == extra then
131            remove(t,i)
132        end
133    end
134    for i=1,#t do
135        if t[i] == value then
136            insert(t,i+1,extra)
137            return
138        end
139    end
140    insert(t,#t+1,extra)
141end
142
143-- experimental
144
145local escape = Cs(Cc('"') * ((P('"')/'""' + P(1))^0) * Cc('"'))
146
147function table.tocsv(t,specification)
148    if t and #t > 0 then
149        local result = { }
150        local r = { }
151        specification = specification or { }
152        local fields = specification.fields
153        if type(fields) ~= "string" then
154            fields = sortedkeys(t[1])
155        end
156        local separator = specification.separator or ","
157        local noffields = #fields
158        if specification.preamble == true then
159            for f=1,noffields do
160                r[f] = lpegmatch(escape,tostring(fields[f]))
161            end
162            result[1] = concat(r,separator)
163        end
164        for i=1,#t do
165            local ti = t[i]
166            for f=1,noffields do
167                local field = ti[fields[f]]
168                if type(field) == "string" then
169                    r[f] = lpegmatch(escape,field)
170                else
171                    r[f] = tostring(field)
172                end
173            end
174         -- result[#result+1] = concat(r,separator)
175            result[i+1] = concat(r,separator)
176        end
177        return concat(result,"\n")
178    else
179        return ""
180    end
181end
182
183-- local nspaces = utilities.strings.newrepeater(" ")
184-- local escape  = Cs((P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;" + P(1))^0)
185--
186-- local function toxml(t,d,result,step)
187--     for k, v in sortedpairs(t) do
188--         local s = nspaces[d]
189--         local tk = type(k)
190--         local tv = type(v)
191--         if tv == "table" then
192--             if tk == "number" then
193--                 result[#result+1] = format("%s<entry n='%s'>",s,k)
194--                 toxml(v,d+step,result,step)
195--                 result[#result+1] = format("%s</entry>",s,k)
196--             else
197--                 result[#result+1] = format("%s<%s>",s,k)
198--                 toxml(v,d+step,result,step)
199--                 result[#result+1] = format("%s</%s>",s,k)
200--             end
201--         elseif tv == "string" then
202--             if tk == "number" then
203--                 result[#result+1] = format("%s<entry n='%s'>%s</entry>",s,k,lpegmatch(escape,v),k)
204--             else
205--                 result[#result+1] = format("%s<%s>%s</%s>",s,k,lpegmatch(escape,v),k)
206--             end
207--         elseif tk == "number" then
208--             result[#result+1] = format("%s<entry n='%s'>%s</entry>",s,k,tostring(v),k)
209--         else
210--             result[#result+1] = format("%s<%s>%s</%s>",s,k,tostring(v),k)
211--         end
212--     end
213-- end
214--
215-- much faster
216
217local nspaces = utilities.strings.newrepeater(" ")
218
219local function toxml(t,d,result,step)
220    local r = #result
221    for k, v in sortedpairs(t) do
222        local s = nspaces[d] -- inlining this is somewhat faster but gives more formatters
223        local tk = type(k)
224        local tv = type(v)
225        if tv == "table" then
226            if tk == "number" then
227                r = r + 1 result[r] = formatters["%s<entry n='%s'>"](s,k)
228                r = toxml(v,d+step,result,step)
229                r = r + 1 result[r] = formatters["%s</entry>"](s,k)
230            else
231                r = r + 1 result[r] = formatters["%s<%s>"](s,k)
232                r = toxml(v,d+step,result,step)
233                r = r + 1 result[r] = formatters["%s</%s>"](s,k)
234            end
235        elseif tv == "string" then
236            if tk == "number" then
237                r = r + 1 result[r] = formatters["%s<entry n='%s'>%!xml!</entry>"](s,k,v,k)
238            else
239                r = r + 1 result[r] = formatters["%s<%s>%!xml!</%s>"](s,k,v,k)
240            end
241        elseif tk == "number" then
242            r = r + 1 result[r] = formatters["%s<entry n='%s'>%S</entry>"](s,k,v,k)
243        else
244            r = r + 1 result[r] = formatters["%s<%s>%S</%s>"](s,k,v,k)
245        end
246    end
247    return r
248end
249
250-- function table.toxml(t,name,nobanner,indent,spaces)
251--     local noroot = name == false
252--     local result = (nobanner or noroot) and { } or { "<?xml version='1.0' standalone='yes' ?>" }
253--     local indent = rep(" ",indent or 0)
254--     local spaces = rep(" ",spaces or 1)
255--     if noroot then
256--         toxml( t, inndent, result, spaces)
257--     else
258--         toxml( { [name or "root"] = t }, indent, result, spaces)
259--     end
260--     return concat(result,"\n")
261-- end
262
263function table.toxml(t,specification)
264    specification = specification or { }
265    local name   = specification.name
266    local noroot = name == false
267    local result = (specification.nobanner or noroot) and { } or { "<?xml version='1.0' standalone='yes' ?>" }
268    local indent = specification.indent or 0
269    local spaces = specification.spaces or 1
270    if noroot then
271        toxml( t, indent, result, spaces)
272    else
273        toxml( { [name or "data"] = t }, indent, result, spaces)
274    end
275    return concat(result,"\n")
276end
277
278-- also experimental
279
280-- encapsulate(table,utilities.tables)
281-- encapsulate(table,utilities.tables,true)
282-- encapsulate(table,true)
283
284function tables.encapsulate(core,capsule,protect)
285    if type(capsule) ~= "table" then
286        protect = true
287        capsule = { }
288    end
289    for key, value in next, core do
290        if capsule[key] then
291            print(formatters["\ninvalid %s %a in %a"]("inheritance",key,core))
292            os.exit()
293        else
294            capsule[key] = value
295        end
296    end
297    if protect then
298        for key, value in next, core do
299            core[key] = nil
300        end
301        setmetatable(core, {
302            __index = capsule,
303            __newindex = function(t,key,value)
304                if capsule[key] then
305                    print(formatters["\ninvalid %s %a' in %a"]("overload",key,core))
306                    os.exit()
307                else
308                    rawset(t,key,value)
309                end
310            end
311        } )
312    end
313end
314
315-- best keep [%q] keys (as we have some in older applications i.e. saving user data (otherwise
316-- we also need to check for reserved words)
317
318if JITSUPPORTED then
319
320    local f_hashed_string   = formatters["[%Q]=%Q,"]
321    local f_hashed_number   = formatters["[%Q]=%s,"]
322    local f_hashed_boolean  = formatters["[%Q]=%l,"]
323    local f_hashed_table    = formatters["[%Q]="]
324
325    local f_indexed_string  = formatters["[%s]=%Q,"]
326    local f_indexed_number  = formatters["[%s]=%s,"]
327    local f_indexed_boolean = formatters["[%s]=%l,"]
328    local f_indexed_table   = formatters["[%s]="]
329
330    local f_ordered_string  = formatters["%Q,"]
331    local f_ordered_number  = formatters["%s,"]
332    local f_ordered_boolean = formatters["%l,"]
333
334    function table.fastserialize(t,prefix) -- todo, move local function out
335
336        -- prefix should contain the =
337        -- not sorted
338        -- only number and string indices (currently)
339
340        local r = { type(prefix) == "string" and prefix or "return" }
341        local m = 1
342        local function fastserialize(t,outer) -- no mixes
343            local n = #t
344            m = m + 1
345            r[m] = "{"
346            if n > 0 then
347                local v = t[0]
348                if v then
349                    local tv = type(v)
350                    if tv == "string" then
351                        m = m + 1 r[m] = f_indexed_string(0,v)
352                    elseif tv == "number" then
353                        m = m + 1 r[m] = f_indexed_number(0,v)
354                    elseif tv == "table" then
355                        m = m + 1 r[m] = f_indexed_table(0)
356                        fastserialize(v)
357                        m = m + 1 r[m] = f_indexed_table(0)
358                    elseif tv == "boolean" then
359                        m = m + 1 r[m] = f_indexed_boolean(0,v)
360                    end
361                end
362                for i=1,n do
363                    local v  = t[i]
364                    local tv = type(v)
365                    if tv == "string" then
366                        m = m + 1 r[m] = f_ordered_string(v)
367                    elseif tv == "number" then
368                        m = m + 1 r[m] = f_ordered_number(v)
369                    elseif tv == "table" then
370                        fastserialize(v)
371                    elseif tv == "boolean" then
372                        m = m + 1 r[m] = f_ordered_boolean(v)
373                    end
374                end
375            end
376            -- hm, can't we avoid this ... lua should have a way to check if there
377            -- is a hash part
378            for k, v in next, t do
379                local tk = type(k)
380                if tk == "number" then
381                    if k > n or k < 0 then
382                        local tv = type(v)
383                        if tv == "string" then
384                            m = m + 1 r[m] = f_indexed_string(k,v)
385                        elseif tv == "number" then
386                            m = m + 1 r[m] = f_indexed_number(k,v)
387                        elseif tv == "table" then
388                            m = m + 1 r[m] = f_indexed_table(k)
389                            fastserialize(v)
390                        elseif tv == "boolean" then
391                            m = m + 1 r[m] = f_indexed_boolean(k,v)
392                        end
393                    end
394                else
395                    local tv = type(v)
396                    if tv == "string" then
397                        m = m + 1 r[m] = f_hashed_string(k,v)
398                    elseif tv == "number" then
399                        m = m + 1 r[m] = f_hashed_number(k,v)
400                    elseif tv == "table" then
401                        m = m + 1 r[m] = f_hashed_table(k)
402                        fastserialize(v)
403                    elseif tv == "boolean" then
404                        m = m + 1 r[m] = f_hashed_boolean(k,v)
405                    end
406                end
407            end
408            m = m + 1
409            if outer then
410                r[m] = "}"
411            else
412                r[m] = "},"
413            end
414            return r
415        end
416        return concat(fastserialize(t,true))
417    end
418
419else
420
421 -- local f_v = formatters["[%q]=%q,"]
422 -- local f_t = formatters["[%q]="]
423 -- local f_q = formatters["%q,"]
424
425    function table.fastserialize(t,prefix) -- todo, move local function out
426        local r = { type(prefix) == "string" and prefix or "return" }
427        local m = 1
428        local function fastserialize(t,outer) -- no mixes
429            local n = #t
430            m = m + 1
431            r[m] = "{"
432            if n > 0 then
433                local v = t[0]
434                if v then
435                    m = m + 1
436                    r[m] = "[0]="
437                    if type(v) == "table" then
438                        fastserialize(v)
439                    else
440                        r[m] = format("%q,",v)
441                    end
442                end
443                for i=1,n do
444                    local v = t[i]
445                    m = m + 1
446                    if type(v) == "table" then
447                        r[m] = format("[%i]=",i)
448                        fastserialize(v)
449                    else
450                        r[m] = format("[%i]=%q,",i,v)
451                    end
452                end
453            end
454            -- hm, can't we avoid this ... lua should have a way to check if there
455            -- is a hash part
456            for k, v in next, t do
457                local tk = type(k)
458                if tk == "number" then
459                    if k > n or k < 0 then
460                        m = m + 1
461                        if type(v) == "table" then
462                            r[m] = format("[%i]=",k)
463                            fastserialize(v)
464                        else
465                            r[m] = format("[%i]=%q,",k,v)
466                        end
467                    end
468                else
469                    m = m + 1
470                    if type(v) == "table" then
471                        r[m] = format("[%q]=",k)
472                        fastserialize(v)
473                    else
474                        r[m] = format("[%q]=%q,",k,v)
475                    end
476                end
477            end
478            m = m + 1
479            if outer then
480                r[m] = "}"
481            else
482                r[m] = "},"
483            end
484            return r
485        end
486        return concat(fastserialize(t,true))
487    end
488
489end
490
491function table.deserialize(str)
492    if not str or str == "" then
493        return
494    end
495    local code = load(str)
496    if not code then
497        return
498    end
499    code = code()
500    if not code then
501        return
502    end
503    return code
504end
505
506-- inspect(table.fastserialize { a = 1, b = { [0]=4, { 5, 6 } }, c = { d = 7, e = 'f"g\nh' } })
507
508function table.load(filename,loader)
509    if filename then
510        local t = (loader or io.loaddata)(filename)
511        if t and t ~= "" then
512            local t = utftoeight(t)
513            t = load(t)
514            if type(t) == "function" then
515                t = t()
516                if type(t) == "table" then
517                    return t
518                end
519            end
520        end
521    end
522end
523
524function table.save(filename,t,n,...)
525    io.savedata(filename,table.serialize(t,n == nil and true or n,...)) -- no frozen table.serialize
526end
527
528local f_key_value    = formatters["%s=%q"]
529local f_add_table    = formatters[" {%t},\n"]
530local f_return_table = formatters["return {\n%t}"]
531
532local function slowdrop(t) -- maybe less memory (intermediate concat)
533    local r = { }
534    local l = { }
535    for i=1,#t do
536        local ti = t[i]
537        local j = 0
538        for k, v in next, ti do
539            j = j + 1
540            l[j] = f_key_value(k,v)
541        end
542        r[i] = f_add_table(l)
543    end
544    return f_return_table(r)
545end
546
547local function fastdrop(t)
548    local r = { "return {\n" }
549    local m = 1
550    for i=1,#t do
551        local ti = t[i]
552        m = m + 1 r[m] = " {"
553        for k, v in next, ti do
554            m = m + 1 r[m] = f_key_value(k,v)
555        end
556        m = m + 1 r[m] = "},\n"
557    end
558    m = m + 1
559    r[m] = "}"
560    return concat(r)
561end
562
563function table.drop(t,slow) -- only { { a=2 }, {a=3} } -- for special cases
564    if #t == 0 then
565        return "return { }"
566    elseif slow == true then
567        return slowdrop(t) -- less memory
568    else
569        return fastdrop(t) -- some 15% faster
570    end
571end
572
573-- inspect(table.drop({ { a=2 }, {a=3} }))
574-- inspect(table.drop({ { a=2 }, {a=3} },true))
575
576-- function table.autokey(t,k) -- replaced
577--     local v = { }
578--     t[k] = v
579--     return v
580-- end
581
582local selfmapper = { __index = function(t,k) t[k] = k return k end }
583
584function table.twowaymapper(t)    -- takes a 0/1 .. n indexed table and returns
585    if not t then                 -- it with  string-numbers as indices + reverse
586        t = { }                   -- mapping (all strings) .. used in cvs etc but
587    else                          -- typically a helper that one forgets about
588        local zero = rawget(t,0)  -- so it might move someplace else
589        for i=zero and 0 or 1,#t do
590            local ti = t[i]       -- t[1]     = "one"
591            if ti then
592                local i = tostring(i)
593                t[i]    = ti      -- t["1"]   = "one"
594                t[ti]   = i       -- t["one"] = "1"
595            end
596        end
597    end
598 -- setmetatableindex(t,"key")
599    setmetatable(t,selfmapper)
600    return t
601end
602
603-- The next version is somewhat faster, although in practice one will seldom
604-- serialize a lot using this one. Often the above variants are more efficient.
605-- If we would really need this a lot, we could hash q keys, or just not used
606-- indented code.
607
608-- char-def.lua : 0.53 -> 0.38
609-- husayni.tma  : 0.28 -> 0.19
610
611local f_start_key_idx     = formatters["%w{"]
612local f_start_key_num     = JITSUPPORTED and formatters["%w[%s]={"] or formatters["%w[%q]={"]
613local f_start_key_str     = formatters["%w[%q]={"]
614local f_start_key_boo     = formatters["%w[%l]={"]
615local f_start_key_nop     = formatters["%w{"]
616
617local f_stop              = formatters["%w},"]
618
619local f_key_num_value_num = JITSUPPORTED and formatters["%w[%s]=%s,"] or formatters["%w[%s]=%q,"]
620local f_key_str_value_num = JITSUPPORTED and formatters["%w[%Q]=%s,"] or formatters["%w[%Q]=%q,"]
621local f_key_boo_value_num = JITSUPPORTED and formatters["%w[%l]=%s,"] or formatters["%w[%l]=%q,"]
622
623local f_key_num_value_str = JITSUPPORTED and formatters["%w[%s]=%Q,"] or formatters["%w[%q]=%Q,"]
624local f_key_str_value_str = formatters["%w[%Q]=%Q,"]
625local f_key_boo_value_str = formatters["%w[%l]=%Q,"]
626
627local f_key_num_value_boo = JITSUPPORTED and formatters["%w[%s]=%l,"] or formatters["%w[%q]=%l,"]
628local f_key_str_value_boo = formatters["%w[%Q]=%l,"]
629local f_key_boo_value_boo = formatters["%w[%l]=%l,"]
630
631local f_key_num_value_not = JITSUPPORTED and formatters["%w[%s]={},"] or formatters["%w[%q]={},"]
632local f_key_str_value_not = formatters["%w[%Q]={},"]
633local f_key_boo_value_not = formatters["%w[%l]={},"]
634
635local f_key_num_value_seq = JITSUPPORTED and formatters["%w[%s]={ %, t },"] or formatters["%w[%q]={ %, t },"]
636local f_key_str_value_seq = formatters["%w[%Q]={ %, t },"]
637local f_key_boo_value_seq = formatters["%w[%l]={ %, t },"]
638
639local f_val_num           = JITSUPPORTED and formatters["%w%s,"] or formatters["%w%q,"]
640local f_val_str           = formatters["%w%Q,"]
641local f_val_boo           = formatters["%w%l,"]
642local f_val_not           = formatters["%w{},"]
643local f_val_seq           = formatters["%w{ %, t },"]
644local f_fin_seq           = formatters[" %, t }"]
645
646local f_table_return      = formatters["return {"]
647local f_table_name        = formatters["%s={"]
648local f_table_direct      = formatters["{"]
649local f_table_entry       = formatters["[%Q]={"]
650local f_table_finish      = formatters["}"]
651
652local spaces = utilities.strings.newrepeater(" ")
653
654local original_serialize = table.serialize -- the extensive one, the one we started with
655
656-- there is still room for optimization: index run, key run, but i need to check with the
657-- latest lua for the value of #n (with holes) .. anyway for tracing purposes we want
658-- indices / keys being sorted, so it will never be real fast
659
660local is_simple_table = table.is_simple_table
661
662-- local function is_simple_table(t)
663--     local nt = #t
664--     if nt > 0 then
665--         local n = 0
666--         for _, v in next, t do
667--             n = n + 1
668--             if type(v) == "table" then
669--                 return nil
670--             end
671--         end
672--      -- local haszero = t[0]
673--         local haszero = rawget(t,0) -- don't trigger meta
674--         if n == nt then
675--             local tt = { }
676--             for i=1,nt do
677--                 local v = t[i]
678--                 local tv = type(v)
679--                 if tv == "number" then
680--                     tt[i] = v -- not needed tostring(v)
681--                 elseif tv == "string" then
682--                     tt[i] = format("%q",v) -- f_string(v)
683--                 elseif tv == "boolean" then
684--                     tt[i] = v and "true" or "false"
685--                 else
686--                     return nil
687--                 end
688--             end
689--             return tt
690--         elseif haszero and (n == nt + 1) then
691--             local tt = { }
692--             for i=0,nt do
693--                 local v = t[i]
694--                 local tv = type(v)
695--                 if tv == "number" then
696--                     tt[i+1] = v -- not needed tostring(v)
697--                 elseif tv == "string" then
698--                     tt[i+1] = format("%q",v) -- f_string(v)
699--                 elseif tv == "boolean" then
700--                     tt[i+1] = v and "true" or "false"
701--                 else
702--                     return nil
703--                 end
704--             end
705--             tt[1] = "[0] = " .. tt[1]
706--             return tt
707--         end
708--     end
709--     return nil
710-- end
711
712-- In order to overcome the luajit (65K constant) limitation I tried a split approach,
713-- i.e. outputting the first level tables as locals but that failed with large cjk
714-- fonts too so I removed that ... just use luatex instead.
715
716local function serialize(root,name,specification)
717
718    if type(specification) == "table" then
719        return original_serialize(root,name,specification) -- the original one
720    end
721
722    local t    -- = { }
723    local n       = 1
724 -- local m       = 0 -- no gain
725    local unknown = false
726
727    local function do_serialize(root,name,depth,level,indexed)
728        if level > 0 then
729            n = n + 1
730            if indexed then
731                t[n] = f_start_key_idx(depth)
732            else
733                local tn = type(name)
734                if tn == "number" then
735                    t[n] = f_start_key_num(depth,name)
736                elseif tn == "string" then
737                    t[n] = f_start_key_str(depth,name)
738                elseif tn == "boolean" then
739                    t[n] = f_start_key_boo(depth,name)
740                else
741                    t[n] = f_start_key_nop(depth)
742                end
743            end
744            depth = depth + 1
745        end
746        -- we could check for k (index) being number (cardinal)
747        if root and next(root) ~= nil then
748            local first = nil
749            local last  = #root
750            if last > 0 then
751                for k=1,last do
752                    if rawget(root,k) == nil then
753                 -- if root[k] == nil then
754                        last = k - 1
755                        break
756                    end
757                end
758                if last > 0 then
759                    first = 1
760                end
761            end
762            local sk = sortedkeys(root)
763            for i=1,#sk do
764                local k  = sk[i]
765                local v  = root[k]
766                local tv = type(v)
767                local tk = type(k)
768                if first and tk == "number" and k <= last and k >= first then
769                    if tv == "number" then
770                        n = n + 1 t[n] = f_val_num(depth,v)
771                    elseif tv == "string" then
772                        n = n + 1 t[n] = f_val_str(depth,v)
773                    elseif tv == "table" then
774                        if next(v) == nil then -- tricky as next is unpredictable in a hash
775                            n = n + 1 t[n] = f_val_not(depth)
776                        else
777                            local st = is_simple_table(v)
778                            if st then
779                                n = n + 1 t[n] = f_val_seq(depth,st)
780                            else
781                                do_serialize(v,k,depth,level+1,true)
782                            end
783                        end
784                    elseif tv == "boolean" then
785                        n = n + 1 t[n] = f_val_boo(depth,v)
786                    elseif unknown then
787                        n = n + 1 t[n] = f_val_str(depth,tostring(v))
788                    end
789                elseif tv == "number" then
790                    if tk == "number" then
791                        n = n + 1 t[n] = f_key_num_value_num(depth,k,v)
792                    elseif tk == "string" then
793                        n = n + 1 t[n] = f_key_str_value_num(depth,k,v)
794                    elseif tk == "boolean" then
795                        n = n + 1 t[n] = f_key_boo_value_num(depth,k,v)
796                    elseif unknown then
797                        n = n + 1 t[n] = f_key_str_value_num(depth,tostring(k),v)
798                    end
799                elseif tv == "string" then
800                    if tk == "number" then
801                        n = n + 1 t[n] = f_key_num_value_str(depth,k,v)
802                    elseif tk == "string" then
803                        n = n + 1 t[n] = f_key_str_value_str(depth,k,v)
804                    elseif tk == "boolean" then
805                        n = n + 1 t[n] = f_key_boo_value_str(depth,k,v)
806                    elseif unknown then
807                        n = n + 1 t[n] = f_key_str_value_str(depth,tostring(k),v)
808                    end
809                elseif tv == "table" then
810                    if next(v) == nil then
811                        if tk == "number" then
812                            n = n + 1 t[n] = f_key_num_value_not(depth,k)
813                        elseif tk == "string" then
814                            n = n + 1 t[n] = f_key_str_value_not(depth,k)
815                        elseif tk == "boolean" then
816                            n = n + 1 t[n] = f_key_boo_value_not(depth,k)
817                        elseif unknown then
818                            n = n + 1 t[n] = f_key_str_value_not(depth,tostring(k))
819                        end
820                    else
821                        local st = is_simple_table(v)
822                        if not st then
823                            do_serialize(v,k,depth,level+1)
824                        elseif tk == "number" then
825                            n = n + 1 t[n] = f_key_num_value_seq(depth,k,st)
826                        elseif tk == "string" then
827                            n = n + 1 t[n] = f_key_str_value_seq(depth,k,st)
828                        elseif tk == "boolean" then
829                            n = n + 1 t[n] = f_key_boo_value_seq(depth,k,st)
830                        elseif unknown then
831                            n = n + 1 t[n] = f_key_str_value_seq(depth,tostring(k),st)
832                        end
833                    end
834                elseif tv == "boolean" then
835                    if tk == "number" then
836                        n = n + 1 t[n] = f_key_num_value_boo(depth,k,v)
837                    elseif tk == "string" then
838                        n = n + 1 t[n] = f_key_str_value_boo(depth,k,v)
839                    elseif tk == "boolean" then
840                        n = n + 1 t[n] = f_key_boo_value_boo(depth,k,v)
841                    elseif unknown then
842                        n = n + 1 t[n] = f_key_str_value_boo(depth,tostring(k),v)
843                    end
844                else
845                    if tk == "number" then
846                        n = n + 1 t[n] = f_key_num_value_str(depth,k,tostring(v))
847                    elseif tk == "string" then
848                        n = n + 1 t[n] = f_key_str_value_str(depth,k,tostring(v))
849                    elseif tk == "boolean" then
850                        n = n + 1 t[n] = f_key_boo_value_str(depth,k,tostring(v))
851                    elseif unknown then
852                        n = n + 1 t[n] = f_key_str_value_str(depth,tostring(k),tostring(v))
853                    end
854                end
855             -- if n > 100000 then -- no gain
856             --     local k = m + 1
857             --     t[k] = concat(t,"\n",k,n)
858             --     n = k
859             --     m = k
860             -- end
861            end
862        end
863        if level > 0 then
864            n = n + 1 t[n] = f_stop(depth-1)
865        end
866    end
867
868    local tname = type(name)
869
870    if tname == "string" then
871        if name == "return" then
872            t = { f_table_return() }
873        else
874            t = { f_table_name(name) }
875        end
876    elseif tname == "number" then
877        t = { f_table_entry(name) }
878    elseif tname == "boolean" then
879        if name then
880            t = { f_table_return() }
881        else
882            t = { f_table_direct() }
883        end
884    else
885        t = { f_table_name("t") }
886    end
887
888    if root then
889        -- The dummy access will initialize a table that has a delayed initialization
890        -- using a metatable. (maybe explicitly test for metatable). This can crash on
891        -- metatables that check the index against a number.
892        if getmetatable(root) then -- todo: make this an option, maybe even per subtable
893            local dummy = root._w_h_a_t_e_v_e_r_ -- needed
894            root._w_h_a_t_e_v_e_r_ = nil
895        end
896        -- Let's forget about empty tables.
897        if next(root) ~= nil then
898            local st = is_simple_table(root)
899            if st then
900                return t[1] .. f_fin_seq(st) -- todo: move up and in one go
901            else
902                do_serialize(root,name,1,0)
903            end
904        end
905    end
906    n = n + 1
907    t[n] = f_table_finish()
908    return concat(t,"\n")
909 -- return concat(t,"\n",1,n) -- no gain
910end
911
912table.serialize = serialize
913
914if setinspector then
915    setinspector("table",function(v)
916        if type(v) == "table" then
917            print(serialize(v,"table",{ metacheck = false }))
918            return true
919        end
920    end)
921end
922
923-- ordered hashes (for now here but in the table namespace):
924
925-- local t = table.orderedhash()
926--
927-- t["1"]  = { "a", "b" }
928-- t["2"]  = { }
929-- t["2a"] = { "a", "c", "d" }
930--
931-- for k, v in table.ordered(t) do
932--     ...
933-- end
934
935local mt = {
936    __newindex = function(t,k,v)
937        local n = t.last + 1
938        t.last    = n
939        t.list[n] = k
940        t.hash[k] = v
941    end,
942    __index = function(t,k)
943        return t.hash[k]
944    end,
945    __len = function(t)
946        return t.last
947    end,
948}
949
950function table.orderedhash()
951    return setmetatable({ list = { }, hash = { }, last = 0 }, mt)
952end
953
954function table.ordered(t)
955    local n = t.last
956    if n > 0 then
957        local l = t.list
958        local i = 1
959        local h = t.hash
960        local f = function()
961            if i <= n then
962                local k = i
963                local v = h[l[k]]
964                i = i + 1
965                return k, v
966            end
967        end
968        return f, 1, h[l[1]]
969    else
970        return function() end
971    end
972end
973
974-- function table.randomremove(t,n)
975--     if not n then
976--         n = #t
977--     end
978--     if n > 0 then
979--         return remove(t,random(1,n))
980--     end
981-- end
982
983function combine(target,source)
984    -- no copy so if that is needed one needs to deepcopy source first
985    if target then
986        for k, v in next, source do
987            if type(v) == "table" then
988               target[k] = combine(target[k],source[k])
989            else
990               target[k] = v
991            end
992        end
993        return target
994    else
995        return source
996    end
997end
998
999table.combine = combine
1000
1001-- If needed we can add something (some discussion on the list but I'm not sure if
1002-- it makes sense because merging such mixed tables is quite unusual.
1003--
1004-- function table.himerged(...)
1005--     local result = { }
1006--     local r      = 0
1007--     for i=1,select("#",...) do
1008--         local s = select(i,...)
1009--         if s then
1010--             for k, v in next, s do
1011--                 if type(k) == "number"  then
1012--                     r = r + 1
1013--                     result[r] = v
1014--                 else
1015--                     result[k] = v
1016--                 end
1017--             end
1018--         end
1019--     end
1020--     return result
1021-- end
1022