if not modules then modules = { } end modules ['l-table'] = {
    version   = 1.001,
    comment   = "companion to luat-lib.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

local type, next, tostring, tonumber, ipairs, select = type, next, tostring, tonumber, ipairs, select
local table, string = table, string
local concat, sort, insert, remove = table.concat, table.sort, table.insert, table.remove
local format, lower, dump = string.format, string.lower, string.dump
local getmetatable, setmetatable = getmetatable, setmetatable
local getinfo = debug.getinfo
local lpegmatch, patterns = lpeg.match, lpeg.patterns
local floor = math.floor

-- extra functions, some might go (when not used)

local stripper = patterns.stripper

function table.strip(tab)
    local lst, l = { }, 0
    for i=1,#tab do
        local s = lpegmatch(stripper,tab[i]) or ""
        if s == "" then
            -- skip this one
        else
            l = l + 1
            lst[l] = s
        end
    end
    return lst
end

function table.keys(t)
    if t then
        local keys, k = { }, 0
        for key, _ in next, t do
            k = k + 1
            keys[k] = key
        end
        return keys
    else
        return { }
    end
end

local function compare(a,b)
    local ta, tb = type(a), type(b) -- needed, else 11 < 2
    if ta == tb then
        return a < b
    else
        return tostring(a) < tostring(b)
    end
end

local function sortedkeys(tab)
    if tab then
        local srt, category, s = { }, 0, 0 -- 0=unknown 1=string, 2=number 3=mixed
        for key,_ in next, tab do
            s = s + 1
            srt[s] = key
            if category == 3 then
                -- no further check
            else
                local tkey = type(key)
                if tkey == "string" then
                    category = (category == 2 and 3) or 1
                elseif tkey == "number" then
                    category = (category == 1 and 3) or 2
                else
                    category = 3
                end
            end
        end
        if category == 0 or category == 3 then
            sort(srt,compare)
        else
            sort(srt)
        end
        return srt
    else
        return { }
    end
end

local function sortedhashkeys(tab,cmp) -- fast one
    if tab then
        local srt, s = { }, 0
        for key,_ in next, tab do
            if key then
                s= s + 1
                srt[s] = key
            end
        end
        sort(srt,cmp)
        return srt
    else
        return { }
    end
end

function table.allkeys(t)
    local keys = { }
    for k, v in next, t do
        for k, v in next, v do
            keys[k] = true
        end
    end
    return sortedkeys(keys)
end

table.sortedkeys     = sortedkeys
table.sortedhashkeys = sortedhashkeys

local function nothing() end

local function sortedhash(t,cmp)
    if t then
        local s
        if cmp then
            -- it would be nice if the sort function would accept a third argument (or nicer, an optional first)
            s = sortedhashkeys(t,function(a,b) return cmp(t,a,b) end)
        else
            s = sortedkeys(t) -- the robust one
        end
        local n = 0
        local function kv(s)
            n = n + 1
            local k = s[n]
            return k, t[k]
        end
        return kv, s
    else
        return nothing
    end
end

table.sortedhash  = sortedhash
table.sortedpairs = sortedhash -- obsolete

function table.append(t,list)
    local n = #t
    for i=1,#list do
        n = n + 1
        t[n] = list[i]
    end
    return t
end

function table.prepend(t, list)
    local nl = #list
    local nt = nl + #t
    for i=#t,1,-1 do
        t[nt] = t[i]
        nt = nt - 1
    end
    for i=1,#list do
        t[i] = list[i]
    end
    return t
end

-- function table.merge(t, ...) -- first one is target
--     t = t or { }
--     local lst = { ... }
--     for i=1,#lst do
--         for k, v in next, lst[i] do
--             t[k] = v
--         end
--     end
--     return t
-- end

function table.merge(t, ...) -- first one is target
    t = t or { }
    for i=1,select("#",...) do
        for k, v in next, (select(i,...)) do
            t[k] = v
        end
    end
    return t
end

-- function table.merged(...)
--     local tmp, lst = { }, { ... }
--     for i=1,#lst do
--         for k, v in next, lst[i] do
--             tmp[k] = v
--         end
--     end
--     return tmp
-- end

function table.merged(...)
    local t = { }
    for i=1,select("#",...) do
        for k, v in next, (select(i,...)) do
            t[k] = v
        end
    end
    return t
end

-- function table.imerge(t, ...)
--     local lst, nt = { ... }, #t
--     for i=1,#lst do
--         local nst = lst[i]
--         for j=1,#nst do
--             nt = nt + 1
--             t[nt] = nst[j]
--         end
--     end
--     return t
-- end

function table.imerge(t, ...)
    local nt = #t
    for i=1,select("#",...) do
        local nst = select(i,...)
        for j=1,#nst do
            nt = nt + 1
            t[nt] = nst[j]
        end
    end
    return t
end

-- function table.imerged(...)
--     local tmp, ntmp, lst = { }, 0, {...}
--     for i=1,#lst do
--         local nst = lst[i]
--         for j=1,#nst do
--             ntmp = ntmp + 1
--             tmp[ntmp] = nst[j]
--         end
--     end
--     return tmp
-- end

function table.imerged(...)
    local tmp, ntmp = { }, 0
    for i=1,select("#",...) do
        local nst = select(i,...)
        for j=1,#nst do
            ntmp = ntmp + 1
            tmp[ntmp] = nst[j]
        end
    end
    return tmp
end

local function fastcopy(old,metatabletoo) -- fast one
    if old then
        local new = { }
        for k, v in next, old do
            if type(v) == "table" then
                new[k] = fastcopy(v,metatabletoo) -- was just table.copy
            else
                new[k] = v
            end
        end
        if metatabletoo then
            -- optional second arg
            local mt = getmetatable(old)
            if mt then
                setmetatable(new,mt)
            end
        end
        return new
    else
        return { }
    end
end

-- todo : copy without metatable

local function copy(t, tables) -- taken from lua wiki, slightly adapted
    tables = tables or { }
    local tcopy = {}
    if not tables[t] then
        tables[t] = tcopy
    end
    for i,v in next, t do -- brrr, what happens with sparse indexed
        if type(i) == "table" then
            if tables[i] then
                i = tables[i]
            else
                i = copy(i, tables)
            end
        end
        if type(v) ~= "table" then
            tcopy[i] = v
        elseif tables[v] then
            tcopy[i] = tables[v]
        else
            tcopy[i] = copy(v, tables)
        end
    end
    local mt = getmetatable(t)
    if mt then
        setmetatable(tcopy,mt)
    end
    return tcopy
end

table.fastcopy = fastcopy
table.copy     = copy

function table.derive(parent) -- for the moment not public
    local child = { }
    if parent then
        setmetatable(child,{ __index = parent })
    end
    return child
end

function table.tohash(t,value)
    local h = { }
    if t then
        if value == nil then value = true end
        for _, v in next, t do -- no ipairs here
            h[v] = value
        end
    end
    return h
end

function table.fromhash(t)
    local hsh, h = { }, 0
    for k, v in next, t do -- no ipairs here
        if v then
            h = h + 1
            hsh[h] = k
        end
    end
    return hsh
end

local noquotes, hexify, handle, reduce, compact, inline, functions

local reserved = table.tohash { -- intercept a language inconvenience: no reserved words as key
    'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if',
    'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while',
}

local function simple_table(t)
    if #t > 0 then
        local n = 0
        for _,v in next, t do
            n = n + 1
        end
        if n == #t then
            local tt, nt = { }, 0
            for i=1,#t do
                local v = t[i]
                local tv = type(v)
                if tv == "number" then
                    nt = nt + 1
                    if hexify then
                        tt[nt] = format("0x%04X",v)
                    else
                        tt[nt] = tostring(v) -- tostring not needed
                    end
                elseif tv == "boolean" then
                    nt = nt + 1
                    tt[nt] = tostring(v)
                elseif tv == "string" then
                    nt = nt + 1
                    tt[nt] = format("%q",v)
                else
                    tt = nil
                    break
                end
            end
            return tt
        end
    end
    return nil
end

-- Because this is a core function of mkiv I moved some function calls
-- inline.
--
-- twice as fast in a test:
--
-- local propername = lpeg.P(lpeg.R("AZ","az","__") * lpeg.R("09","AZ","az", "__")^0 * lpeg.P(-1) )

-- problem: there no good number_to_string converter with the best resolution

-- probably using .. is faster than format
-- maybe split in a few cases (yes/no hexify)

-- todo: %g faster on numbers than %s

-- we can speed this up with repeaters and formatters (is indeed faster)

local propername = patterns.propername -- was find(name,"^%a[%w%_]*$")

local function dummy() end

local function do_serialize(root,name,depth,level,indexed)
    if level > 0 then
        depth = depth .. " "
        if indexed then
            handle(format("%s{",depth))
        else
            local tn = type(name)
            if tn == "number" then
                if hexify then
                    handle(format("%s[0x%04X]={",depth,name))
                else
                    handle(format("%s[%s]={",depth,name))
                end
            elseif tn == "string" then
                if noquotes and not reserved[name] and lpegmatch(propername,name) then
                    handle(format("%s%s={",depth,name))
                else
                    handle(format("%s[%q]={",depth,name))
                end
            elseif tn == "boolean" then
                handle(format("%s[%s]={",depth,tostring(name)))
            else
                handle(format("%s{",depth))
            end
        end
    end
    -- we could check for k (index) being number (cardinal)
    if root and next(root) then
     -- local first, last = nil, 0 -- #root cannot be trusted here (will be ok in 5.2 when ipairs is gone)
     -- if compact then
     --     -- NOT: for k=1,#root do (we need to quit at nil)
     --     for k,v in ipairs(root) do -- can we use next?
     --         if not first then first = k end
     --         last = last + 1
     --     end
     -- end
        local first, last = nil, 0
        if compact then
            last = #root
            for k=1,last do
                if root[k] == nil then
                    last = k - 1
                    break
                end
            end
            if last > 0 then
                first = 1
            end
        end
        local sk = sortedkeys(root)
        for i=1,#sk do
            local k = sk[i]
            local v = root[k]
            --~ if v == root then
                -- circular
            --~ else
            local t, tk = type(v), type(k)
            if compact and first and tk == "number" and k >= first and k <= last then
                if t == "number" then
                    if hexify then
                        handle(format("%s 0x%04X,",depth,v))
                    else
                        handle(format("%s %s,",depth,v)) -- %.99g
                    end
                elseif t == "string" then
                    if reduce and tonumber(v) then
                        handle(format("%s %s,",depth,v))
                    else
                        handle(format("%s %q,",depth,v))
                    end
                elseif t == "table" then
                    if not next(v) then
                        handle(format("%s {},",depth))
                    elseif inline then -- and #t > 0
                        local st = simple_table(v)
                        if st then
                            handle(format("%s { %s },",depth,concat(st,", ")))
                        else
                            do_serialize(v,k,depth,level+1,true)
                        end
                    else
                        do_serialize(v,k,depth,level+1,true)
                    end
                elseif t == "boolean" then
                    handle(format("%s %s,",depth,tostring(v)))
                elseif t == "function" then
                    if functions then
                        handle(format('%s load(%q),',depth,dump(v)))
                    else
                        handle(format('%s "function",',depth))
                    end
                else
                    handle(format("%s %q,",depth,tostring(v)))
                end
            elseif k == "__p__" then -- parent
                if false then
                    handle(format("%s __p__=nil,",depth))
                end
            elseif t == "number" then
                if tk == "number" then
                    if hexify then
                        handle(format("%s [0x%04X]=0x%04X,",depth,k,v))
                    else
                        handle(format("%s [%s]=%s,",depth,k,v)) -- %.99g
                    end
                elseif tk == "boolean" then
                    if hexify then
                        handle(format("%s [%s]=0x%04X,",depth,tostring(k),v))
                    else
                        handle(format("%s [%s]=%s,",depth,tostring(k),v)) -- %.99g
                    end
                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
                    if hexify then
                        handle(format("%s %s=0x%04X,",depth,k,v))
                    else
                        handle(format("%s %s=%s,",depth,k,v)) -- %.99g
                    end
                else
                    if hexify then
                        handle(format("%s [%q]=0x%04X,",depth,k,v))
                    else
                        handle(format("%s [%q]=%s,",depth,k,v)) -- %.99g
                    end
                end
            elseif t == "string" then
                if reduce and tonumber(v) then
                    if tk == "number" then
                        if hexify then
                            handle(format("%s [0x%04X]=%s,",depth,k,v))
                        else
                            handle(format("%s [%s]=%s,",depth,k,v))
                        end
                    elseif tk == "boolean" then
                        handle(format("%s [%s]=%s,",depth,tostring(k),v))
                    elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
                        handle(format("%s %s=%s,",depth,k,v))
                    else
                        handle(format("%s [%q]=%s,",depth,k,v))
                    end
                else
                    if tk == "number" then
                        if hexify then
                            handle(format("%s [0x%04X]=%q,",depth,k,v))
                        else
                            handle(format("%s [%s]=%q,",depth,k,v))
                        end
                    elseif tk == "boolean" then
                        handle(format("%s [%s]=%q,",depth,tostring(k),v))
                    elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
                        handle(format("%s %s=%q,",depth,k,v))
                    else
                        handle(format("%s [%q]=%q,",depth,k,v))
                    end
                end
            elseif t == "table" then
                if not next(v) then
                    if tk == "number" then
                        if hexify then
                            handle(format("%s [0x%04X]={},",depth,k))
                        else
                            handle(format("%s [%s]={},",depth,k))
                        end
                    elseif tk == "boolean" then
                        handle(format("%s [%s]={},",depth,tostring(k)))
                    elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
                        handle(format("%s %s={},",depth,k))
                    else
                        handle(format("%s [%q]={},",depth,k))
                    end
                elseif inline then
                    local st = simple_table(v)
                    if st then
                        if tk == "number" then
                            if hexify then
                                handle(format("%s [0x%04X]={ %s },",depth,k,concat(st,", ")))
                            else
                                handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
                            end
                        elseif tk == "boolean" then
                            handle(format("%s [%s]={ %s },",depth,tostring(k),concat(st,", ")))
                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
                            handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
                        else
                            handle(format("%s [%q]={ %s },",depth,k,concat(st,", ")))
                        end
                    else
                        do_serialize(v,k,depth,level+1)
                    end
                else
                    do_serialize(v,k,depth,level+1)
                end
            elseif t == "boolean" then
                if tk == "number" then
                    if hexify then
                        handle(format("%s [0x%04X]=%s,",depth,k,tostring(v)))
                    else
                        handle(format("%s [%s]=%s,",depth,k,tostring(v)))
                    end
                elseif tk == "boolean" then
                    handle(format("%s [%s]=%s,",depth,tostring(k),tostring(v)))
                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
                    handle(format("%s %s=%s,",depth,k,tostring(v)))
                else
                    handle(format("%s [%q]=%s,",depth,k,tostring(v)))
                end
            elseif t == "function" then
                if functions then
                    local f = getinfo(v).what == "C" and dump(dummy) or dump(v)
                 -- local f = getinfo(v).what == "C" and dump(function(...) return v(...) end) or dump(v)
                    if tk == "number" then
                        if hexify then
                            handle(format("%s [0x%04X]=load(%q),",depth,k,f))
                        else
                            handle(format("%s [%s]=load(%q),",depth,k,f))
                        end
                    elseif tk == "boolean" then
                        handle(format("%s [%s]=load(%q),",depth,tostring(k),f))
                    elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
                        handle(format("%s %s=load(%q),",depth,k,f))
                    else
                        handle(format("%s [%q]=load(%q),",depth,k,f))
                    end
                end
            else
                if tk == "number" then
                    if hexify then
                        handle(format("%s [0x%04X]=%q,",depth,k,tostring(v)))
                    else
                        handle(format("%s [%s]=%q,",depth,k,tostring(v)))
                    end
                elseif tk == "boolean" then
                    handle(format("%s [%s]=%q,",depth,tostring(k),tostring(v)))
                elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
                    handle(format("%s %s=%q,",depth,k,tostring(v)))
                else
                    handle(format("%s [%q]=%q,",depth,k,tostring(v)))
                end
            end
            --~ end
        end
    end
   if level > 0 then
        handle(format("%s},",depth))
    end
end

-- replacing handle by a direct t[#t+1] = ... (plus test) is not much
-- faster (0.03 on 1.00 for zapfino.tma)

local function serialize(_handle,root,name,specification) -- handle wins
    local tname = type(name)
    if type(specification) == "table" then
        noquotes  = specification.noquotes
        hexify    = specification.hexify
        handle    = _handle or specification.handle or print
        reduce    = specification.reduce or false
        functions = specification.functions
        compact   = specification.compact
        inline    = specification.inline and compact
        if functions == nil then
            functions = true
        end
        if compact == nil then
            compact = true
        end
        if inline == nil then
            inline = compact
        end
    else
        noquotes  = false
        hexify    = false
        handle    = _handle or print
        reduce    = false
        compact   = true
        inline    = true
        functions = true
    end
    if tname == "string" then
        if name == "return" then
            handle("return {")
        else
            handle(name .. "={")
        end
    elseif tname == "number" then
        if hexify then
            handle(format("[0x%04X]={",name))
        else
            handle("[" .. name .. "]={")
        end
    elseif tname == "boolean" then
        if name then
            handle("return {")
        else
            handle("{")
        end
    else
        handle("t={")
    end
    if root then
        -- The dummy access will initialize a table that has a delayed initialization
        -- using a metatable. (maybe explicitly test for metatable)
        if getmetatable(root) then -- todo: make this an option, maybe even per subtable
            local dummy = root._w_h_a_t_e_v_e_r_
            root._w_h_a_t_e_v_e_r_ = nil
        end
        -- Let's forget about empty tables.
        if next(root) then
            do_serialize(root,name,"",0)
        end
    end
    handle("}")
end

--    -- This is some 20% faster than using format (because formatters are much faster) but
--    -- of course, inlining the format using .. is then again faster .. anyway, as we do
--    -- some pretty printing as well there is not that much to gain unless we make a 'fast'
--    -- ugly variant as well. But, we would have to move the formatter to l-string then.

--    local formatters = string.formatters

--    local function do_serialize(root,name,level,indexed)
--        if level > 0 then
--            if indexed then
--                handle(formatters["%w{"](level))
--            else
--                local tn = type(name)
--                if tn == "number" then
--                    if hexify then
--                        handle(formatters["%w[%04H]={"](level,name))
--                    else
--                        handle(formatters["%w[%s]={"](level,name))
--                    end
--                elseif tn == "string" then
--                    if noquotes and not reserved[name] and lpegmatch(propername,name) then
--                        handle(formatters["%w%s={"](level,name))
--                    else
--                        handle(formatters["%w[%q]={"](level,name))
--                    end
--                elseif tn == "boolean" then
--                    handle(formatters["%w[%S]={"](level,name))
--                else
--                    handle(formatters["%w{"](level))
--                end
--            end
--        end
--        -- we could check for k (index) being number (cardinal)
--        if root and next(root) then
--         -- local first, last = nil, 0 -- #root cannot be trusted here (will be ok in 5.2 when ipairs is gone)
--         -- if compact then
--         --     -- NOT: for k=1,#root do (we need to quit at nil)
--         --     for k,v in ipairs(root) do -- can we use next?
--         --         if not first then first = k end
--         --         last = last + 1
--         --     end
--         -- end
--            local first, last = nil, 0
--            if compact then
--                last = #root
--                for k=1,last do
--                    if root[k] == nil then
--                        last = k - 1
--                        break
--                    end
--                end
--                if last > 0 then
--                    first = 1
--                end
--            end
--            local sk = sortedkeys(root)
--            for i=1,#sk do
--                local k = sk[i]
--                local v = root[k]
--                --~ if v == root then
--                    -- circular
--                --~ else
--                local t, tk = type(v), type(k)
--                if compact and first and tk == "number" and k >= first and k <= last then
--                    if t == "number" then
--                        if hexify then
--                            handle(formatters["%w %04H,"](level,v))
--                        else
--                            handle(formatters["%w %s,"](level,v)) -- %.99g
--                        end
--                    elseif t == "string" then
--                        if reduce and tonumber(v) then
--                            handle(formatters["%w %s,"](level,v))
--                        else
--                            handle(formatters["%w %q,"](level,v))
--                        end
--                    elseif t == "table" then
--                        if not next(v) then
--                            handle(formatters["%w {},"](level))
--                        elseif inline then -- and #t > 0
--                            local st = simple_table(v)
--                            if st then
--                                handle(formatters["%w { %, t },"](level,st))
--                            else
--                                do_serialize(v,k,level+1,true)
--                            end
--                        else
--                            do_serialize(v,k,level+1,true)
--                        end
--                    elseif t == "boolean" then
--                        handle(formatters["%w %S,"](level,v))
--                    elseif t == "function" then
--                        if functions then
--                            handle(formatters['%w load(%q),'](level,dump(v)))
--                        else
--                            handle(formatters['%w "function",'](level))
--                        end
--                    else
--                        handle(formatters["%w %Q,"](level,v))
--                    end
--                elseif k == "__p__" then -- parent
--                    if false then
--                        handle(formatters["%w __p__=nil,"](level))
--                    end
--                elseif t == "number" then
--                    if tk == "number" then
--                        if hexify then
--                            handle(formatters["%w [%04H]=%04H,"](level,k,v))
--                        else
--                            handle(formatters["%w [%s]=%s,"](level,k,v)) -- %.99g
--                        end
--                    elseif tk == "boolean" then
--                        if hexify then
--                            handle(formatters["%w [%S]=%04H,"](level,k,v))
--                        else
--                            handle(formatters["%w [%S]=%s,"](level,k,v)) -- %.99g
--                        end
--                    elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
--                        if hexify then
--                            handle(formatters["%w %s=%04H,"](level,k,v))
--                        else
--                            handle(formatters["%w %s=%s,"](level,k,v)) -- %.99g
--                        end
--                    else
--                        if hexify then
--                            handle(formatters["%w [%q]=%04H,"](level,k,v))
--                        else
--                            handle(formatters["%w [%q]=%s,"](level,k,v)) -- %.99g
--                        end
--                    end
--                elseif t == "string" then
--                    if reduce and tonumber(v) then
--                        if tk == "number" then
--                            if hexify then
--                                handle(formatters["%w [%04H]=%s,"](level,k,v))
--                            else
--                                handle(formatters["%w [%s]=%s,"](level,k,v))
--                            end
--                        elseif tk == "boolean" then
--                            handle(formatters["%w [%S]=%s,"](level,k,v))
--                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
--                            handle(formatters["%w %s=%s,"](level,k,v))
--                        else
--                            handle(formatters["%w [%q]=%s,"](level,k,v))
--                        end
--                    else
--                        if tk == "number" then
--                            if hexify then
--                                handle(formatters["%w [%04H]=%q,"](level,k,v))
--                            else
--                                handle(formatters["%w [%s]=%q,"](level,k,v))
--                            end
--                        elseif tk == "boolean" then
--                            handle(formatters["%w [%S]=%q,"](level,k,v))
--                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
--                            handle(formatters["%w %s=%q,"](level,k,v))
--                        else
--                            handle(formatters["%w [%q]=%q,"](level,k,v))
--                        end
--                    end
--                elseif t == "table" then
--                    if not next(v) then
--                        if tk == "number" then
--                            if hexify then
--                                handle(formatters["%w [%04H]={},"](level,k))
--                            else
--                                handle(formatters["%w [%s]={},"](level,k))
--                            end
--                        elseif tk == "boolean" then
--                            handle(formatters["%w [%S]={},"](level,k))
--                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
--                            handle(formatters["%w %s={},"](level,k))
--                        else
--                            handle(formatters["%w [%q]={},"](level,k))
--                        end
--                    elseif inline then
--                        local st = simple_table(v)
--                        if st then
--                            if tk == "number" then
--                                if hexify then
--                                    handle(formatters["%w [%04H]={ %, t },"](level,k,st))
--                                else
--                                    handle(formatters["%w [%s]={ %, t },"](level,k,st))
--                                end
--                            elseif tk == "boolean" then
--                                handle(formatters["%w [%S]={ %, t },"](level,k,st))
--                            elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
--                                handle(formatters["%w %s={ %, t },"](level,k,st))
--                            else
--                                handle(formatters["%w [%q]={ %, t },"](level,k,st))
--                            end
--                        else
--                            do_serialize(v,k,level+1)
--                        end
--                    else
--                        do_serialize(v,k,level+1)
--                    end
--                elseif t == "boolean" then
--                    if tk == "number" then
--                        if hexify then
--                            handle(formatters["%w [%04H]=%S,"](level,k,v))
--                        else
--                            handle(formatters["%w [%s]=%S,"](level,k,v))
--                        end
--                    elseif tk == "boolean" then
--                        handle(formatters["%w [%S]=%S,"](level,k,v))
--                    elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
--                        handle(formatters["%w %s=%S,"](level,k,v))
--                    else
--                        handle(formatters["%w [%q]=%S,"](level,k,v))
--                    end
--                elseif t == "function" then
--                    if functions then
--                        local f = getinfo(v).what == "C" and dump(dummy) or dump(v)
--                     -- local f = getinfo(v).what == "C" and dump(function(...) return v(...) end) or dump(v)
--                        if tk == "number" then
--                            if hexify then
--                                handle(formatters["%w [%04H]=load(%q),"](level,k,f))
--                            else
--                                handle(formatters["%w [%s]=load(%q),"](level,k,f))
--                            end
--                        elseif tk == "boolean" then
--                            handle(formatters["%w [%S]=load(%q),"](level,k,f))
--                        elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
--                            handle(formatters["%w %s=load(%q),"](level,k,f))
--                        else
--                            handle(formatters["%w [%q]=load(%q),"](level,k,f))
--                        end
--                    end
--                else
--                    if tk == "number" then
--                        if hexify then
--                            handle(formatters["%w [%04H]=%Q,"](level,k,v))
--                        else
--                            handle(formatters["%w [%s]=%Q,"](level,k,v))
--                        end
--                    elseif tk == "boolean" then
--                        handle(formatters["%w [%S]=%Q,"](level,k,v))
--                    elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
--                        handle(formatters["%w %s=%Q,"](level,k,v))
--                    else
--                        handle(formatters["%w [%q]=%Q,"](level,k,v))
--                    end
--                end
--                --~ end
--            end
--        end
--        if level > 0 then
--            handle(formatters["%w}"](level))
--        end
--    end

--    local function serialize(_handle,root,name,specification) -- handle wins
--        local tname = type(name)
--        if type(specification) == "table" then
--            noquotes  = specification.noquotes
--            hexify    = specification.hexify
--            handle    = _handle or specification.handle or print
--            reduce    = specification.reduce or false
--            functions = specification.functions
--            compact   = specification.compact
--            inline    = specification.inline and compact
--            if functions == nil then
--                functions = true
--            end
--            if compact == nil then
--                compact = true
--            end
--            if inline == nil then
--                inline = compact
--            end
--        else
--            noquotes  = false
--            hexify    = false
--            handle    = _handle or print
--            reduce    = false
--            compact   = true
--            inline    = true
--            functions = true
--        end
--        if tname == "string" then
--            if name == "return" then
--                handle("return {")
--            else
--                handle(name .. "={")
--            end
--        elseif tname == "number" then
--            if hexify then
--                handle(format("[0x%04X]={",name))
--            else
--                handle("[" .. name .. "]={")
--            end
--        elseif tname == "boolean" then
--            if name then
--                handle("return {")
--            else
--                handle("{")
--            end
--        else
--            handle("t={")
--        end
--        if root then
--            -- The dummy access will initialize a table that has a delayed initialization
--            -- using a metatable. (maybe explicitly test for metatable)
--            if getmetatable(root) then -- todo: make this an option, maybe even per subtable
--                local dummy = root._w_h_a_t_e_v_e_r_
--                root._w_h_a_t_e_v_e_r_ = nil
--            end
--            -- Let's forget about empty tables.
--            if next(root) then
--                do_serialize(root,name,0)
--            end
--        end
--        handle("}")
--    end

-- name:
--
-- true     : return     { }
-- false    :            { }
-- nil      : t        = { }
-- string   : string   = { }
-- "return" : return     { }
-- number   : [number] = { }

function table.serialize(root,name,specification)
    local t, n = { }, 0
    local function flush(s)
        n = n + 1
        t[n] = s
    end
    serialize(flush,root,name,specification)
    return concat(t,"\n")
end

--   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" } } }
--   local t = os.clock()
--   for i=1,10000 do
--       table.serialize(a)
--   end
--   print(os.clock()-t,table.serialize(a))

table.tohandle = serialize

-- sometimes tables are real use (zapfino extra pro is some 85M) in which
-- case a stepwise serialization is nice; actually, we could consider:
--
-- for line in table.serializer(root,name,reduce,noquotes) do
--    ...(line)
-- end
--
-- so this is on the todo list

local maxtab = 2*1024

function table.tofile(filename,root,name,specification)
    local f = io.open(filename,'w')
    if f then
        if maxtab > 1 then
            local t, n = { }, 0
            local function flush(s)
                n = n + 1
                t[n] = s
                if n > maxtab then
                    f:write(concat(t,"\n"),"\n") -- hm, write(sometable) should be nice
                    t, n = { }, 0 -- we could recycle t if needed
                end
            end
            serialize(flush,root,name,specification)
            f:write(concat(t,"\n"),"\n")
        else
            local function flush(s)
                f:write(s,"\n")
            end
            serialize(flush,root,name,specification)
        end
        f:close()
        io.flush()
    end
end

local function flattened(t,f,depth) -- also handles { nil, 1, nil, 2 }
    if f == nil then
        f = { }
        depth = 0xFFFF
    elseif tonumber(f) then
        -- assume that only two arguments are given
        depth = f
        f = { }
    elseif not depth then
        depth = 0xFFFF
    end
    for k, v in next, t do
        if type(k) ~= "number" then
            if depth > 0 and type(v) == "table" then
                flattened(v,f,depth-1)
            else
                f[#f+1] = v
            end
        end
    end
    for k=1,#t do
        local v = t[k]
        if depth > 0 and type(v) == "table" then
            flattened(v,f,depth-1)
        else
            f[#f+1] = v
        end
    end
    return f
end

table.flattened = flattened

local function unnest(t,f) -- only used in mk, for old times sake
    if not f then          -- and only relevant for token lists
        f = { }            -- this one can become obsolete
    end
    for i=1,#t do
        local v = t[i]
        if type(v) == "table" then
            if type(v[1]) == "table" then
                unnest(v,f)
            else
                f[#f+1] = v
            end
        else
            f[#f+1] = v
        end
    end
    return f
end

function table.unnest(t) -- bad name
    return unnest(t)
end

local function are_equal(a,b,n,m) -- indexed
    if a and b and #a == #b then
        n = n or 1
        m = m or #a
        for i=n,m do
            local ai, bi = a[i], b[i]
            if ai==bi then
                -- same
            elseif type(ai) == "table" and type(bi) == "table" then
                if not are_equal(ai,bi) then
                    return false
                end
            else
                return false
            end
        end
        return true
    else
        return false
    end
end

local function identical(a,b) -- assumes same structure
    for ka, va in next, a do
        local vb = b[ka]
        if va == vb then
            -- same
        elseif type(va) == "table" and  type(vb) == "table" then
            if not identical(va,vb) then
                return false
            end
        else
            return false
        end
    end
    return true
end

table.identical = identical
table.are_equal = are_equal

-- maybe also make a combined one

function table.compact(t) -- remove empty tables, assumes subtables
    if t then
        for k, v in next, t do
            if not next(v) then -- no type checking
                t[k] = nil
            end
        end
    end
end

function table.contains(t, v)
    if t then
        for i=1, #t do
            if t[i] == v then
                return i
            end
        end
    end
    return false
end

function table.count(t)
    local n = 0
    for k, v in next, t do
        n = n + 1
    end
    return n
end

function table.swapped(t,s) -- hash
    local n = { }
    if s then
        for k, v in next, s do
            n[k] = v
        end
    end
    for k, v in next, t do
        n[v] = k
    end
    return n
end

function table.mirrored(t) -- hash
    local n = { }
    for k, v in next, t do
        n[v] = k
        n[k] = v
    end
    return n
end

function table.reversed(t)
    if t then
        local tt, tn = { }, #t
        if tn > 0 then
            local ttn = 0
            for i=tn,1,-1 do
                ttn = ttn + 1
                tt[ttn] = t[i]
            end
        end
        return tt
    end
end

function table.reverse(t)
    if t then
        local n = #t
        for i=1,floor(n/2) do
            local j = n - i + 1
            t[i], t[j] = t[j], t[i]
        end
        return t
    end
end

function table.sequenced(t,sep,simple) -- hash only
    if not t then
        return ""
    end
    local n = #t
    local s = { }
    if n > 0 then
        -- indexed
        for i=1,n do
            s[i] = tostring(t[i])
        end
    else
        -- hashed
        n = 0
        for k, v in sortedhash(t) do
            if simple then
                if v == true then
                    n = n + 1
                    s[n] = k
                elseif v and v~= "" then
                    n = n + 1
                    s[n] = k .. "=" .. tostring(v)
                end
            else
                n = n + 1
                s[n] = k .. "=" .. tostring(v)
            end
        end
    end
    return concat(s,sep or " | ")
end

function table.print(t,...)
    if type(t) ~= "table" then
        print(tostring(t))
    else
        serialize(print,t,...)
    end
end

setinspector(function(v) if type(v) == "table" then serialize(print,v,"table") return true end end)

-- -- -- obsolete but we keep them for a while and might comment them later -- -- --

-- roughly: copy-loop : unpack : sub == 0.9 : 0.4 : 0.45 (so in critical apps, use unpack)

function table.sub(t,i,j)
    return { unpack(t,i,j) }
end

-- slower than #t on indexed tables (#t only returns the size of the numerically indexed slice)

function table.is_empty(t)
    return not t or not next(t)
end

function table.has_one_entry(t)
    return t and not next(t,next(t))
end

-- new

function table.loweredkeys(t) -- maybe utf
    local l = { }
    for k, v in next, t do
        l[lower(k)] = v
    end
    return l
end

-- new, might move (maybe duplicate)

function table.unique(old)
    local hash = { }
    local new = { }
    local n = 0
    for i=1,#old do
        local oi = old[i]
        if not hash[oi] then
            n = n + 1
            new[n] = oi
            hash[oi] = true
        end
    end
    return new
end

function table.sorted(t,...)
    sort(t,...)
    return t -- still sorts in-place
end
