summaryrefslogtreecommitdiff
path: root/tex/context/base/l-table.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/l-table.lua')
-rw-r--r--tex/context/base/l-table.lua2724
1 files changed, 1362 insertions, 1362 deletions
diff --git a/tex/context/base/l-table.lua b/tex/context/base/l-table.lua
index 54c2b86e3..9a1b97fff 100644
--- a/tex/context/base/l-table.lua
+++ b/tex/context/base/l-table.lua
@@ -1,1362 +1,1362 @@
-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
+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