diff options
| -rw-r--r-- | lualibs-util-tab.lua | 307 | 
1 files changed, 258 insertions, 49 deletions
diff --git a/lualibs-util-tab.lua b/lualibs-util-tab.lua index 7a2da29..ecf36b1 100644 --- a/lualibs-util-tab.lua +++ b/lualibs-util-tab.lua @@ -10,24 +10,53 @@ utilities        = utilities or {}  utilities.tables = utilities.tables or { }  local tables     = utilities.tables -local format, gmatch, rep, gsub = string.format, string.gmatch, string.rep, string.gsub +local format, gmatch, gsub = string.format, string.gmatch, 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 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 formatters = string.formatters -function tables.definetable(target) -- defines undefined tables -    local composed, t, n = nil, { }, 0 -    for name in gmatch(target,"([^%.]+)") do -        n = n + 1 +local splitter = lpeg.tsplitat(".") + +function tables.definetable(target,nofirst,nolast) -- defines undefined tables +    local composed, shortcut, t = nil, nil, { } +    local snippets = lpegmatch(splitter,target) +    for i=1,#snippets - (nolast and 1 or 0) do +        local name = snippets[i]          if composed then -            composed = composed .. "." .. name +            composed = shortcut .. "." .. name +            shortcut = shortcut .. "_" .. name +            t[#t+1] = formatters["local %s = %s if not %s then %s = { } %s = %s end"](shortcut,composed,shortcut,shortcut,composed,shortcut)          else              composed = name +            shortcut = name +            if not nofirst then +                t[#t+1] = formatters["%s = %s or { }"](composed,composed) +            end          end -        t[n] = format("%s = %s or { }",composed,composed)      end -    return concat(t,"\n") +    if nolast then +        composed = shortcut .. "." .. snippets[#snippets] +    end +    return concat(t,"\n"), composed +end + +-- local t = tables.definedtable("a","b","c","d") + +function tables.definedtable(...) +    local t = _G +    for i=1,select("#",...) do +        local li = select(i,...) +        local tl = t[li] +        if not tl then +            tl = { } +            t[li] = tl +        end +        t = tl +    end +    return t  end  function tables.accesstable(target,root) @@ -98,35 +127,131 @@ end  -- experimental +local escape = Cs(Cc('"') * ((P('"')/'""' + P(1))^0) * Cc('"')) + +function table.tocsv(t,specification) +    if t and #t > 0 then +        local result = { } +        local r = { } +        specification = specification or { } +        local fields = specification.fields +        if type(fields) ~= "string" then +            fields = sortedkeys(t[1]) +        end +        local separator = specification.separator or "," +        if specification.preamble == true then +            for f=1,#fields do +                r[f] = lpegmatch(escape,tostring(fields[f])) +            end +            result[1] = concat(r,separator) +        end +        for i=1,#t do +            local ti = t[i] +            for f=1,#fields do +                local field = ti[fields[f]] +                if type(field) == "string" then +                    r[f] = lpegmatch(escape,field) +                else +                    r[f] = tostring(field) +                end +            end +            result[#result+1] = concat(r,separator) +        end +        return concat(result,"\n") +    else +        return "" +    end +end + +-- local nspaces = utilities.strings.newrepeater(" ") +-- local escape  = Cs((P("<")/"<" + P(">")/">" + P("&")/"&" + P(1))^0) +-- +-- local function toxml(t,d,result,step) +--     for k, v in sortedpairs(t) do +--         local s = nspaces[d] +--         local tk = type(k) +--         local tv = type(v) +--         if tv == "table" then +--             if tk == "number" then +--                 result[#result+1] = format("%s<entry n='%s'>",s,k) +--                 toxml(v,d+step,result,step) +--                 result[#result+1] = format("%s</entry>",s,k) +--             else +--                 result[#result+1] = format("%s<%s>",s,k) +--                 toxml(v,d+step,result,step) +--                 result[#result+1] = format("%s</%s>",s,k) +--             end +--         elseif tv == "string" then +--             if tk == "number" then +--                 result[#result+1] = format("%s<entry n='%s'>%s</entry>",s,k,lpegmatch(escape,v),k) +--             else +--                 result[#result+1] = format("%s<%s>%s</%s>",s,k,lpegmatch(escape,v),k) +--             end +--         elseif tk == "number" then +--             result[#result+1] = format("%s<entry n='%s'>%s</entry>",s,k,tostring(v),k) +--         else +--             result[#result+1] = format("%s<%s>%s</%s>",s,k,tostring(v),k) +--         end +--     end +-- end +-- +-- much faster + +local nspaces = utilities.strings.newrepeater(" ") +  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) +    for k, v in sortedpairs(t) do +        local s = nspaces[d] -- inlining this is somewhat faster but gives more formatters +        local tk = type(k) +        local tv = type(v) +        if tv == "table" then +            if tk == "number" then +                result[#result+1] = formatters["%s<entry n='%s'>"](s,k) +                toxml(v,d+step,result,step) +                result[#result+1] = formatters["%s</entry>"](s,k) +            else +                result[#result+1] = formatters["%s<%s>"](s,k) +                toxml(v,d+step,result,step) +                result[#result+1] = formatters["%s</%s>"](s,k) +            end +        elseif tv == "string" then +            if tk == "number" then +                result[#result+1] = formatters["%s<entry n='%s'>%!xml!</entry>"](s,k,v,k)              else -                result[#result+1] = format("%s<%s>",d,k) -                toxml(v,d..step,result,step) -                result[#result+1] = format("%s</%s>",d,k) +                result[#result+1] = formatters["%s<%s>%!xml!</%s>"](s,k,v,k)              end -        elseif type(k) == "number" then -            result[#result+1] = format("%s<entry n='%s'>%s</entry>",d,k,v,k) +        elseif tk == "number" then +            result[#result+1] = formatters["%s<entry n='%s'>%S</entry>"](s,k,v,k)          else -            result[#result+1] = format("%s<%s>%s</%s>",d,k,tostring(v),k) +            result[#result+1] = formatters["%s<%s>%S</%s>"](s,k,v,k)          end      end  end -function table.toxml(t,name,nobanner,indent,spaces) +-- 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 + +function table.toxml(t,specification) +    specification = specification or { } +    local name   = specification.name      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) +    local result = (specification.nobanner or noroot) and { } or { "<?xml version='1.0' standalone='yes' ?>" } +    local indent = specification.indent or 0 +    local spaces = specification.spaces or 1      if noroot then -        toxml( t, inndent, result, spaces) +        toxml( t, indent, result, spaces)      else -        toxml( { [name or "root"] = t }, indent, result, spaces) +        toxml( { [name or "data"] = t }, indent, result, spaces)      end      return concat(result,"\n")  end @@ -144,7 +269,7 @@ function tables.encapsulate(core,capsule,protect)      end      for key, value in next, core do          if capsule[key] then -            print(format("\ninvalid inheritance '%s' in '%s': %s",key,tostring(core))) +            print(formatters["\ninvalid %s %a in %a"]("inheritance",key,core))              os.exit()          else              capsule[key] = value @@ -158,7 +283,7 @@ function tables.encapsulate(core,capsule,protect)              __index = capsule,              __newindex = function(t,key,value)                  if capsule[key] then -                    print(format("\ninvalid overload '%s' in '%s'",key,tostring(core))) +                    print(formatters["\ninvalid %s %a' in %a"]("overload",key,core))                      os.exit()                  else                      rawset(t,key,value) @@ -168,7 +293,7 @@ function tables.encapsulate(core,capsule,protect)      end  end -local function serialize(t,r,outer) -- no mixes +local function fastserialize(t,r,outer) -- no mixes      r[#r+1] = "{"      local n = #t      if n > 0 then @@ -176,27 +301,27 @@ local function serialize(t,r,outer) -- no mixes              local v = t[i]              local tv = type(v)              if tv == "string" then -                r[#r+1] = format("%q,",v) +                r[#r+1] = formatters["%q,"](v)              elseif tv == "number" then -                r[#r+1] = format("%s,",v) +                r[#r+1] = formatters["%s,"](v)              elseif tv == "table" then -                serialize(v,r) +                fastserialize(v,r)              elseif tv == "boolean" then -                r[#r+1] = format("%s,",tostring(v)) +                r[#r+1] = formatters["%S,"](v)              end          end      else          for k, v in next, t do              local tv = type(v)              if tv == "string" then -                r[#r+1] = format("[%q]=%q,",k,v) +                r[#r+1] = formatters["[%q]=%q,"](k,v)              elseif tv == "number" then -                r[#r+1] = format("[%q]=%s,",k,v) +                r[#r+1] = formatters["[%q]=%s,"](k,v)              elseif tv == "table" then -                r[#r+1] = format("[%q]=",k) -                serialize(v,r) +                r[#r+1] = formatters["[%q]="](k) +                fastserialize(v,r)              elseif tv == "boolean" then -                r[#r+1] = format("[%q]=%s,",k,tostring(v)) +                r[#r+1] = formatters["[%q]=%S,"](k,v)              end          end      end @@ -208,15 +333,67 @@ local function serialize(t,r,outer) -- no mixes      return r  end -function table.fastserialize(t,prefix) -    return concat(serialize(t,{ prefix or "return" },true)) +-- 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))  end  function table.deserialize(str)      if not str or str == "" then          return      end -    local code = loadstring(str) +    local code = load(str)      if not code then          return      end @@ -233,7 +410,7 @@ function table.load(filename)      if filename then          local t = io.loaddata(filename)          if t and t ~= "" then -            t = loadstring(t) +            t = load(t)              if type(t) == "function" then                  t = t()                  if type(t) == "table" then @@ -244,6 +421,10 @@ function table.load(filename)      end  end +function table.save(filename,t,n,...) +    io.savedata(filename,serialize(t,n == nil and true or n,...)) +end +  local function slowdrop(t)      local r = { }      local l = { } @@ -252,11 +433,11 @@ local function slowdrop(t)          local j = 0          for k, v in next, ti do              j = j + 1 -            l[j] = format("%s=%q",k,v) +            l[j] = formatters["%s=%q"](k,v)          end -        r[i] = format(" {%s},\n",concat(l)) +        r[i] = formatters[" {%t},\n"](l)      end -    return format("return {\n%s}",concat(r)) +    return formatters["return {\n%st}"](r)  end  local function fastdrop(t) @@ -265,7 +446,7 @@ local function fastdrop(t)          local ti = t[i]          r[#r+1] = " {"          for k, v in next, ti do -            r[#r+1] = format("%s=%q",k,v) +            r[#r+1] = formatters["%s=%q"](k,v)          end          r[#r+1] = "},\n"      end @@ -273,7 +454,7 @@ local function fastdrop(t)      return concat(r)  end -function table.drop(t,slow) +function table.drop(t,slow) -- only  { { a=2 }, {a=3} }      if #t == 0 then          return "return { }"      elseif slow == true then @@ -282,3 +463,31 @@ function table.drop(t,slow)          return fastdrop(t) -- some 15% faster      end  end + +function table.autokey(t,k) +    local v = { } +    t[k] = v +    return v +end + +local selfmapper = { __index = function(t,k) t[k] = k return k end } + +function table.twowaymapper(t) +    if not t then +        t = { } +    else +        for i=0,#t do +            local ti = t[i]       -- t[1]     = "one" +            if ti then +                local i = tostring(i) +                t[i]    = ti      -- t["1"]   = "one" +                t[ti]   = i       -- t["one"] = "1" +            end +        end +        t[""] = t[0] or "" +    end + -- setmetatableindex(t,"key") +    setmetatable(t,selfmapper) +    return t +end +  | 
