summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lualibs-table.lua380
-rw-r--r--lualibs-util-str.lua14
-rw-r--r--lualibs-util-tab.lua416
-rw-r--r--lualibs-util-tpl.lua22
4 files changed, 376 insertions, 456 deletions
diff --git a/lualibs-table.lua b/lualibs-table.lua
index 493a820..4f6b9b4 100644
--- a/lualibs-table.lua
+++ b/lualibs-table.lua
@@ -366,12 +366,12 @@ local function simple_table(t)
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)
+ elseif tv == "boolean" then
+ nt = nt + 1
+ tt[nt] = v and "true" or "false"
else
tt = nil
break
@@ -397,7 +397,8 @@ end
-- todo: %g faster on numbers than %s
--- we can speed this up with repeaters and formatters (is indeed faster)
+-- 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%_]*$")
@@ -423,7 +424,7 @@ local function do_serialize(root,name,depth,level,indexed)
handle(format("%s[%q]={",depth,name))
end
elseif tn == "boolean" then
- handle(format("%s[%s]={",depth,tostring(name)))
+ handle(format("%s[%s]={",depth,name and "true" or "false"))
else
handle(format("%s{",depth))
end
@@ -459,21 +460,21 @@ local function do_serialize(root,name,depth,level,indexed)
--~ if v == root then
-- circular
--~ else
- local t, tk = type(v), type(k)
+ local tv, tk = type(v), type(k)
if compact and first and tk == "number" and k >= first and k <= last then
- if t == "number" then
+ if tv == "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
+ elseif tv == "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
+ elseif tv == "table" then
if not next(v) then
handle(format("%s {},",depth))
elseif inline then -- and #t > 0
@@ -486,9 +487,9 @@ local function do_serialize(root,name,depth,level,indexed)
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
+ 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
@@ -501,7 +502,7 @@ local function do_serialize(root,name,depth,level,indexed)
if false then
handle(format("%s __p__=nil,",depth))
end
- elseif t == "number" then
+ elseif tv == "number" then
if tk == "number" then
if hexify then
handle(format("%s [0x%04X]=0x%04X,",depth,k,v))
@@ -510,9 +511,9 @@ local function do_serialize(root,name,depth,level,indexed)
end
elseif tk == "boolean" then
if hexify then
- handle(format("%s [%s]=0x%04X,",depth,tostring(k),v))
+ handle(format("%s [%s]=0x%04X,",depth,k and "true" or "false",v))
else
- handle(format("%s [%s]=%s,",depth,tostring(k),v)) -- %.99g
+ handle(format("%s [%s]=%s,",depth,k and "true" or "false",v)) -- %.99g
end
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
if hexify then
@@ -527,7 +528,7 @@ local function do_serialize(root,name,depth,level,indexed)
handle(format("%s [%q]=%s,",depth,k,v)) -- %.99g
end
end
- elseif t == "string" then
+ elseif tv == "string" then
if reduce and tonumber(v) then
if tk == "number" then
if hexify then
@@ -536,7 +537,7 @@ local function do_serialize(root,name,depth,level,indexed)
handle(format("%s [%s]=%s,",depth,k,v))
end
elseif tk == "boolean" then
- handle(format("%s [%s]=%s,",depth,tostring(k),v))
+ handle(format("%s [%s]=%s,",depth,k and "true" or "false",v))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s=%s,",depth,k,v))
else
@@ -550,14 +551,14 @@ local function do_serialize(root,name,depth,level,indexed)
handle(format("%s [%s]=%q,",depth,k,v))
end
elseif tk == "boolean" then
- handle(format("%s [%s]=%q,",depth,tostring(k),v))
+ handle(format("%s [%s]=%q,",depth,k and "true" or "false",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
+ elseif tv == "table" then
if not next(v) then
if tk == "number" then
if hexify then
@@ -566,7 +567,7 @@ local function do_serialize(root,name,depth,level,indexed)
handle(format("%s [%s]={},",depth,k))
end
elseif tk == "boolean" then
- handle(format("%s [%s]={},",depth,tostring(k)))
+ handle(format("%s [%s]={},",depth,k and "true" or "false"))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s={},",depth,k))
else
@@ -582,7 +583,7 @@ local function do_serialize(root,name,depth,level,indexed)
handle(format("%s [%s]={ %s },",depth,k,concat(st,", ")))
end
elseif tk == "boolean" then
- handle(format("%s [%s]={ %s },",depth,tostring(k),concat(st,", ")))
+ handle(format("%s [%s]={ %s },",depth,k and "true" or "false",concat(st,", ")))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s={ %s },",depth,k,concat(st,", ")))
else
@@ -594,21 +595,21 @@ local function do_serialize(root,name,depth,level,indexed)
else
do_serialize(v,k,depth,level+1)
end
- elseif t == "boolean" then
+ elseif tv == "boolean" then
if tk == "number" then
if hexify then
- handle(format("%s [0x%04X]=%s,",depth,k,tostring(v)))
+ handle(format("%s [0x%04X]=%s,",depth,k,v and "true" or "false"))
else
- handle(format("%s [%s]=%s,",depth,k,tostring(v)))
+ 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),tostring(v)))
+ handle(format("%s [%s]=%s,",depth,tostring(k),v and "true" or "false"))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
- handle(format("%s %s=%s,",depth,k,tostring(v)))
+ handle(format("%s %s=%s,",depth,k,v and "true" or "false"))
else
- handle(format("%s [%q]=%s,",depth,k,tostring(v)))
+ handle(format("%s [%q]=%s,",depth,k,v and "true" or "false"))
end
- elseif t == "function" then
+ elseif tv == "function" then
if functions 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
@@ -619,7 +620,7 @@ local function do_serialize(root,name,depth,level,indexed)
handle(format("%s [%s]=load(%q),",depth,k,f))
end
elseif tk == "boolean" then
- handle(format("%s [%s]=load(%q),",depth,tostring(k),f))
+ handle(format("%s [%s]=load(%q),",depth,k and "true" or "false",f))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s=load(%q),",depth,k,f))
else
@@ -634,7 +635,7 @@ local function do_serialize(root,name,depth,level,indexed)
handle(format("%s [%s]=%q,",depth,k,tostring(v)))
end
elseif tk == "boolean" then
- handle(format("%s [%s]=%q,",depth,tostring(k),tostring(v)))
+ handle(format("%s [%s]=%q,",depth,k and "true" or "false",tostring(v)))
elseif noquotes and not reserved[k] and lpegmatch(propername,k) then
handle(format("%s %s=%q,",depth,k,tostring(v)))
else
@@ -716,321 +717,10 @@ local function serialize(_handle,root,name,specification) -- handle wins
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))) -- maybe strip
--- 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) -- 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(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
+-- 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:
--
diff --git a/lualibs-util-str.lua b/lualibs-util-str.lua
index 10456a7..0c8c0e2 100644
--- a/lualibs-util-str.lua
+++ b/lualibs-util-str.lua
@@ -735,11 +735,17 @@ strings.formatters.add = add
-- registered in the default instance (should we fall back on this one?)
-lpeg.patterns.xmlescape = Cs((P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;" + P('"')/"&quot;" + P(1))^0)
-lpeg.patterns.texescape = Cs((C(S("#$%\\{}"))/"\\%1" + P(1))^0)
+patterns.xmlescape = Cs((P("<")/"&lt;" + P(">")/"&gt;" + P("&")/"&amp;" + P('"')/"&quot;" + P(1))^0)
+patterns.texescape = Cs((C(S("#$%\\{}"))/"\\%1" + P(1))^0)
+patterns.luaescape = Cs(((1-S('"\n'))^1 + P('"')/'\\"' + P('\n')/'\\n"')^0) -- maybe also \0
+patterns.luaquoted = Cs(Cc('"') * ((1-S('"\n'))^1 + P('"')/'\\"' + P('\n')/'\\n"')^0 * Cc('"'))
-add(formatters,"xml",[[lpegmatch(xmlescape,%s)]],[[local xmlescape = lpeg.patterns.xmlescape]])
-add(formatters,"tex",[[lpegmatch(texescape,%s)]],[[local texescape = lpeg.patterns.texescape]])
+-- escaping by lpeg is faster for strings without quotes, slower on a string with quotes, but
+-- faster again when other q-escapables are found (the ones we don't need to escape)
+
+add(formatters,"xml", [[lpegmatch(xmlescape,%s)]],[[local xmlescape = lpeg.patterns.xmlescape]])
+add(formatters,"tex", [[lpegmatch(texescape,%s)]],[[local texescape = lpeg.patterns.texescape]])
+add(formatters,"lua", [[lpegmatch(luaescape,%s)]],[[local luaescape = lpeg.patterns.luaescape]])
-- -- yes or no:
--
diff --git a/lualibs-util-tab.lua b/lualibs-util-tab.lua
index a47c0cb..b7db4fa 100644
--- a/lualibs-util-tab.lua
+++ b/lualibs-util-tab.lua
@@ -15,7 +15,7 @@ local concat, insert, remove = table.concat, table.insert, table.remove
local setmetatable, getmetatable, tonumber, tostring = setmetatable, getmetatable, tonumber, tostring
local type, next, rawset, tonumber, tostring, load, select = type, next, rawset, tonumber, tostring, load, select
local lpegmatch, P, Cs, Cc = lpeg.match, lpeg.P, lpeg.Cs, lpeg.Cc
-local serialize, sortedkeys, sortedpairs = table.serialize, table.sortedkeys, table.sortedpairs
+local sortedkeys, sortedpairs = table.sortedkeys, table.sortedpairs
local formatters = string.formatters
local splitter = lpeg.tsplitat(".")
@@ -293,100 +293,66 @@ function tables.encapsulate(core,capsule,protect)
end
end
-local function fastserialize(t,r,outer) -- no mixes
- r[#r+1] = "{"
- local n = #t
- if n > 0 then
- for i=1,n do
- local v = t[i]
- local tv = type(v)
- if tv == "string" then
- r[#r+1] = formatters["%q,"](v)
- elseif tv == "number" then
- r[#r+1] = formatters["%s,"](v)
- elseif tv == "table" then
- fastserialize(v,r)
- elseif tv == "boolean" then
- r[#r+1] = formatters["%S,"](v)
+-- we no longer have %q in keys as for this kind of tables we can
+-- assume sane keys
+
+local f_hashed_string = formatters["[%s]=%q,"]
+local f_hashed_number = formatters["[%s]=%s,"]
+local f_hashed_boolean = formatters["[%s]=%l,"]
+local f_hashed_table = formatters["[%s]="]
+
+local f_indexed_string = formatters["%q,"]
+local f_indexed_number = formatters["%s,"]
+local f_indexed_boolean = formatters["%l,"]
+
+function table.fastserialize(t,prefix) -- so prefix should contain the = | not sorted
+
+ local r = { prefix or "return" }
+ local m = 1
+
+ local function fastserialize(t,outer) -- no mixes
+ local n = #t
+ m = m + 1
+ r[m] = "{"
+ if n > 0 then
+ for i=1,n do
+ local v = t[i]
+ local tv = type(v)
+ if tv == "string" then
+ m = m + 1 r[m] = f_indexed_string(v)
+ elseif tv == "number" then
+ m = m + 1 r[m] = f_indexed_number(v)
+ elseif tv == "table" then
+ fastserialize(v)
+ elseif tv == "boolean" then
+ m = m + 1 r[m] = f_indexed_boolean(v)
+ end
end
- end
- else
- for k, v in next, t do
- local tv = type(v)
- if tv == "string" then
- r[#r+1] = formatters["[%q]=%q,"](k,v)
- elseif tv == "number" then
- r[#r+1] = formatters["[%q]=%s,"](k,v)
- elseif tv == "table" then
- r[#r+1] = formatters["[%q]="](k)
- fastserialize(v,r)
- elseif tv == "boolean" then
- r[#r+1] = formatters["[%q]=%S,"](k,v)
+ else
+ for k, v in next, t do
+ local tv = type(v)
+ if tv == "string" then
+ m = m + 1 r[m] = f_hashed_string(k,v)
+ elseif tv == "number" then
+ m = m + 1 r[m] = f_hashed_number(k,v)
+ elseif tv == "table" then
+ m = m + 1 r[m] = f_hashed_table(k)
+ fastserialize(v)
+ elseif tv == "boolean" then
+ m = m + 1 r[m] = f_hashed_boolean(k,v)
+ end
end
end
+ m = m + 1
+ if outer then
+ r[m] = "}"
+ else
+ r[m] = "},"
+ end
+ return r
end
- if outer then
- r[#r+1] = "}"
- else
- r[#r+1] = "},"
- end
- return r
-end
-
--- local f_hashed_string = formatters["[%q]=%q,"]
--- local f_hashed_number = formatters["[%q]=%s,"]
--- local f_hashed_table = formatters["[%q]="]
--- local f_hashed_true = formatters["[%q]=true,"]
--- local f_hashed_false = formatters["[%q]=false,"]
---
--- local f_indexed_string = formatters["%q,"]
--- local f_indexed_number = formatters["%s,"]
--- ----- f_indexed_true = formatters["true,"]
--- ----- f_indexed_false = formatters["false,"]
---
--- local function fastserialize(t,r,outer) -- no mixes
--- r[#r+1] = "{"
--- local n = #t
--- if n > 0 then
--- for i=1,n do
--- local v = t[i]
--- local tv = type(v)
--- if tv == "string" then
--- r[#r+1] = f_indexed_string(v)
--- elseif tv == "number" then
--- r[#r+1] = f_indexed_number(v)
--- elseif tv == "table" then
--- fastserialize(v,r)
--- elseif tv == "boolean" then
--- -- r[#r+1] = v and f_indexed_true(k) or f_indexed_false(k)
--- r[#r+1] = v and "true," or "false,"
--- end
--- end
--- else
--- for k, v in next, t do
--- local tv = type(v)
--- if tv == "string" then
--- r[#r+1] = f_hashed_string(k,v)
--- elseif tv == "number" then
--- r[#r+1] = f_hashed_number(k,v)
--- elseif tv == "table" then
--- r[#r+1] = f_hashed_table(k)
--- fastserialize(v,r)
--- elseif tv == "boolean" then
--- r[#r+1] = v and f_hashed_true(k) or f_hashed_false(k)
--- end
--- end
--- end
--- if outer then
--- r[#r+1] = "}"
--- else
--- r[#r+1] = "},"
--- end
--- return r
--- end
-function table.fastserialize(t,prefix) -- so prefix should contain the =
- return concat(fastserialize(t,{ prefix or "return" },true))
+ return concat(fastserialize(t,true))
end
function table.deserialize(str)
@@ -422,10 +388,14 @@ function table.load(filename,loader)
end
function table.save(filename,t,n,...)
- io.savedata(filename,serialize(t,n == nil and true or n,...))
+ io.savedata(filename,table.serialize(t,n == nil and true or n,...)) -- no frozen table.serialize
end
-local function slowdrop(t)
+local f_key_value = formatters["%s=%q"]
+local f_add_table = formatters[" {%t},\n"]
+local f_return_table = formatters["return {\n%t}"]
+
+local function slowdrop(t) -- maybe less memory (intermediate concat)
local r = { }
local l = { }
for i=1,#t do
@@ -433,28 +403,30 @@ local function slowdrop(t)
local j = 0
for k, v in next, ti do
j = j + 1
- l[j] = formatters["%s=%q"](k,v)
+ l[j] = f_key_value(k,v)
end
- r[i] = formatters[" {%t},\n"](l)
+ r[i] = f_add_table(l)
end
- return formatters["return {\n%st}"](r)
+ return f_return_table(r)
end
local function fastdrop(t)
local r = { "return {\n" }
+ local m = 1
for i=1,#t do
local ti = t[i]
- r[#r+1] = " {"
+ m = m + 1 r[m] = " {"
for k, v in next, ti do
- r[#r+1] = formatters["%s=%q"](k,v)
+ m = m + 1 r[m] = f_key_value(k,v)
end
- r[#r+1] = "},\n"
+ m = m + 1 r[m] = "},\n"
end
- r[#r+1] = "}"
+ m = m + 1
+ r[m] = "}"
return concat(r)
end
-function table.drop(t,slow) -- only { { a=2 }, {a=3} }
+function table.drop(t,slow) -- only { { a=2 }, {a=3} } -- for special cases
if #t == 0 then
return "return { }"
elseif slow == true then
@@ -464,6 +436,9 @@ function table.drop(t,slow) -- only { { a=2 }, {a=3} }
end
end
+-- inspect(table.drop({ { a=2 }, {a=3} }))
+-- inspect(table.drop({ { a=2 }, {a=3} },true))
+
function table.autokey(t,k)
local v = { }
t[k] = v
@@ -491,3 +466,244 @@ function table.twowaymapper(t)
return t
end
+-- The next version is somewhat faster, although in practice one will seldom
+-- serialize a lot using this one. Often the above variants are more efficient.
+-- If we would really need this a lot, we could hash q keys.
+
+-- char-def.lua : 0.53 -> 0.38
+-- husayni.tma : 0.28 -> 0.19
+
+local f_start_key_idx = formatters["%w{"]
+local f_start_key_num = formatters["%w[%s]={"]
+local f_start_key_str = formatters["%w[%q]={"]
+local f_start_key_boo = formatters["%w[%l]={"]
+local f_start_key_nop = formatters["%w{"]
+
+local f_stop = formatters["%w},"]
+
+local f_key_num_value_num = formatters["%w[%s]=%s,"]
+local f_key_str_value_num = formatters["%w[%q]=%s,"]
+local f_key_boo_value_num = formatters["%w[%l]=%s,"]
+
+local f_key_num_value_str = formatters["%w[%s]=%q,"]
+local f_key_str_value_str = formatters["%w[%q]=%q,"]
+local f_key_boo_value_str = formatters["%w[%l]=%q,"]
+
+local f_key_num_value_boo = formatters["%w[%s]=%l,"]
+local f_key_str_value_boo = formatters["%w[%q]=%l,"]
+local f_key_boo_value_boo = formatters["%w[%l]=%l,"]
+
+local f_key_num_value_not = formatters["%w[%s]={},"]
+local f_key_str_value_not = formatters["%w[%q]={},"]
+local f_key_boo_value_not = formatters["%w[%l]={},"]
+
+local f_key_num_value_seq = formatters["%w[%s]={ %, t },"]
+local f_key_str_value_seq = formatters["%w[%q]={ %, t },"]
+local f_key_boo_value_seq = formatters["%w[%l]={ %, t },"]
+
+local f_val_num = formatters["%w%s,"]
+local f_val_str = formatters["%w%q,"]
+local f_val_boo = formatters["%w%l,"]
+local f_val_not = formatters["%w{},"]
+local f_val_seq = formatters["%w{ %, t },"]
+
+local f_table_return = formatters["return {"]
+local f_table_name = formatters["%s={"]
+local f_table_direct = formatters["{"]
+local f_table_entry = formatters["[%q]={"]
+local f_table_finish = formatters["}"]
+
+----- f_string = formatters["%q"]
+
+local spaces = utilities.strings.newrepeater(" ")
+
+local serialize = table.serialize -- the extensive one, the one we started with
+
+function table.serialize(root,name,specification)
+
+ if specification then
+ return serialize(root,name,specification) -- the original one
+ end
+
+ local t -- = { }
+ local n = 1
+
+ local function simple_table(t)
+ if #t > 0 then
+ local n = 0
+ for _, v in next, t do
+ n = n + 1
+ if type(v) == "table" then
+ return nil
+ end
+ end
+ if n == #t then
+ local tt = { }
+ local nt = 0
+ for i=1,#t do
+ local v = t[i]
+ local tv = type(v)
+ nt = nt + 1
+ if tv == "number" then
+ tt[nt] = v
+ elseif tv == "string" then
+ tt[nt] = format("%q",v) -- f_string(v)
+ elseif tv == "boolean" then
+ tt[nt] = v and "true" or "false"
+ else
+ return nil
+ end
+ end
+ return tt
+ end
+ end
+ return nil
+ end
+
+ local function do_serialize(root,name,depth,level,indexed)
+ if level > 0 then
+ n = n + 1
+ if indexed then
+ t[n] = f_start_key_idx(depth)
+ else
+ local tn = type(name)
+ if tn == "number" then
+ t[n] = f_start_key_num(depth,name)
+ elseif tn == "string" then
+ t[n] = f_start_key_str(depth,name)
+ elseif tn == "boolean" then
+ t[n] = f_start_key_boo(depth,name)
+ else
+ t[n] = f_start_key_nop(depth)
+ end
+ end
+ depth = depth + 1
+ end
+ -- we could check for k (index) being number (cardinal)
+ if root and next(root) then
+ local first = nil
+ local last = 0
+ 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
+ local sk = sortedkeys(root) -- inline fast version?
+ for i=1,#sk do
+ local k = sk[i]
+ local v = root[k]
+ local tv = type(v)
+ local tk = type(k)
+ if first and tk == "number" and k >= first and k <= last then
+ if tv == "number" then
+ n = n + 1 t[n] = f_val_num(depth,v)
+ elseif tv == "string" then
+ n = n + 1 t[n] = f_val_str(depth,v)
+ elseif tv == "table" then
+ if not next(v) then
+ n = n + 1 t[n] = f_val_not(depth)
+ else
+ local st = simple_table(v)
+ if st then
+ n = n + 1 t[n] = f_val_seq(depth,st)
+ else
+ do_serialize(v,k,depth,level+1,true)
+ end
+ end
+ elseif tv == "boolean" then
+ n = n + 1 t[n] = f_val_boo(depth,v)
+ end
+ elseif tv == "number" then
+ if tk == "number" then
+ n = n + 1 t[n] = f_key_num_value_num(depth,k,v)
+ elseif tk == "string" then
+ n = n + 1 t[n] = f_key_str_value_num(depth,k,v)
+ elseif tk == "boolean" then
+ n = n + 1 t[n] = f_key_boo_value_num(depth,k,v)
+ end
+ elseif tv == "string" then
+ if tk == "number" then
+ n = n + 1 t[n] = f_key_num_value_str(depth,k,v)
+ elseif tk == "string" then
+ n = n + 1 t[n] = f_key_str_value_str(depth,k,v)
+ elseif tk == "boolean" then
+ n = n + 1 t[n] = f_key_boo_value_str(depth,k,v)
+ end
+ elseif tv == "table" then
+ if not next(v) then
+ if tk == "number" then
+ n = n + 1 t[n] = f_key_num_value_not(depth,k,v)
+ elseif tk == "string" then
+ n = n + 1 t[n] = f_key_str_value_not(depth,k,v)
+ elseif tk == "boolean" then
+ n = n + 1 t[n] = f_key_boo_value_not(depth,k,v)
+ end
+ else
+ local st = simple_table(v)
+ if not st then
+ do_serialize(v,k,depth,level+1)
+ elseif tk == "number" then
+ n = n + 1 t[n] = f_key_num_value_seq(depth,k,st)
+ elseif tk == "string" then
+ n = n + 1 t[n] = f_key_str_value_seq(depth,k,st)
+ elseif tk == "boolean" then
+ n = n + 1 t[n] = f_key_boo_value_seq(depth,k,st)
+ end
+ end
+ elseif tv == "boolean" then
+ if tk == "number" then
+ n = n + 1 t[n] = f_key_num_value_boo(depth,k,v)
+ elseif tk == "string" then
+ n = n + 1 t[n] = f_key_str_value_boo(depth,k,v)
+ elseif tk == "boolean" then
+ n = n + 1 t[n] = f_key_boo_value_boo(depth,k,v)
+ end
+ end
+ end
+ end
+ if level > 0 then
+ n = n + 1 t[n] = f_stop(depth-1)
+ end
+ end
+
+ local tname = type(name)
+
+ if tname == "string" then
+ if name == "return" then
+ t = { f_table_return() }
+ else
+ t = { f_table_name(name) }
+ end
+ elseif tname == "number" then
+ t = { f_table_entry(name) }
+ elseif tname == "boolean" then
+ if name then
+ t = { f_table_return() }
+ else
+ t = { f_table_direct() }
+ end
+ else
+ t = { f_table_name("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,1,0)
+ end
+ end
+ n = n + 1
+ t[n] = f_table_finish()
+ return concat(t,"\n")
+end
diff --git a/lualibs-util-tpl.lua b/lualibs-util-tpl.lua
index 511076b..dcc4c12 100644
--- a/lualibs-util-tpl.lua
+++ b/lualibs-util-tpl.lua
@@ -18,7 +18,7 @@ local report_template = logs.reporter("template")
local tostring = tostring
local format, sub = string.format, string.sub
-local P, C, Cs, Carg, lpegmatch = lpeg.P, lpeg.C, lpeg.Cs, lpeg.Carg, lpeg.match
+local P, C, Cs, Carg, lpegmatch, lpegpatterns = lpeg.P, lpeg.C, lpeg.Cs, lpeg.Carg, lpeg.match, lpeg.patterns
-- todo: make installable template.new
@@ -52,7 +52,10 @@ local sqlescape = lpeg.replacer {
-- { "\t", "\\t" },
}
-local sqlquotedescape = lpeg.Cs(lpeg.Cc("'") * sqlescape * lpeg.Cc("'"))
+local sqlquoted = lpeg.Cs(lpeg.Cc("'") * sqlescape * lpeg.Cc("'"))
+
+lpegpatterns.sqlescape = sqlescape
+lpegpatterns.sqlquoted = sqlquoted
-- escapeset : \0\1\2\3\4\5\6\7\8\9\10\11\12\13\14\15\16\17\18\19\20\21\22\23\24\25\26\27\28\29\30\31\"\\\127
-- test string: [[1\0\31test23"\\]] .. string.char(19) .. "23"
@@ -78,9 +81,16 @@ local sqlquotedescape = lpeg.Cs(lpeg.Cc("'") * sqlescape * lpeg.Cc("'"))
-- P(1)
-- )^0)
+-- local xmlescape = lpegpatterns.xmlescape
+-- local texescape = lpegpatterns.texescape
+-- local luaescape = lpegpatterns.luaescape
+-- local sqlquoted = lpegpatterns.sqlquoted
+-- local luaquoted = lpegpatterns.luaquoted
+
local escapers = {
lua = function(s)
- return sub(format("%q",s),2,-2)
+ -- return sub(format("%q",s),2,-2)
+ return lpegmatch(luaescape,s)
end,
sql = function(s)
return lpegmatch(sqlescape,s)
@@ -89,16 +99,14 @@ local escapers = {
local quotedescapers = {
lua = function(s)
+ -- return lpegmatch(luaquoted,s)
return format("%q",s)
end,
sql = function(s)
- return lpegmatch(sqlquotedescape,s)
+ return lpegmatch(sqlquoted,s)
end,
}
-lpeg.patterns.sqlescape = sqlescape
-lpeg.patterns.sqlquotedescape = sqlquotedescape
-
local luaescaper = escapers.lua
local quotedluaescaper = quotedescapers.lua