372 lines
12 KiB
Lua
Executable File
372 lines
12 KiB
Lua
Executable File
---------------------------------------------------------------------
|
|
----------------------------------------------------------------------
|
|
--
|
|
-- Table module extension
|
|
--
|
|
----------------------------------------------------------------------
|
|
----------------------------------------------------------------------
|
|
|
|
-- todo: table.scan (scan1?) fold1? flip?
|
|
|
|
function table.transpose(t)
|
|
local tt = { }
|
|
for a, b in pairs(t) do tt[b] = a end
|
|
return tt
|
|
end
|
|
|
|
function table.iforeach(f, ...)
|
|
-- assert (type (f) == "function") [wouldn't allow metamethod __call]
|
|
local nargs = select("#", ...)
|
|
if nargs==1 then -- Quick iforeach (most common case), just one table arg
|
|
local t = ...
|
|
assert (type (t) == "table")
|
|
for i = 1, #t do
|
|
local result = f (t[i])
|
|
-- If the function returns non-false, stop iteration
|
|
if result then return result end
|
|
end
|
|
else -- advanced case: boundaries and/or multiple tables
|
|
-- 1 - find boundaries if any
|
|
local args, fargs, first, last, arg1 = {...}, { }
|
|
if type(args[1]) ~= "number" then first, arg1 = 1, 1
|
|
elseif type(args[2]) ~= "number" then first, last, arg1 = 1, args[1], 2
|
|
else first, last, i = args[1], args[2], 3 end
|
|
assert (nargs > arg1)
|
|
-- 2 - determine upper boundary if not given
|
|
if not last then for i = arg1, nargs do
|
|
assert (type (args[i]) == "table")
|
|
last = max (#args[i], last)
|
|
end end
|
|
-- 3 - perform the iteration
|
|
for i = first, last do
|
|
for j = arg1, nargs do fargs[j] = args[j][i] end -- build args list
|
|
local result = f (unpack (fargs)) -- here is the call
|
|
-- If the function returns non-false, stop iteration
|
|
if result then return result end
|
|
end
|
|
end
|
|
end
|
|
|
|
function table.imap (f, ...)
|
|
local result, idx = { }, 1
|
|
local function g(...) result[idx] = f(...); idx=idx+1 end
|
|
table.iforeach(g, ...)
|
|
return result
|
|
end
|
|
|
|
function table.ifold (f, acc, ...)
|
|
local function g(...) acc = f (acc,...) end
|
|
table.iforeach (g, ...)
|
|
return acc
|
|
end
|
|
|
|
-- function table.ifold1 (f, ...)
|
|
-- return table.ifold (f, acc, 2, false, ...)
|
|
-- end
|
|
|
|
function table.izip(...)
|
|
local function g(...) return {...} end
|
|
return table.imap(g, ...)
|
|
end
|
|
|
|
function table.ifilter(f, t)
|
|
local yes, no = { }, { }
|
|
for i=1,#t do table.insert (f(t[i]) and yes or no, t[i]) end
|
|
return yes, no
|
|
end
|
|
|
|
function table.icat(...)
|
|
local result = { }
|
|
for t in values {...} do
|
|
for x in values (t) do
|
|
table.insert (result, x)
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
function table.iflatten (x) return table.icat (unpack (x)) end
|
|
|
|
function table.irev (t)
|
|
local result, nt = { }, #t
|
|
for i=0, nt-1 do result[nt-i] = t[i+1] end
|
|
return result
|
|
end
|
|
|
|
function table.isub (t, ...)
|
|
local ti, u = table.insert, { }
|
|
local args, nargs = {...}, select("#", ...)
|
|
for i=1, nargs/2 do
|
|
local a, b = args[2*i-1], args[2*i]
|
|
for i=a, b, a<=b and 1 or -1 do ti(u, t[i]) end
|
|
end
|
|
return u
|
|
end
|
|
|
|
function table.iall (f, ...)
|
|
local result = true
|
|
local function g(...) return not f(...) end
|
|
return not table.iforeach(g, ...)
|
|
--return result
|
|
end
|
|
|
|
function table.iany (f, ...)
|
|
local function g(...) return not f(...) end
|
|
return not table.iall(g, ...)
|
|
end
|
|
|
|
function table.shallow_copy(x)
|
|
local y={ }
|
|
for k, v in pairs(x) do y[k]=v end
|
|
return y
|
|
end
|
|
|
|
-- Warning, this is implementation dependent: it relies on
|
|
-- the fact the [next()] enumerates the array-part before the hash-part.
|
|
function table.cat(...)
|
|
local y={ }
|
|
for x in values{...} do
|
|
-- cat array-part
|
|
for _, v in ipairs(x) do table.insert(y,v) end
|
|
-- cat hash-part
|
|
local lx, k = #x
|
|
if lx>0 then k=next(x,lx) else k=next(x) end
|
|
while k do y[k]=x[k]; k=next(x,k) end
|
|
end
|
|
return y
|
|
end
|
|
|
|
function table.deep_copy(x)
|
|
local tracker = { }
|
|
local function aux (x)
|
|
if type(x) == "table" then
|
|
local y=tracker[x]
|
|
if y then return y end
|
|
y = { }; tracker[x] = y
|
|
setmetatable (y, getmetatable (x))
|
|
for k,v in pairs(x) do y[aux(k)] = aux(v) end
|
|
return y
|
|
else return x end
|
|
end
|
|
return aux(x)
|
|
end
|
|
|
|
function table.override(dst, src)
|
|
for k, v in pairs(src) do dst[k] = v end
|
|
for i = #src+1, #dst do dst[i] = nil end
|
|
return dst
|
|
end
|
|
|
|
|
|
function table.range(a,b,c)
|
|
if not b then assert(not(c)); b=a; a=1
|
|
elseif not c then c = (b>=a) and 1 or -1 end
|
|
local result = { }
|
|
for i=a, b, c do table.insert(result, i) end
|
|
return result
|
|
end
|
|
|
|
-- FIXME: new_indent seems to be always nil?!
|
|
-- FIXME: accumulator function should be configurable,
|
|
-- so that print() doesn't need to bufferize the whole string
|
|
-- before starting to print.
|
|
function table.tostring(t, ...)
|
|
local PRINT_HASH, HANDLE_TAG, FIX_INDENT, LINE_MAX, INITIAL_INDENT = true, true
|
|
for _, x in ipairs {...} do
|
|
if type(x) == "number" then
|
|
if not LINE_MAX then LINE_MAX = x
|
|
else INITIAL_INDENT = x end
|
|
elseif x=="nohash" then PRINT_HASH = false
|
|
elseif x=="notag" then HANDLE_TAG = false
|
|
else
|
|
local n = string['match'](x, "^indent%s*(%d*)$")
|
|
if n then FIX_INDENT = tonumber(n) or 3 end
|
|
end
|
|
end
|
|
LINE_MAX = LINE_MAX or math.huge
|
|
INITIAL_INDENT = INITIAL_INDENT or 1
|
|
|
|
local current_offset = 0 -- indentation level
|
|
local xlen_cache = { } -- cached results for xlen()
|
|
local acc_list = { } -- Generated bits of string
|
|
local function acc(...) -- Accumulate a bit of string
|
|
local x = table.concat{...}
|
|
current_offset = current_offset + #x
|
|
table.insert(acc_list, x)
|
|
end
|
|
local function valid_id(x)
|
|
-- FIXME: we should also reject keywords; but the list of
|
|
-- current keywords is not fixed in metalua...
|
|
return type(x) == "string"
|
|
and string['match'](x, "^[a-zA-Z_][a-zA-Z0-9_]*$")
|
|
end
|
|
|
|
-- Compute the number of chars it would require to display the table
|
|
-- on a single line. Helps to decide whether some carriage returns are
|
|
-- required. Since the size of each sub-table is required many times,
|
|
-- it's cached in [xlen_cache].
|
|
local xlen_type = { }
|
|
local function xlen(x, nested)
|
|
nested = nested or { }
|
|
if x==nil then return #"nil" end
|
|
--if nested[x] then return #tostring(x) end -- already done in table
|
|
local len = xlen_cache[x]
|
|
if len then return len end
|
|
local f = xlen_type[type(x)]
|
|
if not f then return #tostring(x) end
|
|
len = f (x, nested)
|
|
xlen_cache[x] = len
|
|
return len
|
|
end
|
|
|
|
-- optim: no need to compute lengths if I'm not going to use them
|
|
-- anyway.
|
|
if LINE_MAX == math.huge then xlen = function() return 0 end end
|
|
|
|
xlen_type["nil"] = function () return 3 end
|
|
function xlen_type.number (x) return #tostring(x) end
|
|
function xlen_type.boolean (x) return x and 4 or 5 end
|
|
function xlen_type.string (x) return #string.format("%q",x) end
|
|
function xlen_type.table (adt, nested)
|
|
|
|
-- Circular references detection
|
|
if nested [adt] then return #tostring(adt) end
|
|
nested [adt] = true
|
|
|
|
local has_tag = HANDLE_TAG and valid_id(adt.tag)
|
|
local alen = #adt
|
|
local has_arr = alen>0
|
|
local has_hash = false
|
|
local x = 0
|
|
|
|
if PRINT_HASH then
|
|
-- first pass: count hash-part
|
|
for k, v in pairs(adt) do
|
|
if k=="tag" and has_tag then
|
|
-- this is the tag -> do nothing!
|
|
elseif type(k)=="number" and k<=alen and math.fmod(k,1)==0 then
|
|
-- array-part pair -> do nothing!
|
|
else
|
|
has_hash = true
|
|
if valid_id(k) then x=x+#k
|
|
else x = x + xlen (k, nested) + 2 end -- count surrounding brackets
|
|
x = x + xlen (v, nested) + 5 -- count " = " and ", "
|
|
end
|
|
end
|
|
end
|
|
|
|
for i = 1, alen do x = x + xlen (adt[i], nested) + 2 end -- count ", "
|
|
|
|
nested[adt] = false -- No more nested calls
|
|
|
|
if not (has_tag or has_arr or has_hash) then return 3 end
|
|
if has_tag then x=x+#adt.tag+1 end
|
|
if not (has_arr or has_hash) then return x end
|
|
if not has_hash and alen==1 and type(adt[1])~="table" then
|
|
return x-2 -- substract extraneous ", "
|
|
end
|
|
return x+2 -- count "{ " and " }", substract extraneous ", "
|
|
end
|
|
|
|
-- Recursively print a (sub) table at given indentation level.
|
|
-- [newline] indicates whether newlines should be inserted.
|
|
local function rec (adt, nested, indent)
|
|
if not FIX_INDENT then indent = current_offset end
|
|
local function acc_newline()
|
|
acc ("\n"); acc (string.rep (" ", indent))
|
|
current_offset = indent
|
|
end
|
|
local x = { }
|
|
x["nil"] = function() acc "nil" end
|
|
function x.number() acc (tostring (adt)) end
|
|
--function x.string() acc (string.format ("%q", adt)) end
|
|
function x.string() acc ((string.format ("%q", adt):gsub("\\\n", "\\n"))) end
|
|
function x.boolean() acc (adt and "true" or "false") end
|
|
function x.table()
|
|
if nested[adt] then acc(tostring(adt)); return end
|
|
nested[adt] = true
|
|
|
|
|
|
local has_tag = HANDLE_TAG and valid_id(adt.tag)
|
|
local alen = #adt
|
|
local has_arr = alen>0
|
|
local has_hash = false
|
|
|
|
if has_tag then acc("`"); acc(adt.tag) end
|
|
|
|
-- First pass: handle hash-part
|
|
if PRINT_HASH then
|
|
for k, v in pairs(adt) do
|
|
-- pass if the key belongs to the array-part or is the "tag" field
|
|
if not (k=="tag" and HANDLE_TAG) and
|
|
not (type(k)=="number" and k<=alen and math.fmod(k,1)==0) then
|
|
|
|
-- Is it the first time we parse a hash pair?
|
|
if not has_hash then
|
|
acc "{ "
|
|
if not FIX_INDENT then indent = current_offset end
|
|
else acc ", " end
|
|
|
|
-- Determine whether a newline is required
|
|
local is_id, expected_len = valid_id(k)
|
|
if is_id then expected_len = #k + xlen (v, nested) + #" = , "
|
|
else expected_len = xlen (k, nested) +
|
|
xlen (v, nested) + #"[] = , " end
|
|
if has_hash and expected_len + current_offset > LINE_MAX
|
|
then acc_newline() end
|
|
|
|
-- Print the key
|
|
if is_id then acc(k); acc " = "
|
|
else acc "["; rec (k, nested, indent+(FIX_INDENT or 0)); acc "] = " end
|
|
|
|
-- Print the value
|
|
rec (v, nested, indent+(FIX_INDENT or 0))
|
|
has_hash = true
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Now we know whether there's a hash-part, an array-part, and a tag.
|
|
-- Tag and hash-part are already printed if they're present.
|
|
if not has_tag and not has_hash and not has_arr then acc "{ }";
|
|
elseif has_tag and not has_hash and not has_arr then -- nothing, tag already in acc
|
|
else
|
|
assert (has_hash or has_arr)
|
|
local no_brace = false
|
|
if has_hash and has_arr then acc ", "
|
|
elseif has_tag and not has_hash and alen==1 and type(adt[1])~="table" then
|
|
-- No brace required; don't print "{", remember not to print "}"
|
|
acc (" "); rec (adt[1], nested, indent+(FIX_INDENT or 0))
|
|
no_brace = true
|
|
elseif not has_hash then
|
|
-- Braces required, but not opened by hash-part handler yet
|
|
acc "{ "
|
|
if not FIX_INDENT then indent = current_offset end
|
|
end
|
|
|
|
-- 2nd pass: array-part
|
|
if not no_brace and has_arr then
|
|
rec (adt[1], nested, indent+(FIX_INDENT or 0))
|
|
for i=2, alen do
|
|
acc ", ";
|
|
if current_offset + xlen (adt[i], { }) > LINE_MAX
|
|
then acc_newline() end
|
|
rec (adt[i], nested, indent+(FIX_INDENT or 0))
|
|
end
|
|
end
|
|
if not no_brace then acc " }" end
|
|
end
|
|
nested[adt] = false -- No more nested calls
|
|
end
|
|
local y = x[type(adt)]
|
|
if y then y() else acc(tostring(adt)) end
|
|
end
|
|
--printf("INITIAL_INDENT = %i", INITIAL_INDENT)
|
|
current_offset = INITIAL_INDENT or 0
|
|
rec(t, { }, 0)
|
|
return table.concat (acc_list)
|
|
end
|
|
|
|
function table.print(...) return print(table.tostring(...)) end
|
|
|
|
return table |