From ee229c4cb8b72d7d69c7283a9817a601d37e22b7 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <philipp.gesang@alumni.uni-heidelberg.de>
Date: Fri, 19 Oct 2012 22:11:40 +0200
Subject: update util-tab

---
 lualibs-util-dim.lua |   2 +-
 lualibs-util-lua.lua | 234 ++++++++++++++++++++++++++++
 lualibs-util-mrg.lua |   2 +-
 lualibs-util-sto.lua | 172 +++++++++++++++++++++
 lualibs-util-tab.lua | 427 +++++++++++++++++++++++++++------------------------
 5 files changed, 635 insertions(+), 202 deletions(-)
 create mode 100644 lualibs-util-lua.lua
 create mode 100644 lualibs-util-sto.lua

diff --git a/lualibs-util-dim.lua b/lualibs-util-dim.lua
index d77bd49..4bae2d0 100644
--- a/lualibs-util-dim.lua
+++ b/lualibs-util-dim.lua
@@ -1,4 +1,4 @@
-if not modules then modules = { } end modules ['l-dimen'] = {
+if not modules then modules = { } end modules ['util-dim'] = {
     version   = 1.001,
     comment   = "support for dimensions",
     author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
diff --git a/lualibs-util-lua.lua b/lualibs-util-lua.lua
new file mode 100644
index 0000000..2baeaa8
--- /dev/null
+++ b/lualibs-util-lua.lua
@@ -0,0 +1,234 @@
+if not modules then modules = { } end modules ['util-lua'] = {
+    version   = 1.001,
+    comment   = "companion to luat-lib.mkiv",
+    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+    comment   = "the strip code is written by Peter Cawley",
+    copyright = "PRAGMA ADE / ConTeXt Development Team",
+    license   = "see context related readme files"
+}
+
+local rep, sub, byte, dump, format = string.rep, string.sub, string.byte, string.dump, string.format
+local loadstring, loadfile, type = loadstring, loadfile, type
+
+utilities          = utilities or {}
+utilities.lua      = utilities.lua or { }
+local luautilities = utilities.lua
+
+utilities.report   = logs and logs.reporter("system") or print -- can be overloaded later
+
+local tracestripping           = false
+local forcestupidcompile       = true  -- use internal bytecode compiler
+luautilities.stripcode         = true  -- support stripping when asked for
+luautilities.alwaysstripcode   = false -- saves 1 meg on 7 meg compressed format file (2012.08.12)
+luautilities.nofstrippedchunks = 0
+luautilities.nofstrippedbytes  = 0
+
+-- The next function was posted by Peter Cawley on the lua list and strips line
+-- number information etc. from the bytecode data blob. We only apply this trick
+-- when we store data tables. Stripping makes the compressed format file about
+-- 1MB smaller (and uncompressed we save at least 6MB).
+--
+-- You can consider this feature an experiment, so it might disappear. There is
+-- no noticeable gain in runtime although the memory footprint should be somewhat
+-- smaller (and the file system has a bit less to deal with).
+--
+-- Begin of borrowed code ... works for Lua 5.1 which LuaTeX currently uses ...
+
+local function strip_code_pc(dump,name)
+    local before = #dump
+    local version, format, endian, int, size, ins, num = byte(dump,5,11)
+    local subint
+    if endian == 1 then
+        subint = function(dump, i, l)
+            local val = 0
+            for n = l, 1, -1 do
+                val = val * 256 + byte(dump,i + n - 1)
+            end
+            return val, i + l
+        end
+    else
+        subint = function(dump, i, l)
+            local val = 0
+            for n = 1, l, 1 do
+                val = val * 256 + byte(dump,i + n - 1)
+            end
+            return val, i + l
+        end
+    end
+    local strip_function
+    strip_function = function(dump)
+        local count, offset = subint(dump, 1, size)
+        local stripped, dirty = rep("\0", size), offset + count
+        offset = offset + count + int * 2 + 4
+        offset = offset + int + subint(dump, offset, int) * ins
+        count, offset = subint(dump, offset, int)
+        for n = 1, count do
+            local t
+            t, offset = subint(dump, offset, 1)
+            if t == 1 then
+                offset = offset + 1
+            elseif t == 4 then
+                offset = offset + size + subint(dump, offset, size)
+            elseif t == 3 then
+                offset = offset + num
+            end
+        end
+        count, offset = subint(dump, offset, int)
+        stripped = stripped .. sub(dump,dirty, offset - 1)
+        for n = 1, count do
+            local proto, off = strip_function(sub(dump,offset, -1))
+            stripped, offset = stripped .. proto, offset + off - 1
+        end
+        offset = offset + subint(dump, offset, int) * int + int
+        count, offset = subint(dump, offset, int)
+        for n = 1, count do
+            offset = offset + subint(dump, offset, size) + size + int * 2
+        end
+        count, offset = subint(dump, offset, int)
+        for n = 1, count do
+            offset = offset + subint(dump, offset, size) + size
+        end
+        stripped = stripped .. rep("\0", int * 3)
+        return stripped, offset
+    end
+    dump = sub(dump,1,12) .. strip_function(sub(dump,13,-1))
+    local after = #dump
+    local delta = before-after
+    if tracestripping then
+        utilities.report("stripped bytecode: %s, before %s, after %s, delta %s",name or "unknown",before,after,delta)
+    end
+    luautilities.nofstrippedchunks = luautilities.nofstrippedchunks + 1
+    luautilities.nofstrippedbytes  = luautilities.nofstrippedbytes  + delta
+    return dump, delta
+end
+
+-- ... end of borrowed code.
+
+local function strippedbytecode(code,forcestrip,name)
+    if (forcestrip and luautilities.stripcode) or luautilities.alwaysstripcode then
+        return strip_code_pc(code,name)
+    else
+        return code, 0
+    end
+end
+
+luautilities.stripbytecode    = strip_code_pc
+luautilities.strippedbytecode = strippedbytecode
+
+local function fatalerror(name)
+    utilities.report(format("fatal error in %q",name or "unknown"))
+end
+
+-- quite subtle ... doing this wrong incidentally can give more bytes
+
+
+function luautilities.loadedluacode(fullname,forcestrip,name)
+    -- quite subtle ... doing this wrong incidentally can give more bytes
+    name = name or fullname
+    local code = loadfile(fullname)
+    if code then
+        code()
+    end
+    if forcestrip and luautilities.stripcode then
+        if type(forcestrip) == "function" then
+            forcestrip = forcestrip(fullname)
+        end
+        if forcestrip then
+            local code, n = strip_code_pc(dump(code,name))
+            return loadstring(code), n
+        elseif luautilities.alwaysstripcode then
+            return loadstring(strip_code_pc(dump(code),name))
+        else
+            return code, 0
+        end
+    elseif luautilities.alwaysstripcode then
+        return loadstring(strip_code_pc(dump(code),name))
+    else
+        return code, 0
+    end
+end
+
+function luautilities.strippedloadstring(code,forcestrip,name) -- not executed
+    local n = 0
+    if (forcestrip and luautilities.stripcode) or luautilities.alwaysstripcode then
+        code = loadstring(code)
+        if not code then
+            fatalerror(name)
+        end
+        code, n = strip_code_pc(dump(code),name)
+    end
+    return loadstring(code), n
+end
+
+local function stupidcompile(luafile,lucfile,strip)
+    local code = io.loaddata(luafile)
+    local n = 0
+    if code and code ~= "" then
+        code = loadstring(code)
+        if not code then
+            fatalerror()
+        end
+        code = dump(code)
+        if strip then
+            code, n = strippedbytecode(code,true,luafile) -- last one is reported
+        end
+        if code and code ~= "" then
+            io.savedata(lucfile,code)
+        end
+    end
+    return n
+end
+
+local luac_normal = "texluac -o %q %q"
+local luac_strip  = "texluac -s -o %q %q"
+
+function luautilities.compile(luafile,lucfile,cleanup,strip,fallback) -- defaults: cleanup=false strip=true
+    utilities.report("lua: compiling %s into %s",luafile,lucfile)
+    os.remove(lucfile)
+    local done = false
+    if strip ~= false then
+        strip = true
+    end
+    if forcestupidcompile then
+        fallback = true
+    elseif strip then
+        done = os.spawn(format(luac_strip, lucfile,luafile)) == 0
+    else
+        done = os.spawn(format(luac_normal,lucfile,luafile)) == 0
+    end
+    if not done and fallback then
+        local n = stupidcompile(luafile,lucfile,strip)
+        if n > 0 then
+            utilities.report("lua: %s dumped into %s (%i bytes stripped)",luafile,lucfile,n)
+        else
+            utilities.report("lua: %s dumped into %s (unstripped)",luafile,lucfile)
+        end
+        cleanup = false -- better see how bad it is
+    end
+    if done and cleanup == true and lfs.isfile(lucfile) and lfs.isfile(luafile) then
+        utilities.report("lua: removing %s",luafile)
+        os.remove(luafile)
+    end
+    return done
+end
+--~ local getmetatable, type = getmetatable, type
+
+--~ local types = { }
+
+--~ function luautilities.registerdatatype(d,name)
+--~     types[getmetatable(d)] = name
+--~ end
+
+--~ function luautilities.datatype(d)
+--~     local t = type(d)
+--~     if t == "userdata" then
+--~         local m = getmetatable(d)
+--~         return m and types[m] or "userdata"
+--~     else
+--~         return t
+--~     end
+--~ end
+
+--~ luautilities.registerdatatype(lpeg.P("!"),"lpeg")
+
+--~ print(luautilities.datatype(lpeg.P("oeps")))
diff --git a/lualibs-util-mrg.lua b/lualibs-util-mrg.lua
index d59745b..8d6c5dd 100644
--- a/lualibs-util-mrg.lua
+++ b/lualibs-util-mrg.lua
@@ -1,4 +1,4 @@
-if not modules then modules = { } end modules ['l-utils'] = {
+if not modules then modules = { } end modules ['util-mrg'] = {
     version   = 1.001,
     comment   = "companion to luat-lib.mkiv",
     author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
diff --git a/lualibs-util-sto.lua b/lualibs-util-sto.lua
new file mode 100644
index 0000000..42ee6cf
--- /dev/null
+++ b/lualibs-util-sto.lua
@@ -0,0 +1,172 @@
+if not modules then modules = { } end modules ['util-sto'] = {
+    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 setmetatable, getmetatable = setmetatable, getmetatable
+
+utilities         = utilities or { }
+utilities.storage = utilities.storage or { }
+local storage     = utilities.storage
+
+function storage.mark(t)
+    if not t then
+        texio.write_nl("fatal error: storage cannot be marked")
+        return -- os.exit()
+    end
+    local m = getmetatable(t)
+    if not m then
+        m = { }
+        setmetatable(t,m)
+    end
+    m.__storage__ = true
+    return t
+end
+
+function storage.allocate(t)
+    t = t or { }
+    local m = getmetatable(t)
+    if not m then
+        m = { }
+        setmetatable(t,m)
+    end
+    m.__storage__ = true
+    return t
+end
+
+function storage.marked(t)
+    local m = getmetatable(t)
+    return m and m.__storage__
+end
+
+function storage.checked(t)
+    if not t then
+        texio.write_nl("fatal error: storage has not been allocated")
+        return -- os.exit()
+    end
+    return t
+end
+
+--~ function utilities.storage.delay(parent,name,filename)
+--~     local m = getmetatable(parent)
+--~     m.__list[name] = filename
+--~ end
+--~
+--~ function utilities.storage.predefine(parent)
+--~     local list = { }
+--~     local m = getmetatable(parent) or {
+--~         __list = list,
+--~         __index = function(t,k)
+--~             local l = require(list[k])
+--~             t[k] = l
+--~             return l
+--~         end
+--~     }
+--~     setmetatable(parent,m)
+--~ end
+--~
+--~ bla = { }
+--~ utilities.storage.predefine(bla)
+--~ utilities.storage.delay(bla,"test","oepsoeps")
+--~ local t = bla.test
+--~ table.print(t)
+--~ print(t.a)
+
+function storage.setinitializer(data,initialize)
+    local m = getmetatable(data) or { }
+    m.__index = function(data,k)
+        m.__index = nil -- so that we can access the entries during initializing
+        initialize()
+        return data[k]
+    end
+    setmetatable(data, m)
+end
+
+local keyisvalue = { __index = function(t,k)
+    t[k] = k
+    return k
+end }
+
+function storage.sparse(t)
+    t = t or { }
+    setmetatable(t,keyisvalue)
+    return t
+end
+
+-- table namespace ?
+
+local function f_empty ()             return "" end -- t,k
+local function f_self  (t,k) t[k] = k return k  end
+local function f_ignore()                       end -- t,k,v
+
+local t_empty  = { __index    = f_empty  }
+local t_self   = { __index    = f_self   }
+local t_ignore = { __newindex = f_ignore }
+
+function table.setmetatableindex(t,f)
+    local m = getmetatable(t)
+    if m then
+        if f == "empty" then
+            m.__index = f_empty
+        elseif f == "key" then
+            m.__index = f_self
+        else
+            m.__index = f
+        end
+    else
+        if f == "empty" then
+            setmetatable(t, t_empty)
+        elseif f == "key" then
+            setmetatable(t, t_self)
+        else
+            setmetatable(t,{ __index = f })
+        end
+    end
+    return t
+end
+
+function table.setmetatablenewindex(t,f)
+    local m = getmetatable(t)
+    if m then
+        if f == "ignore" then
+            m.__newindex = f_ignore
+        else
+            m.__newindex = f
+        end
+    else
+        if f == "ignore" then
+            setmetatable(t, t_ignore)
+        else
+            setmetatable(t,{ __newindex = f })
+        end
+    end
+    return t
+end
+
+function table.setmetatablecall(t,f)
+    local m = getmetatable(t)
+    if m then
+        m.__call = f
+    else
+        setmetatable(t,{ __call = f })
+    end
+    return t
+end
+
+function table.setmetatablekey(t,key,value)
+    local m = getmetatable(t)
+    if not m then
+        m = { }
+        setmetatable(t,m)
+    end
+    m[key] = value
+    return t
+end
+
+function table.getmetatablekey(t,key,value)
+    local m = getmetatable(t)
+    return m and m[key]
+end
diff --git a/lualibs-util-tab.lua b/lualibs-util-tab.lua
index 7950a03..7a2da29 100644
--- a/lualibs-util-tab.lua
+++ b/lualibs-util-tab.lua
@@ -1,4 +1,4 @@
-if not modules then modules = { } end modules ['l-aux'] = {
+if not modules then modules = { } end modules ['util-tab'] = {
     version   = 1.001,
     comment   = "companion to luat-lib.mkiv",
     author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
@@ -6,252 +6,279 @@ if not modules then modules = { } end modules ['l-aux'] = {
     license   = "see context related readme files"
 }
 
--- for inline, no store split : for s in string.gmatch(str,",* *([^,]+)") do .. end
+utilities        = utilities or {}
+utilities.tables = utilities.tables or { }
+local tables     = utilities.tables
 
-aux = aux or { }
+local format, gmatch, rep, gsub = string.format, string.gmatch, string.rep, string.gsub
+local concat, insert, remove = table.concat, table.insert, table.remove
+local setmetatable, getmetatable, tonumber, tostring = setmetatable, getmetatable, tonumber, tostring
+local type, next, rawset, tonumber, loadstring = type, next, rawset, tonumber, loadstring
+local lpegmatch, P, Cs = lpeg.match, lpeg.P, lpeg.Cs
 
-local concat, format, gmatch = table.concat, string.format, string.gmatch
-local tostring, type = tostring, type
-local lpegmatch = lpeg.match
-
-local P, R, V = lpeg.P, lpeg.R, lpeg.V
-
-local escape, left, right = P("\\"), P('{'), P('}')
-
-lpeg.patterns.balanced = P {
-    [1] = ((escape * (left+right)) + (1 - (left+right)) + V(2))^0,
-    [2] = left * V(1) * right
-}
-
-local space     = lpeg.P(' ')
-local equal     = lpeg.P("=")
-local comma     = lpeg.P(",")
-local lbrace    = lpeg.P("{")
-local rbrace    = lpeg.P("}")
-local nobrace   = 1 - (lbrace+rbrace)
-local nested    = lpeg.P { lbrace * (nobrace + lpeg.V(1))^0 * rbrace }
-local spaces    = space^0
-
-local value     = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
-
-local key       = lpeg.C((1-equal-comma)^1)
-local pattern_a = (space+comma)^0 * (key * equal * value + key * lpeg.C(""))
-local pattern_c = (space+comma)^0 * (key * equal * value)
-
-local key       = lpeg.C((1-space-equal-comma)^1)
-local pattern_b = spaces * comma^0 * spaces * (key * ((spaces * equal * spaces * value) + lpeg.C("")))
-
--- "a=1, b=2, c=3, d={a{b,c}d}, e=12345, f=xx{a{b,c}d}xx, g={}" : outer {} removes, leading spaces ignored
-
-local hash = { }
-
-local function set(key,value) -- using Carg is slower here
-    hash[key] = value
+function tables.definetable(target) -- defines undefined tables
+    local composed, t, n = nil, { }, 0
+    for name in gmatch(target,"([^%.]+)") do
+        n = n + 1
+        if composed then
+            composed = composed .. "." .. name
+        else
+            composed = name
+        end
+        t[n] = format("%s = %s or { }",composed,composed)
+    end
+    return concat(t,"\n")
 end
 
-local pattern_a_s = (pattern_a/set)^1
-local pattern_b_s = (pattern_b/set)^1
-local pattern_c_s = (pattern_c/set)^1
-
-aux.settings_to_hash_pattern_a = pattern_a_s
-aux.settings_to_hash_pattern_b = pattern_b_s
-aux.settings_to_hash_pattern_c = pattern_c_s
-
-function aux.make_settings_to_hash_pattern(set,how)
-    if how == "strict" then
-        return (pattern_c/set)^1
-    elseif how == "tolerant" then
-        return (pattern_b/set)^1
-    else
-        return (pattern_a/set)^1
+function tables.accesstable(target,root)
+    local t = root or _G
+    for name in gmatch(target,"([^%.]+)") do
+        t = t[name]
+        if not t then
+            return
+        end
     end
+    return t
 end
 
-function aux.settings_to_hash(str,existing)
-    if str and str ~= "" then
-        hash = existing or { }
-        if moretolerant then
-            lpegmatch(pattern_b_s,str)
-        else
-            lpegmatch(pattern_a_s,str)
+function tables.migratetable(target,v,root)
+    local t = root or _G
+    local names = string.split(target,".")
+    for i=1,#names-1 do
+        local name = names[i]
+        t[name] = t[name] or { }
+        t = t[name]
+        if not t then
+            return
         end
-        return hash
-    else
-        return { }
     end
+    t[names[#names]] = v
 end
 
-function aux.settings_to_hash_tolerant(str,existing)
-    if str and str ~= "" then
-        hash = existing or { }
-        lpegmatch(pattern_b_s,str)
-        return hash
-    else
-        return { }
+function tables.removevalue(t,value) -- todo: n
+    if value then
+        for i=1,#t do
+            if t[i] == value then
+                remove(t,i)
+                -- remove all, so no: return
+            end
+        end
     end
 end
 
-function aux.settings_to_hash_strict(str,existing)
-    if str and str ~= "" then
-        hash = existing or { }
-        lpegmatch(pattern_c_s,str)
-        return next(hash) and hash
-    else
-        return nil
+function tables.insertbeforevalue(t,value,extra)
+    for i=1,#t do
+        if t[i] == extra then
+            remove(t,i)
+        end
+    end
+    for i=1,#t do
+        if t[i] == value then
+            insert(t,i,extra)
+            return
+        end
     end
+    insert(t,1,extra)
 end
 
-local separator = comma * space^0
-local value     = lpeg.P(lbrace * lpeg.C((nobrace + nested)^0) * rbrace) + lpeg.C((nested + (1-comma))^0)
-local pattern   = lpeg.Ct(value*(separator*value)^0)
-
--- "aap, {noot}, mies" : outer {} removes, leading spaces ignored
-
-aux.settings_to_array_pattern = pattern
-
--- we could use a weak table as cache
+function tables.insertaftervalue(t,value,extra)
+    for i=1,#t do
+        if t[i] == extra then
+            remove(t,i)
+        end
+    end
+    for i=1,#t do
+        if t[i] == value then
+            insert(t,i+1,extra)
+            return
+        end
+    end
+    insert(t,#t+1,extra)
+end
 
-function aux.settings_to_array(str)
-    if not str or str == "" then
-        return { }
-    else
-        return lpegmatch(pattern,str)
+-- experimental
+
+local function toxml(t,d,result,step)
+    for k, v in table.sortedpairs(t) do
+        if type(v) == "table" then
+            if type(k) == "number" then
+                result[#result+1] = format("%s<entry n='%s'>",d,k)
+                toxml(v,d..step,result,step)
+                result[#result+1] = format("%s</entry>",d,k)
+            else
+                result[#result+1] = format("%s<%s>",d,k)
+                toxml(v,d..step,result,step)
+                result[#result+1] = format("%s</%s>",d,k)
+            end
+        elseif type(k) == "number" then
+            result[#result+1] = format("%s<entry n='%s'>%s</entry>",d,k,v,k)
+        else
+            result[#result+1] = format("%s<%s>%s</%s>",d,k,tostring(v),k)
+        end
     end
 end
 
-local function set(t,v)
-    t[#t+1] = v
+function table.toxml(t,name,nobanner,indent,spaces)
+    local noroot = name == false
+    local result = (nobanner or noroot) and { } or { "<?xml version='1.0' standalone='yes' ?>" }
+    local indent = rep(" ",indent or 0)
+    local spaces = rep(" ",spaces or 1)
+    if noroot then
+        toxml( t, inndent, result, spaces)
+    else
+        toxml( { [name or "root"] = t }, indent, result, spaces)
+    end
+    return concat(result,"\n")
 end
 
-local value   = lpeg.P(lpeg.Carg(1)*value) / set
-local pattern = value*(separator*value)^0 * lpeg.Carg(1)
+-- also experimental
 
-function aux.add_settings_to_array(t,str)
-    return lpegmatch(pattern,str,nil,t)
-end
+-- encapsulate(table,utilities.tables)
+-- encapsulate(table,utilities.tables,true)
+-- encapsulate(table,true)
 
-function aux.hash_to_string(h,separator,yes,no,strict,omit)
-    if h then
-        local t, s = { }, table.sortedkeys(h)
-        omit = omit and table.tohash(omit)
-        for i=1,#s do
-            local key = s[i]
-            if not omit or not omit[key] then
-                local value = h[key]
-                if type(value) == "boolean" then
-                    if yes and no then
-                        if value then
-                            t[#t+1] = key .. '=' .. yes
-                        elseif not strict then
-                            t[#t+1] = key .. '=' .. no
-                        end
-                    elseif value or not strict then
-                        t[#t+1] = key .. '=' .. tostring(value)
-                    end
+function tables.encapsulate(core,capsule,protect)
+    if type(capsule) ~= "table" then
+        protect = true
+        capsule = { }
+    end
+    for key, value in next, core do
+        if capsule[key] then
+            print(format("\ninvalid inheritance '%s' in '%s': %s",key,tostring(core)))
+            os.exit()
+        else
+            capsule[key] = value
+        end
+    end
+    if protect then
+        for key, value in next, core do
+            core[key] = nil
+        end
+        setmetatable(core, {
+            __index = capsule,
+            __newindex = function(t,key,value)
+                if capsule[key] then
+                    print(format("\ninvalid overload '%s' in '%s'",key,tostring(core)))
+                    os.exit()
                 else
-                    t[#t+1] = key .. '=' .. value
+                    rawset(t,key,value)
                 end
             end
-        end
-        return concat(t,separator or ",")
-    else
-        return ""
+        } )
     end
 end
 
-function aux.array_to_string(a,separator)
-    if a then
-        return concat(a,separator or ",")
+local function serialize(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] = format("%q,",v)
+            elseif tv == "number" then
+                r[#r+1] = format("%s,",v)
+            elseif tv == "table" then
+                serialize(v,r)
+            elseif tv == "boolean" then
+                r[#r+1] = format("%s,",tostring(v))
+            end
+        end
     else
-        return ""
+        for k, v in next, t do
+            local tv = type(v)
+            if tv == "string" then
+                r[#r+1] = format("[%q]=%q,",k,v)
+            elseif tv == "number" then
+                r[#r+1] = format("[%q]=%s,",k,v)
+            elseif tv == "table" then
+                r[#r+1] = format("[%q]=",k)
+                serialize(v,r)
+            elseif tv == "boolean" then
+                r[#r+1] = format("[%q]=%s,",k,tostring(v))
+            end
+        end
     end
-end
-
-function aux.settings_to_set(str,t)
-    t = t or { }
-    for s in gmatch(str,"%s*([^,]+)") do
-        t[s] = true
+    if outer then
+        r[#r+1] = "}"
+    else
+        r[#r+1] = "},"
     end
-    return t
+    return r
 end
 
-local value     = lbrace * lpeg.C((nobrace + nested)^0) * rbrace
-local pattern   = lpeg.Ct((space + value)^0)
-
-function aux.arguments_to_table(str)
-    return lpegmatch(pattern,str)
+function table.fastserialize(t,prefix)
+    return concat(serialize(t,{ prefix or "return" },true))
 end
 
--- temporary here
-
-function aux.getparameters(self,class,parentclass,settings)
-    local sc = self[class]
-    if not sc then
-        sc = table.clone(self[parent])
-        self[class] = sc
+function table.deserialize(str)
+    if not str or str == "" then
+        return
+    end
+    local code = loadstring(str)
+    if not code then
+        return
     end
-    aux.settings_to_hash(settings,sc)
+    code = code()
+    if not code then
+        return
+    end
+    return code
 end
 
--- temporary here
-
-local digit         = lpeg.R("09")
-local period        = lpeg.P(".")
-local zero          = lpeg.P("0")
-local trailingzeros = zero^0 * -digit -- suggested by Roberto R
-local case_1        = period * trailingzeros / ""
-local case_2        = period * (digit - trailingzeros)^1 * (trailingzeros / "")
-local number        = digit^1 * (case_1 + case_2)
-local stripper      = lpeg.Cs((number + 1)^0)
-
---~ local sample = "bla 11.00 bla 11 bla 0.1100 bla 1.00100 bla 0.00 bla 0.001 bla 1.1100 bla 0.100100100 bla 0.00100100100"
---~ collectgarbage("collect")
---~ str = string.rep(sample,10000)
---~ local ts = os.clock()
---~ lpegmatch(stripper,str)
---~ print(#str, os.clock()-ts, lpegmatch(stripper,sample))
-
-lpeg.patterns.strip_zeros = stripper
-
-function aux.strip_zeros(str)
-    return lpegmatch(stripper,str)
+-- inspect(table.fastserialize { a = 1, b = { 4, { 5, 6 } }, c = { d = 7, e = 'f"g\nh' } })
+
+function table.load(filename)
+    if filename then
+        local t = io.loaddata(filename)
+        if t and t ~= "" then
+            t = loadstring(t)
+            if type(t) == "function" then
+                t = t()
+                if type(t) == "table" then
+                    return t
+                end
+            end
+        end
+    end
 end
 
-function aux.definetable(target) -- defines undefined tables
-    local composed, t = nil, { }
-    for name in gmatch(target,"([^%.]+)") do
-        if composed then
-            composed = composed .. "." .. name
-        else
-            composed = name
+local function slowdrop(t)
+    local r = { }
+    local l = { }
+    for i=1,#t do
+        local ti = t[i]
+        local j = 0
+        for k, v in next, ti do
+            j = j + 1
+            l[j] = format("%s=%q",k,v)
         end
-        t[#t+1] = format("%s = %s or { }",composed,composed)
+        r[i] = format(" {%s},\n",concat(l))
     end
-    return concat(t,"\n")
+    return format("return {\n%s}",concat(r))
 end
 
-function aux.accesstable(target)
-    local t = _G
-    for name in gmatch(target,"([^%.]+)") do
-        t = t[name]
+local function fastdrop(t)
+    local r = { "return {\n" }
+    for i=1,#t do
+        local ti = t[i]
+        r[#r+1] = " {"
+        for k, v in next, ti do
+            r[#r+1] = format("%s=%q",k,v)
+        end
+        r[#r+1] = "},\n"
     end
-    return t
+    r[#r+1] = "}"
+    return concat(r)
 end
 
--- as we use this a lot ...
-
---~ function aux.cachefunction(action,weak)
---~     local cache = { }
---~     if weak then
---~         setmetatable(cache, { __mode = "kv" } )
---~     end
---~     local function reminder(str)
---~         local found = cache[str]
---~         if not found then
---~             found = action(str)
---~             cache[str] = found
---~         end
---~         return found
---~     end
---~     return reminder, cache
---~ end
+function table.drop(t,slow)
+    if #t == 0 then
+        return "return { }"
+    elseif slow == true then
+        return slowdrop(t) -- less memory
+    else
+        return fastdrop(t) -- some 15% faster
+    end
+end
-- 
cgit v1.2.3