| ------------------------------------------------------------------------------- |
| -- Copyright (c) 2006-2013 Fabien Fleutot and others. |
| -- |
| -- All rights reserved. |
| -- |
| -- This program and the accompanying materials are made available |
| -- under the terms of the Eclipse Public License v1.0 which |
| -- accompanies this distribution, and is available at |
| -- http://www.eclipse.org/legal/epl-v10.html |
| -- |
| -- This program and the accompanying materials are also made available |
| -- under the terms of the MIT public license which accompanies this |
| -- distribution, and is available at http://www.lua.org/license.html |
| -- |
| -- Contributors: |
| -- Fabien Fleutot - API and implementation |
| -- |
| ------------------------------------------------------------------------------- |
| |
| --------------------------------------------------------------------- |
| ---------------------------------------------------------------------- |
| -- |
| -- 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 |
| |
| -- fargs: arguments fot a single call to f |
| -- first, last: indexes of the first & last elements mapped in each table |
| -- arg1: index of the first table in args |
| |
| -- 1 - find boundaries if any |
| local args, fargs, first, last, arg1 = {...}, { } |
| if type(args[1]) ~= "number" then first, arg1 = 1, 1 -- no boundary |
| elseif type(args[2]) ~= "number" then first, last, arg1 = 1, args[1], 2 |
| else first, last, arg1 = args[1], args[2], 3 end |
| assert (nargs >= arg1) -- at least one table |
| -- 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 - remove non-table arguments from args, adjust nargs |
| if arg1>1 then args = { select(arg1, unpack(args)) }; nargs = #args end |
| |
| -- 4 - perform the iteration |
| for i = first, last do |
| for j = 1, 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 ipairs {...} do |
| for _, x in pairs (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 ipairs{...} 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 |
| |
| |
| local tostring_cache = { } |
| local function __tostring(x) |
| local the_string = tostring_cache[x] |
| if the_string~=nil then return the_string end |
| local mt = getmetatable(x) |
| if mt then |
| local __tostring = mt.__tostring |
| if __tostring then |
| the_string = __tostring(x) |
| tostring_cache[x] = the_string |
| return the_string |
| end |
| end |
| if x~=nil then tostring_cache[x] = false end -- nil is an illegal key |
| return false |
| 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) |
| |
| local custom_string = __tostring(adt) |
| if custom_string then return #custom_string end |
| |
| -- 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 and k>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):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 and k>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 custom_string = __tostring(adt) |
| if custom_string then acc(custom_string) else |
| local y = x[type(adt)] |
| if y then y() else acc(tostring(adt)) end |
| 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 |