--[[ General Lua Libraries for Lua 5.1, 5.2 & 5.3 Copyright (C) 2002-2018 stdlib authors ]] --[[-- Extensions to the core table module. The module table returned by `std.table` also contains all of the entries from the core table module. An hygienic way to import this module, then, is simply to override the core `table` locally: local table = require 'std.table' @corelibrary std.table ]] local _ = require 'std._base' local argscheck = _.typecheck and _.typecheck.argscheck local invert = _.table.invert local maxn = _.table.maxn _ = nil local _ENV = require 'std.normalize' { 'table', merge = 'table.merge', min = 'math.min', } --[[ =============== ]]-- --[[ Implementation. ]]-- --[[ =============== ]]-- local M local function merge_allfields(t, u, map, nometa) if type(map) ~= 'table' then map, nometa = nil, map end if not nometa then setmetatable(t, getmetatable(u)) end if map then for k, v in pairs(u) do t[map[k] or k] = v end else for k, v in pairs(u) do t[k] = v end end return t end local function merge_namedfields(t, u, keys, nometa) if type(keys) ~= 'table' then keys, nometa = nil, keys end if not nometa then setmetatable(t, getmetatable(u)) end for _, k in pairs(keys or {}) do t[k] = u[k] end return t end local function depair(ls) local t = {} for _, v in ipairs(ls) do t[v[1]] = v[2] end return t end local function enpair(t) local tt = {} for i, v in pairs(t) do tt[#tt + 1] = {i, v} end return tt end local _insert = table.insert local function insert(t, pos, v) if v == nil then pos, v = len(t) + 1, pos end if pos < 1 or pos > len(t) + 1 then argerror('std.table.insert', 2, 'position ' .. pos .. ' out of bounds', 2) end _insert(t, pos, v) return t end local function keys(t) local l = {} for k in pairs(t) do l[#l + 1] = k end return l end local function new(x, t) return setmetatable(t or {}, {__index = function(t, i) return x end}) end local function project(fkey, tt) local r = {} for _, t in ipairs(tt) do r[#r + 1] = t[fkey] end return r end local function size(t) local n = 0 for _ in pairs(t) do n = n + 1 end return n end -- Preserve core table sort function. local _sort = table.sort local function sort(t, c) _sort(t, c) return t end local _remove = table.remove local function remove(t, pos) local lent = len(t) pos = pos or lent if pos < min(1, lent) or pos > lent + 1 then -- +1? whu? that's what 5.2.3 does!?! argerror('std.table.remove', 2, 'position ' .. pos .. ' out of bounds', 2) end return _remove(t, pos) end local _unpack = unpack local function unpack(t, i, j) if j == nil then -- if j was not given, respect __len, otherwise use maxn local m = getmetamethod(t, '__len') j = m and m(t) or maxn(t) end return _unpack(t, tonumber(i) or 1, tonumber(j)) end local function values(t) local l = {} for _, v in pairs(t) do l[#l + 1] = v end return l end --[[ ================= ]]-- --[[ Public Interface. ]]-- --[[ ================= ]]-- local function X(decl, fn) return argscheck and argscheck('std.table.' .. decl, fn) or fn end M = { --- Core Functions -- @section corefuncs --- Enhance core *table.insert* to return its result. -- If *pos* is not given, respect `__len` metamethod when calculating -- default append. Also, diagnose out of bounds *pos* arguments -- consistently on any supported version of Lua. -- @function insert -- @tparam table t a table -- @int[opt=len(t)] pos index at which to insert new element -- @param v value to insert into *t* -- @treturn table *t* -- @usage -- --> {1, 'x', 2, 3, 'y'} -- insert(insert({1, 2, 3}, 2, 'x'), 'y') insert = X('insert(table, [int], any)', insert), --- Largest integer key in a table. -- @function maxn -- @tparam table t a table -- @treturn int largest integer key in *t* -- @usage -- --> 42 -- maxn {'a', b='c', 99, [42]='x', 'x', [5]=67} maxn = X('maxn(table)', maxn), --- Turn a tuple into a list, with tuple-size in field `n` -- @function pack -- @param ... tuple -- @return list-like table, with tuple-size in field `n` -- @usage -- --> {1, 2, 'ax', n=3} -- pack(find('ax1', '(%D+)')) pack = pack, --- Enhance core *table.remove* to respect `__len` when *pos* is omitted. -- Also, diagnose out of bounds *pos* arguments consistently on any supported -- version of Lua. -- @function remove -- @tparam table t a table -- @int[opt=len(t)] pos index from which to remove an element -- @return removed value, or else `nil` -- @usage -- --> {1, 2, 5} -- t = {1, 2, 'x', 5} -- remove(t, 3) == 'x' and t remove = X('remove(table, ?int)', remove), --- Enhance core *table.sort* to return its result. -- @function sort -- @tparam table t unsorted table -- @tparam[opt=std.operator.lt] comparator c ordering function callback -- @return *t* with keys sorted according to *c* -- @usage -- table.concat(sort(object)) sort = X('sort(table, ?function)', sort), --- Enhance core *table.unpack* to always unpack up to __len or maxn. -- @function unpack -- @tparam table t table to act on -- @int[opt=1] i first index to unpack -- @int[opt=table.maxn(t)] j last index to unpack -- @return ... values of numeric indices of *t* -- @usage -- return unpack(results_table) unpack = X('unpack(table, ?int, ?int)', unpack), --- Accessor Functions -- @section accessorfuncs --- Make a shallow copy of a table, including any metatable. -- @function clone -- @tparam table t source table -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` -- @bool[opt] nometa if non-nil don't copy metatable -- @return copy of *t*, also sharing *t*'s metatable unless *nometa* -- is true, and with keys renamed according to *map* -- @see merge -- @see clone_select -- @usage -- shallowcopy = clone(original, {rename_this='to_this'}, ':nometa') clone = X('clone(table, [table], ?boolean|:nometa)', function(...) return merge_allfields({}, ...) end), --- Make a partial clone of a table. -- -- Like `clone`, but does not copy any fields by default. -- @function clone_select -- @tparam table t source table -- @tparam[opt={}] table keys list of keys to copy -- @bool[opt] nometa if non-nil don't copy metatable -- @treturn table copy of fields in *selection* from *t*, also sharing *t*'s -- metatable unless *nometa* -- @see clone -- @see merge_select -- @usage -- partialcopy = clone_select(original, {'this', 'and_this'}, true) clone_select = X('clone_select(table, [table], ?boolean|:nometa)', function(...) return merge_namedfields({}, ...) end), --- Turn a list of pairs into a table. -- @todo Find a better name. -- @function depair -- @tparam table ls list of lists -- @treturn table a flat table with keys and values from *ls* -- @see enpair -- @usage -- --> {a=1, b=2, c=3} -- depair {{'a', 1}, {'b', 2}, {'c', 3}} depair = X('depair(list of lists)', depair), --- Turn a table into a list of pairs. -- @todo Find a better name. -- @function enpair -- @tparam table t a table `{i1=v1, ..., in=vn}` -- @treturn table a new list of pairs containing `{{i1, v1}, ..., {in, vn}}` -- @see depair -- @usage -- --> {{1, 'a'}, {2, 'b'}, {3, 'c'}} -- enpair {'a', 'b', 'c'} enpair = X('enpair(table)', enpair), --- Return whether table is empty. -- @function empty -- @tparam table t any table -- @treturn boolean `true` if *t* is empty, otherwise `false` -- @usage -- if empty(t) then error 'ohnoes' end empty = X('empty(table)', function(t) return not next(t) end), --- Make a table with a default value for unset keys. -- @function new -- @param[opt=nil] x default entry value -- @tparam[opt={}] table t initial table -- @treturn table table whose unset elements are *x* -- @usage -- t = new(0) new = X('new(?any, ?table)', new), --- Project a list of fields from a list of tables. -- @function project -- @param fkey field to project -- @tparam table tt a list of tables -- @treturn table list of *fkey* fields from *tt* -- @usage -- --> {1, 3, 'yy'} -- project('xx', {{'a', xx=1, yy='z'}, {'b', yy=2}, {'c', xx=3}, {xx='yy'}) project = X('project(any, list of tables)', project), --- Find the number of elements in a table. -- @function size -- @tparam table t any table -- @treturn int number of non-nil values in *t* -- @usage -- --> 3 -- size {foo=true, bar=true, baz=false} size = X('size(table)', size), --- Make the list of values of a table. -- @function values -- @tparam table t any table -- @treturn table list of values in *t* -- @see keys -- @usage -- --> {'a', 'c', 42} -- values {'a', b='c', [-1]=42} values = X('values(table)', values), --- Mutator Functions -- @section mutatorfuncs --- Invert a table. -- @function invert -- @tparam table t a table with `{k=v, ...}` -- @treturn table inverted table `{v=k, ...}` -- @usage -- --> {a=1, b=2, c=3} -- invert {'a', 'b', 'c'} invert = X('invert(table)', invert), --- Make the list of keys in table. -- @function keys -- @tparam table t a table -- @treturn table list of keys from *t* -- @see values -- @usage -- globals = keys(_G) keys = X('keys(table)', keys), --- Destructively merge one table's fields into another. -- @function merge -- @tparam table t destination table -- @tparam table u table with fields to merge -- @tparam[opt={}] table map table of `{old_key=new_key, ...}` -- @bool[opt] nometa if `true` or ':nometa' don't copy metatable -- @treturn table *t* with fields from *u* merged in -- @see clone -- @see merge_select -- @usage -- merge(_G, require 'std.debug', {say='log'}, ':nometa') merge = X('merge(table, table, [table], ?boolean|:nometa)', merge_allfields), --- Destructively merge another table's named fields into *table*. -- -- Like `merge`, but does not merge any fields by default. -- @function merge_select -- @tparam table t destination table -- @tparam table u table with fields to merge -- @tparam[opt={}] table keys list of keys to copy -- @bool[opt] nometa if `true` or ':nometa' don't copy metatable -- @treturn table copy of fields in *selection* from *t*, also sharing *t*'s -- metatable unless *nometa* -- @see merge -- @see clone_select -- @usage -- merge_select(_G, require 'std.debug', {'say'}, false) merge_select = X('merge_select(table, table, [table], ?boolean|:nometa)', merge_namedfields), } return merge(table, M) --- Types -- @section Types --- Signature of a @{sort} comparator function. -- @function comparator -- @param a any object -- @param b any object -- @treturn boolean `true` if *a* sorts before *b*, otherwise `false` -- @see sort -- @usage -- local reversor = function(a, b) return a > b end -- sort(t, reversor)