Current File : //usr/share/texlive/texmf-dist/tex/luatex/luaotfload/fontloader-l-table.lua |
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, select, rawget = type, next, tostring, tonumber, select, rawget
local table, string = table, string
local concat, sort = table.concat, table.sort
local format, lower, dump = string.format, string.lower, string.dump
local getmetatable, setmetatable = getmetatable, setmetatable
local lpegmatch, patterns = lpeg.match, lpeg.patterns
local floor = math.floor
-- extra functions, some might go (when not used)
--
-- we could serialize using %a but that won't work well is in the code we mostly use
-- floats and as such we get unequality e.g. in version comparisons
local stripper = patterns.stripper
function table.getn(t)
return t and #t -- for very old times sake
end
function table.strip(tab)
local lst = { }
local 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 = { }
local 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 = type(a) -- needed, else 11 < 2
-- local tb = type(b) -- needed, else 11 < 2
-- if ta == tb and ta == "number" then
-- return a < b
-- else
-- return tostring(a) < tostring(b) -- not that efficient
-- end
-- end
-- local function compare(a,b)
-- local ta = type(a) -- needed, else 11 < 2
-- local tb = type(b) -- needed, else 11 < 2
-- if ta == tb and (ta == "number" or ta == "string") then
-- return a < b
-- else
-- return tostring(a) < tostring(b) -- not that efficient
-- 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 compare(a,b)
-- local ta = type(a) -- needed, else 11 < 2
-- local tb = type(b) -- needed, else 11 < 2
-- if ta == tb and (ta == "number" or ta == "string") then
-- return a < b
-- else
-- return tostring(a) < tostring(b) -- not that efficient
-- end
-- end
-- local function compare(a,b)
-- local ta = type(a) -- needed, else 11 < 2
-- if ta == "number" or ta == "string" then
-- local tb = type(b) -- needed, else 11 < 2
-- if ta == tb then
-- return a < b
-- end
-- end
-- return tostring(a) < tostring(b) -- not that efficient
-- end
local function compare(a,b)
local ta = type(a) -- needed, else 11 < 2
if ta == "number" then
local tb = type(b) -- needed, else 11 < 2
if ta == tb then
return a < b
elseif tb == "string" then
return tostring(a) < b
end
elseif ta == "string" then
local tb = type(b) -- needed, else 11 < 2
if ta == tb then
return a < b
else
return a < tostring(b)
end
end
return tostring(a) < tostring(b) -- not that efficient
end
local function sortedkeys(tab)
if tab then
local srt = { }
local category = 0 -- 0=unknown 1=string, 2=number 3=mixed
local s = 0
for key in next, tab do
s = s + 1
srt[s] = key
if category ~= 3 then
local tkey = type(key)
if category == 1 then
if tkey ~= "string" then
category = 3
end
elseif category == 2 then
if tkey ~= "number" then
category = 3
end
else
if tkey == "string" then
category = 1
elseif tkey == "number" then
category = 2
else
category = 3
end
end
end
end
if s < 2 then
-- nothing to sort
elseif category == 3 then
sort(srt,compare)
else
sort(srt)
end
return srt
else
return { }
end
end
local function sortedhashonly(tab)
if tab then
local srt = { }
local s = 0
for key in next, tab do
if type(key) == "string" then
s = s + 1
srt[s] = key
end
end
if s > 1 then
sort(srt)
end
return srt
else
return { }
end
end
local function sortedindexonly(tab)
if tab then
local srt = { }
local s = 0
for key in next, tab do
if type(key) == "number" then
s = s + 1
srt[s] = key
end
end
if s > 1 then
sort(srt)
end
return srt
else
return { }
end
end
local function sortedhashkeys(tab,cmp) -- fast one
if tab then
local srt = { }
local s = 0
for key in next, tab do
if key then
s= s + 1
srt[s] = key
end
end
if s > 1 then
sort(srt,cmp)
end
return srt
else
return { }
end
end
function table.allkeys(t)
local keys = { }
for k, v in next, t do
for k in next, v do
keys[k] = true
end
end
return sortedkeys(keys)
end
table.sortedkeys = sortedkeys
table.sortedhashonly = sortedhashonly
table.sortedindexonly = sortedindexonly
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 m = #s
if m == 1 then
return next, t
elseif m > 0 then
local n = 0
return function()
if n < m then
n = n + 1
local k = s[n]
return k, t[k]
end
end
end
end
return nothing
end
-- local function iterate(t,i)
-- local i = i + 1
-- if i <= t.n then
-- local k = t[i]
-- return i, k, t.t[k]
-- end
-- end
--
-- local function indexedhash(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 m = #s
-- if m == 1 then
-- return next, t
-- elseif m > 0 then
-- s.n = m
-- s.t = t
-- return iterate, s, 0
-- end
-- end
-- return nothing
-- end
--
-- -- for i, k, v in indexedhash(t) do print(k,v,s) 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
if not t then
t = { }
end
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 = { }
local 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
if not tables then
tables = { }
end
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
h[v] = value
end
end
return h
end
function table.fromhash(t)
local hsh = { }
local h = 0
for k, v in next, t do
if v then
h = h + 1
hsh[h] = k
end
end
return hsh
end
local noquotes, hexify, handle, compact, inline, functions, metacheck, accurate
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',
'NaN', 'goto', 'const',
}
local function is_simple_table(t,hexify,accurate) -- also used in util-tab so maybe public
local nt = #t
if nt > 0 then
local n = 0
for _, v in next, t do
n = n + 1
if type(v) == "table" then
return nil
end
end
-- local haszero = t[0]
local haszero = rawget(t,0) -- don't trigger meta
if n == nt then
local tt = { }
for i=1,nt do
local v = t[i]
local tv = type(v)
if tv == "number" then
-- tt[i] = v -- not needed tostring(v)
if hexify then
tt[i] = format("0x%X",v)
elseif accurate then
tt[i] = format("%q",v)
else
tt[i] = v -- not needed tostring(v)
end
elseif tv == "string" then
tt[i] = format("%q",v) -- f_string(v)
elseif tv == "boolean" then
tt[i] = v and "true" or "false"
else
return nil
end
end
return tt
elseif haszero and (n == nt + 1) then
local tt = { }
for i=0,nt do
local v = t[i]
local tv = type(v)
if tv == "number" then
-- tt[i+1] = v -- not needed tostring(v)
if hexify then
tt[i+1] = format("0x%X",v)
elseif accurate then
tt[i+1] = format("%q",v)
else
tt[i+1] = v -- not needed tostring(v)
end
elseif tv == "string" then
tt[i+1] = format("%q",v) -- f_string(v)
elseif tv == "boolean" then
tt[i+1] = v and "true" or "false"
else
return nil
end
end
tt[1] = "[0] = " .. tt[1]
return tt
end
end
return nil
end
table.is_simple_table = is_simple_table
-- 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 but we haven't defined them
-- yet
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%X]={",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,name and "true" or "false"))
else
handle(format("%s{",depth))
end
end
end
-- we could check for k (index) being number (cardinal)
if root and next(root) ~= nil then
local first = nil
local last = 0
if compact then
last = #root
for k=1,last do
-- if root[k] == nil then
if rawget(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]
local tv = type(v)
local tk = type(k)
if compact and first and tk == "number" and k >= first and k <= last then
if tv == "number" then
if hexify then
handle(format("%s 0x%X,",depth,v))
elseif accurate then
handle(format("%s %q,",depth,v))
else
handle(format("%s %s,",depth,v)) -- %.99g
end
elseif tv == "string" then
handle(format("%s %q,",depth,v))
elseif tv == "table" then
if next(v) == nil then
handle(format("%s {},",depth))
elseif inline then -- and #t > 0
local st = is_simple_table(v,hexify,accurate)
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 tv == "boolean" then
handle(format("%s %s,",depth,v and "true" or "false"))
elseif tv == "function" then
if functions then
handle(format('%s load(%q),',depth,dump(v))) -- maybe strip
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 tv == "number" then
if tk == "number" then
if hexify then
handle(format("%s [0x%X]=0x%X,",depth,k,v))
elseif accurate then
handle(format("%s [%s]=%q,",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%X,",depth,k and "true" or "false",v))
elseif accurate then
handle(format("%s [%s]=%q,",depth,k and "true" or "false",v))
else
handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) -- %.99g
end
elseif tk ~= "string" then
-- ignore
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
if hexify then
handle(format("%s %s=0x%X,",depth,k,v))
elseif accurate then
handle(format("%s %s=%q,",depth,k,v))
else
handle(format("%s %s=%s,",depth,k,v)) -- %.99g
end
else
if hexify then
handle(format("%s [%q]=0x%X,",depth,k,v))
elseif accurate then
handle(format("%s [%q]=%q,",depth,k,v))
else
handle(format("%s [%q]=%s,",depth,k,v)) -- %.99g
end
end
elseif tv == "string" then
if tk == "number" then
if hexify then
handle(format("%s [0x%X]=%q,",depth,k,v))
elseif accurate then
handle(format("%s [%q]=%q,",depth,k,v))
else
handle(format("%s [%s]=%q,",depth,k,v))
end
elseif tk == "boolean" then
handle(format("%s [%s]=%q,",depth,k and "true" or "false",v))
elseif tk ~= "string" then
-- ignore
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
elseif tv == "table" then
if next(v) == nil then
if tk == "number" then
if hexify then
handle(format("%s [0x%X]={},",depth,k))
elseif accurate then
handle(format("%s [%q]={},",depth,k))
else
handle(format("%s [%s]={},",depth,k))
end
elseif tk == "boolean" then
handle(format("%s [%s]={},",depth,k and "true" or "false"))
elseif tk ~= "string" then
-- ignore
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 = is_simple_table(v,hexify,accurate)
if st then
if tk == "number" then
if hexify then
handle(format("%s [0x%X]={ %s },",depth,k,concat(st,", ")))
elseif accurate then
handle(format("%s [%q]={ %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,k and "true" or "false",concat(st,", ")))
elseif tk ~= "string" then
-- ignore
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 tv == "boolean" then
if tk == "number" then
if hexify then
handle(format("%s [0x%X]=%s,",depth,k,v and "true" or "false"))
elseif accurate then
handle(format("%s [%q]=%s,",depth,k,v and "true" or "false"))
else
handle(format("%s [%s]=%s,",depth,k,v and "true" or "false"))
end
elseif tk == "boolean" then
handle(format("%s [%s]=%s,",depth,tostring(k),v and "true" or "false"))
elseif tk ~= "string" then
-- ignore
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s=%s,",depth,k,v and "true" or "false"))
else
handle(format("%s [%q]=%s,",depth,k,v and "true" or "false"))
end
elseif tv == "function" then
if functions then
local getinfo = debug and debug.getinfo
if getinfo then
local f = getinfo(v).what == "C" and dump(dummy) or dump(v) -- maybe strip
-- local f = getinfo(v).what == "C" and dump(function(...) return v(...) end) or dump(v) -- maybe strip
if tk == "number" then
if hexify then
handle(format("%s [0x%X]=load(%q),",depth,k,f))
elseif accurate then
handle(format("%s [%q]=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,k and "true" or "false",f))
elseif tk ~= "string" then
-- ignore
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
end
else
if tk == "number" then
if hexify then
handle(format("%s [0x%X]=%q,",depth,k,tostring(v)))
elseif accurate then
handle(format("%s [%q]=%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,k and "true" or "false",tostring(v)))
elseif tk ~= "string" then
-- ignore
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
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
accurate = specification.accurate
handle = _handle or specification.handle or print
functions = specification.functions
compact = specification.compact
inline = specification.inline and compact
metacheck = specification.metacheck
if functions == nil then
functions = true
end
if compact == nil then
compact = true
end
if inline == nil then
inline = compact
end
if metacheck == nil then
metacheck = true
end
else
noquotes = false
hexify = false
handle = _handle or print
compact = true
inline = true
functions = true
metacheck = 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%X]={",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). This can crash on
-- metatables that check the index against a number.
if metacheck and getmetatable(root) then
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) ~= nil then
do_serialize(root,name,"",0)
end
end
handle("}")
end
-- A version with formatters 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.
-- name:
--
-- true : return { }
-- false : { }
-- nil : t = { }
-- string : string = { }
-- "return" : return { }
-- number : [number] = { }
function table.serialize(root,name,specification)
local t = { }
local 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
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 = { }
local 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 = { } -- we could recycle t if needed
n = 0
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 collapsed(t,f,h)
if f == nil then
f = { }
h = { }
end
for k=1,#t do
local v = t[k]
if type(v) == "table" then
collapsed(v,f,h)
elseif not h[v] then
f[#f+1] = v
h[v] = true
end
end
return f
end
local function collapsedhash(t,h)
if h == nil then
h = { }
end
for k=1,#t do
local v = t[k]
if type(v) == "table" then
collapsedhash(v,h)
else
h[v] = true
end
end
return h
end
table.collapsed = collapsed -- 20% faster than unique(collapsed(t))
table.collapsedhash = collapsedhash
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 == b then
return true
elseif a and b and #a == #b then
if not n then
n = 1
end
if not m then
m = #a
end
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
if a ~= b then
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
end
return true
end
table.identical = identical
table.are_equal = are_equal
local function sparse(old,nest,keeptables)
local new = { }
for k, v in next, old do
if not (v == "" or v == false) then
if nest and type(v) == "table" then
v = sparse(v,nest)
if keeptables or next(v) ~= nil then
new[k] = v
end
else
new[k] = v
end
end
end
return new
end
table.sparse = sparse
function table.compact(t)
return sparse(t,true,true)
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, we need to make sure we don't mess up next
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.hashed(t) -- list, add hash to index (save because we are not yet mixed
for i=1,#t do
t[t[i]] = i
end
return t
end
function table.mirrored(t) -- hash, we need to make sure we don't mess up next
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 = { }
local 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) -- check with 5.3 ?
if t then
local n = #t
local m = n + 1
for i=1,floor(n/2) do -- maybe just n//2
local j = m - i
t[i], t[j] = t[j], t[i]
end
return t
end
end
-- This one is for really simple cases where need a hash from a table.
local function sequenced(t,sep,simple)
if not t then
return ""
elseif type(t) ~= "table" then
return t -- handy fallback
end
local n = #t
local s = { }
if n > 0 then
-- indexed
for i=1,n do
local v = t[i]
if type(v) == "table" then
s[i] = "{" .. sequenced(v,sep,simple) .. "}"
else
s[i] = tostring(t[i])
end
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
if type(v) == "table" then
s[n] = k .. "={" .. sequenced(v,sep,simple) .. "}"
else
s[n] = k .. "=" .. tostring(v)
end
end
else
n = n + 1
if type(v) == "table" then
s[n] = k .. "={" .. sequenced(v,sep,simple) .. "}"
else
s[n] = k .. "=" .. tostring(v)
end
end
end
end
if sep == true then
return "{ " .. concat(s,", ") .. " }"
else
return concat(s,sep or " | ")
end
end
table.sequenced = sequenced
function table.print(t,...)
if type(t) ~= "table" then
print(tostring(t))
else
serialize(print,t,...)
end
end
if setinspector then
setinspector("table",function(v) if type(v) == "table" then serialize(print,v,"table") return true end 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 next(t) == nil
end
function table.has_one_entry(t)
return t and next(t,next(t)) == nil
end
-- new (rather basic, not indexed and nested)
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
--
function table.values(t,s) -- optional sort flag
if t then
local values = { }
local keys = { }
local v = 0
for key, value in next, t do
if not keys[value] then
v = v + 1
values[v] = value
keys[k] = key
end
end
if s then
sort(values)
end
return values
else
return { }
end
end
-- maybe this will move to util-tab.lua
-- for k, v in table.filtered(t,pattern) do ... end
-- for k, v in table.filtered(t,pattern,true) do ... end
-- for k, v in table.filtered(t,pattern,true,cmp) do ... end
function table.filtered(t,pattern,sort,cmp)
if t and type(pattern) == "string" then
if sort 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 m = #s
local function kv(s)
while n < m do
n = n + 1
local k = s[n]
if find(k,pattern) then
return k, t[k]
end
end
end
return kv, s
else
local n = next(t)
local function iterator()
while n ~= nil do
local k = n
n = next(t,k)
if find(k,pattern) then
return k, t[k]
end
end
end
return iterator, t
end
else
return nothing
end
end
-- lua 5.3:
if not table.move then
function table.move(a1,f,e,t,a2)
if a2 and a1 ~= a2 then
for i=f,e do
a2[t] = a1[i]
t = t + 1
end
return a2
else
t = t + e - f
for i=e,f,-1 do
a1[t] = a1[i]
t = t - 1
end
return a1
end
end
end