mirror of
https://github.com/appgurueu/modlib.git
synced 2024-12-22 21:32:27 +01:00
889 lines
20 KiB
Lua
889 lines
20 KiB
Lua
-- Localize globals
|
|
local assert, ipairs, math, next, pairs, rawget, rawset, getmetatable, setmetatable, select, string, table, type
|
|
= assert, ipairs, math, next, pairs, rawget, rawset, getmetatable, setmetatable, select, string, table, type
|
|
|
|
local lt = modlib.func.lt
|
|
|
|
-- Set environment
|
|
local _ENV = {}
|
|
setfenv(1, _ENV)
|
|
|
|
-- Empty table
|
|
empty = {}
|
|
|
|
-- Table helpers
|
|
|
|
function from_iterator(...)
|
|
local table = {}
|
|
for key, value in ... do
|
|
table[key] = value
|
|
end
|
|
return table
|
|
end
|
|
|
|
function default(table, value)
|
|
return setmetatable(table, {
|
|
__index = function()
|
|
return value
|
|
end,
|
|
})
|
|
end
|
|
|
|
function map_index(table, func)
|
|
local mapping_metatable = {
|
|
__index = function(table, key)
|
|
return rawget(table, func(key))
|
|
end,
|
|
__newindex = function(table, key, value)
|
|
rawset(table, func(key), value)
|
|
end
|
|
}
|
|
return setmetatable(table, mapping_metatable)
|
|
end
|
|
|
|
function set_case_insensitive_index(table)
|
|
return map_index(table, string.lower)
|
|
end
|
|
|
|
--+ nilget(a, "b", "c") == a?.b?.c
|
|
function nilget(value, ...)
|
|
local n = select("#", ...)
|
|
for i = 1, n do
|
|
if value == nil then return nil end
|
|
value = value[select(i, ...)]
|
|
end
|
|
return value
|
|
end
|
|
|
|
deepget = nilget
|
|
|
|
--+ `deepset(a, "b", "c", d)` is the same as `a.b = a.b ?? {}; a.b.c = d`
|
|
function deepset(table, ...)
|
|
local n = select("#", ...)
|
|
for i = 1, n - 2 do
|
|
local key = select(i, ...)
|
|
local parent = table
|
|
table = parent[key]
|
|
if table == nil then
|
|
table = {}
|
|
parent[key] = table
|
|
end
|
|
end
|
|
table[select(n - 1, ...)] = select(n, ...)
|
|
end
|
|
|
|
-- Fisher-Yates
|
|
function shuffle(table)
|
|
for index = 1, #table - 1 do
|
|
local index_2 = math.random(index, #table)
|
|
table[index], table[index_2] = table[index_2], table[index]
|
|
end
|
|
return table
|
|
end
|
|
|
|
local rope_metatable = {__index = {
|
|
write = function(self, text)
|
|
table.insert(self, text)
|
|
end,
|
|
to_text = function(self)
|
|
return table.concat(self)
|
|
end
|
|
}}
|
|
--> rope with simple metatable (:write(text) and :to_text())
|
|
function rope(table)
|
|
return setmetatable(table or {}, rope_metatable)
|
|
end
|
|
|
|
local rope_len_metatable = {__index = {
|
|
write = function(self, text)
|
|
self.len = self.len + text:len()
|
|
end
|
|
}}
|
|
--> rope for determining length of text supporting `:write(text)` and `.len` to get the length of written text
|
|
function rope_len(len)
|
|
return setmetatable({len = len or 0}, rope_len_metatable)
|
|
end
|
|
|
|
function is_circular(table)
|
|
assert(type(table) == "table")
|
|
local known = {}
|
|
local function _is_circular(value)
|
|
if type(value) ~= "table" then
|
|
return false
|
|
end
|
|
if known[value] then
|
|
return true
|
|
end
|
|
known[value] = true
|
|
for key, value in pairs(value) do
|
|
if _is_circular(key) or _is_circular(value) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return _is_circular(table)
|
|
end
|
|
|
|
--+ Simple table equality check. Stack overflow if tables are too deep or circular.
|
|
--+ Use `is_circular(table)` to check whether a table is circular.
|
|
--> Equality of noncircular tables if `table` and `other_table` are tables
|
|
--> `table == other_table` else
|
|
function equals_noncircular(table, other_table)
|
|
local is_equal = table == other_table
|
|
if is_equal or type(table) ~= "table" or type(other_table) ~= "table" then
|
|
return is_equal
|
|
end
|
|
if #table ~= #other_table then
|
|
return false
|
|
end
|
|
local table_keys = {}
|
|
for key, value in pairs(table) do
|
|
local value_2 = other_table[key]
|
|
if not equals_noncircular(value, value_2) then
|
|
if type(key) == "table" then
|
|
table_keys[key] = value
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
for other_key, other_value in pairs(other_table) do
|
|
if type(other_key) == "table" then
|
|
local found
|
|
for table, value in pairs(table_keys) do
|
|
if equals_noncircular(other_key, table) and equals_noncircular(other_value, value) then
|
|
table_keys[table] = nil
|
|
found = true
|
|
break
|
|
end
|
|
end
|
|
if not found then
|
|
return false
|
|
end
|
|
else
|
|
if table[other_key] == nil then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
equals = equals_noncircular
|
|
|
|
--+ Table equality check properly handling circular tables - tables are equal as long as they provide equal key/value-pairs
|
|
--> Table content equality if `table` and `other_table` are tables
|
|
--> `table == other_table` else
|
|
function equals_content(table, other_table)
|
|
local equal_tables = {}
|
|
local function _equals(table, other_equal_table)
|
|
local function set_equal_tables(value)
|
|
equal_tables[table] = equal_tables[table] or {}
|
|
equal_tables[table][other_equal_table] = value
|
|
return value
|
|
end
|
|
local is_equal = table == other_equal_table
|
|
if is_equal or type(table) ~= "table" or type(other_equal_table) ~= "table" then
|
|
return is_equal
|
|
end
|
|
if #table ~= #other_equal_table then
|
|
return set_equal_tables(false)
|
|
end
|
|
local lookup_equal = (equal_tables[table] or {})[other_equal_table]
|
|
if lookup_equal ~= nil then
|
|
return lookup_equal
|
|
end
|
|
-- Premise
|
|
set_equal_tables(true)
|
|
local table_keys = {}
|
|
for key, value in pairs(table) do
|
|
local other_value = other_equal_table[key]
|
|
if not _equals(value, other_value) then
|
|
if type(key) == "table" then
|
|
table_keys[key] = value
|
|
else
|
|
return set_equal_tables(false)
|
|
end
|
|
end
|
|
end
|
|
for other_key, other_value in pairs(other_equal_table) do
|
|
if type(other_key) == "table" then
|
|
local found = false
|
|
for table_key, value in pairs(table_keys) do
|
|
if _equals(table_key, other_key) and _equals(value, other_value) then
|
|
table_keys[table_key] = nil
|
|
found = true
|
|
-- Breaking is fine as per transitivity
|
|
break
|
|
end
|
|
end
|
|
if not found then
|
|
return set_equal_tables(false)
|
|
end
|
|
else
|
|
if table[other_key] == nil then
|
|
return set_equal_tables(false)
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
return _equals(table, other_table)
|
|
end
|
|
|
|
--+ Table equality check: content has to be equal, relations between tables as well
|
|
--+ The only difference may be in the memory addresses ("identities") of the (sub)tables
|
|
--+ Performance may suffer if the tables contain table keys
|
|
--+ equals(table, copy(table)) is true
|
|
--> equality (same tables after table reference substitution) of circular tables if `table` and `other_table` are tables
|
|
--> `table == other_table` else
|
|
function equals_references(table, other_table)
|
|
local function _equals(table, other_table, equal_refs)
|
|
if equal_refs[table] then
|
|
return equal_refs[table] == other_table
|
|
end
|
|
local is_equal = table == other_table
|
|
-- this check could be omitted if table key equality is being checked
|
|
if type(table) ~= "table" or type(other_table) ~= "table" then
|
|
return is_equal
|
|
end
|
|
if is_equal then
|
|
equal_refs[table] = other_table
|
|
return true
|
|
end
|
|
-- Premise: table = other table
|
|
equal_refs[table] = other_table
|
|
local table_keys = {}
|
|
for key, value in pairs(table) do
|
|
if type(key) == "table" then
|
|
table_keys[key] = value
|
|
else
|
|
local other_value = other_table[key]
|
|
if not _equals(value, other_value, equal_refs) then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
local other_table_keys = {}
|
|
for other_key, other_value in pairs(other_table) do
|
|
if type(other_key) == "table" then
|
|
other_table_keys[other_key] = other_value
|
|
elseif table[other_key] == nil then
|
|
return false
|
|
end
|
|
end
|
|
local function _next(current_key, equal_refs, available_keys)
|
|
local key, value = next(table_keys, current_key)
|
|
if key == nil then
|
|
return true
|
|
end
|
|
for other_key, other_value in pairs(other_table_keys) do
|
|
local copy_equal_refs = shallowcopy(equal_refs)
|
|
if _equals(key, other_key, copy_equal_refs) and _equals(value, other_value, copy_equal_refs) then
|
|
local copy_available_keys = shallowcopy(available_keys)
|
|
copy_available_keys[other_key] = nil
|
|
if _next(key, copy_equal_refs, copy_available_keys) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
return _next(nil, equal_refs, other_table_keys)
|
|
end
|
|
return _equals(table, other_table, {})
|
|
end
|
|
|
|
-- Supports circular tables; does not support table keys
|
|
--> `true` if a mapping of references exists, `false` otherwise
|
|
function same(a, b)
|
|
local same = {}
|
|
local function is_same(a, b)
|
|
if type(a) ~= "table" or type(b) ~= "table" then
|
|
return a == b
|
|
end
|
|
if same[a] or same[b] then
|
|
return same[a] == b and same[b] == a
|
|
end
|
|
if a == b then
|
|
return true
|
|
end
|
|
|
|
same[a], same[b] = b, a
|
|
local count = 0
|
|
for k, v in pairs(a) do
|
|
count = count + 1
|
|
assert(type(k) ~= "table", "table keys not supported")
|
|
if not is_same(v, b[k], same) then
|
|
return false
|
|
end
|
|
end
|
|
for _ in pairs(b) do
|
|
count = count - 1
|
|
if count < 0 then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
return is_same(a, b)
|
|
end
|
|
|
|
function shallowcopy(
|
|
table -- table to copy
|
|
, strip_metatables -- whether to strip metatables; falsy by default; metatables are not copied
|
|
)
|
|
if type(table) ~= "table" then
|
|
return table
|
|
end
|
|
|
|
local copy = {}
|
|
if not strip_metatables then
|
|
setmetatable(copy, getmetatable(table))
|
|
end
|
|
for key, value in pairs(table) do
|
|
copy[key] = value
|
|
end
|
|
return copy
|
|
end
|
|
|
|
function deepcopy_tree(
|
|
table -- table; may not contain circular references; cross references will be copied multiple times
|
|
, strip_metatables -- whether to strip metatables; falsy by default; metatables are not copied
|
|
)
|
|
if type(table) ~= "table" then
|
|
return table
|
|
end
|
|
|
|
local copy = {}
|
|
if not strip_metatables then
|
|
setmetatable(copy, getmetatable(table))
|
|
end
|
|
for key, value in pairs(table) do
|
|
copy[deepcopy_tree(key)] = deepcopy_tree(value)
|
|
end
|
|
return copy
|
|
end
|
|
deepcopy_noncircular = deepcopy_tree
|
|
|
|
function deepcopy(
|
|
table -- table to copy; reference equality will be preserved
|
|
, strip_metatables -- whether to strip metatables; falsy by default; metatables are not copied
|
|
)
|
|
local copies = {}
|
|
local function _deepcopy(table)
|
|
local copy = copies[table]
|
|
if copy then
|
|
return copy
|
|
end
|
|
copy = {}
|
|
if not strip_metatables then
|
|
setmetatable(copy, getmetatable(table))
|
|
end
|
|
copies[table] = copy
|
|
local function _copy(value)
|
|
if type(value) ~= "table" then
|
|
return value
|
|
end
|
|
|
|
if copies[value] then
|
|
return copies[value]
|
|
end
|
|
|
|
return _deepcopy(value)
|
|
end
|
|
for key, value in pairs(table) do
|
|
copy[_copy(key)] = _copy(value)
|
|
end
|
|
return copy
|
|
end
|
|
return _deepcopy(table)
|
|
end
|
|
|
|
copy = deepcopy
|
|
|
|
function count(table)
|
|
local count = 0
|
|
for _ in pairs(table) do
|
|
count = count + 1
|
|
end
|
|
return count
|
|
end
|
|
|
|
function count_equals(table, count)
|
|
local k
|
|
for _ = 1, count do
|
|
k = next(table, k)
|
|
if k == nil then return false end -- less than n keys
|
|
end
|
|
return next(table, k) == nil -- no (n + 1)th entry
|
|
end
|
|
|
|
function is_empty(table)
|
|
return next(table) == nil
|
|
end
|
|
|
|
function clear(table)
|
|
for k in pairs(table) do
|
|
table[k] = nil
|
|
end
|
|
end
|
|
|
|
function foreach(table, func)
|
|
for k, v in pairs(table) do
|
|
func(k, v)
|
|
end
|
|
end
|
|
|
|
function deep_foreach_any(table, func)
|
|
local seen = {}
|
|
local function visit(value)
|
|
func(value)
|
|
if type(value) == "table" then
|
|
if seen[value] then return end
|
|
seen[value] = true
|
|
for k, v in pairs(value) do
|
|
visit(k)
|
|
visit(v)
|
|
end
|
|
end
|
|
end
|
|
visit(table)
|
|
end
|
|
|
|
-- Recursively counts occurences of objects (non-primitives including strings) in a table.
|
|
function count_objects(value)
|
|
local counts = {}
|
|
if value == nil then
|
|
-- Early return for nil
|
|
return counts
|
|
end
|
|
local function count_values(value)
|
|
local type_ = type(value)
|
|
if type_ == "boolean" or type_ == "number" then return end
|
|
local count = counts[value]
|
|
counts[value] = (count or 0) + 1
|
|
if not count and type_ == "table" then
|
|
for k, v in pairs(value) do
|
|
count_values(k)
|
|
count_values(v)
|
|
end
|
|
end
|
|
end
|
|
count_values(value)
|
|
return counts
|
|
end
|
|
|
|
function foreach_value(table, func)
|
|
for _, v in pairs(table) do
|
|
func(v)
|
|
end
|
|
end
|
|
|
|
function call(table, ...)
|
|
for _, func in pairs(table) do
|
|
func(...)
|
|
end
|
|
end
|
|
|
|
function icall(table, ...)
|
|
for _, func in ipairs(table) do
|
|
func(...)
|
|
end
|
|
end
|
|
|
|
function foreach_key(table, func)
|
|
for key, _ in pairs(table) do
|
|
func(key)
|
|
end
|
|
end
|
|
|
|
function map(table, func)
|
|
for key, value in pairs(table) do
|
|
table[key] = func(value)
|
|
end
|
|
return table
|
|
end
|
|
|
|
map_values = map
|
|
|
|
function map_keys(table, func)
|
|
local new_tab = {}
|
|
for key, value in pairs(table) do
|
|
new_tab[func(key)] = value
|
|
end
|
|
return new_tab
|
|
end
|
|
|
|
function process(tab, func)
|
|
local results = {}
|
|
for key, value in pairs(tab) do
|
|
table.insert(results, func(key, value))
|
|
end
|
|
return results
|
|
end
|
|
|
|
function call(funcs, ...)
|
|
for _, func in ipairs(funcs) do
|
|
func(...)
|
|
end
|
|
end
|
|
|
|
function find(list, value)
|
|
for index, other_value in pairs(list) do
|
|
if value == other_value then
|
|
return index
|
|
end
|
|
end
|
|
end
|
|
|
|
contains = find
|
|
|
|
function to_add(table, after_additions)
|
|
local additions = {}
|
|
for key, value in pairs(after_additions) do
|
|
if table[key] ~= value then
|
|
additions[key] = value
|
|
end
|
|
end
|
|
return additions
|
|
end
|
|
|
|
difference = to_add
|
|
|
|
function deep_to_add(table, after_additions)
|
|
local additions = {}
|
|
for key, value in pairs(after_additions) do
|
|
if type(table[key]) == "table" and type(value) == "table" then
|
|
local sub_additions = deep_to_add(table[key], value)
|
|
if next(sub_additions) ~= nil then
|
|
additions[key] = sub_additions
|
|
end
|
|
elseif table[key] ~= value then
|
|
additions[key] = value
|
|
end
|
|
end
|
|
return additions
|
|
end
|
|
|
|
function add_all(table, additions)
|
|
for key, value in pairs(additions) do
|
|
table[key] = value
|
|
end
|
|
return table
|
|
end
|
|
|
|
function deep_add_all(table, additions)
|
|
for key, value in pairs(additions) do
|
|
if type(table[key]) == "table" and type(value) == "table" then
|
|
deep_add_all(table[key], value)
|
|
else
|
|
table[key] = value
|
|
end
|
|
end
|
|
return table
|
|
end
|
|
|
|
function complete(table, completions)
|
|
for key, value in pairs(completions) do
|
|
if table[key] == nil then
|
|
table[key] = value
|
|
end
|
|
end
|
|
return table
|
|
end
|
|
|
|
function deepcomplete(table, completions)
|
|
for key, value in pairs(completions) do
|
|
if table[key] == nil then
|
|
table[key] = value
|
|
elseif type(table[key]) == "table" and type(value) == "table" then
|
|
deepcomplete(table[key], value)
|
|
end
|
|
end
|
|
return table
|
|
end
|
|
|
|
function merge(table, other_table, merge_func)
|
|
merge_func = merge_func or merge
|
|
local res = {}
|
|
for key, value in pairs(table) do
|
|
local other_value = other_table[key]
|
|
if other_value == nil then
|
|
res[key] = value
|
|
else
|
|
res[key] = merge_func(value, other_value)
|
|
end
|
|
end
|
|
for key, value in pairs(other_table) do
|
|
if table[key] == nil then
|
|
res[key] = value
|
|
end
|
|
end
|
|
return res
|
|
end
|
|
|
|
function merge_tables(table, other_table)
|
|
return add_all(shallowcopy(table), other_table)
|
|
end
|
|
|
|
union = merge_tables
|
|
|
|
function intersection(table, other_table)
|
|
local result = {}
|
|
for key, value in pairs(table) do
|
|
if other_table[key] then
|
|
result[key] = value
|
|
end
|
|
end
|
|
return result
|
|
end
|
|
|
|
function append(table, other_table)
|
|
local length = #table
|
|
for index, value in ipairs(other_table) do
|
|
table[length + index] = value
|
|
end
|
|
return table
|
|
end
|
|
|
|
function keys(table)
|
|
local keys = {}
|
|
for key, _ in pairs(table) do
|
|
keys[#keys + 1] = key
|
|
end
|
|
return keys
|
|
end
|
|
|
|
function values(table)
|
|
local values = {}
|
|
for _, value in pairs(table) do
|
|
values[#values + 1] = value
|
|
end
|
|
return values
|
|
end
|
|
|
|
function flip(table)
|
|
local flipped = {}
|
|
for key, value in pairs(table) do
|
|
flipped[value] = key
|
|
end
|
|
return flipped
|
|
end
|
|
|
|
function set(table)
|
|
local flipped = {}
|
|
for _, value in pairs(table) do
|
|
flipped[value] = true
|
|
end
|
|
return flipped
|
|
end
|
|
|
|
function unique(table)
|
|
return keys(set(table))
|
|
end
|
|
|
|
function ivalues(table)
|
|
local index = 0
|
|
return function()
|
|
index = index + 1
|
|
return table[index]
|
|
end
|
|
end
|
|
|
|
function rpairs(table)
|
|
local index = #table
|
|
return function()
|
|
if index >= 1 then
|
|
local value = table[index]
|
|
index = index - 1
|
|
if value ~= nil then
|
|
return index + 1, value
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Iterates the hash (= non-list) part of the table. The list part may not be modified while iterating.
|
|
function hpairs(table)
|
|
local len = #table -- length only has to be determined once as hnext is a closure
|
|
local function hnext(key)
|
|
local value
|
|
key, value = next(table, key)
|
|
if type(key) == "number" and key % 1 == 0 and key >= 1 and key <= len then -- list entry, skip
|
|
return hnext(key)
|
|
end
|
|
return key, value
|
|
end
|
|
return hnext
|
|
end
|
|
|
|
function min_key(table, less_than)
|
|
less_than = less_than or lt
|
|
local min_key = next(table)
|
|
if min_key == nil then
|
|
return -- empty table
|
|
end
|
|
for candidate_key in next, table, min_key do
|
|
if less_than(candidate_key, min_key) then
|
|
min_key = candidate_key
|
|
end
|
|
end
|
|
return min_key
|
|
end
|
|
|
|
function min_value(table, less_than)
|
|
less_than = less_than or lt
|
|
local min_key, min_value = next(table)
|
|
if min_key == nil then
|
|
return -- empty table
|
|
end
|
|
for candidate_key, candidate_value in next, table, min_key do
|
|
if less_than(candidate_value, min_value) then
|
|
min_key, min_value = candidate_key, candidate_value
|
|
end
|
|
end
|
|
return min_value, min_key
|
|
end
|
|
|
|
-- TODO move all of the below functions to modlib.list eventually
|
|
|
|
--! deprecated
|
|
function default_comparator(value, other_value)
|
|
if value == other_value then
|
|
return 0
|
|
end
|
|
if value > other_value then
|
|
return 1
|
|
end
|
|
return -1
|
|
end
|
|
|
|
--! deprecated, use `binary_search(list, value, less_than)` instead
|
|
--> index if element found
|
|
--> -index for insertion if not found
|
|
function binary_search_comparator(comparator)
|
|
return function(list, value)
|
|
local min, max = 1, #list
|
|
while min <= max do
|
|
local pivot = min + math.floor((max - min) / 2)
|
|
local element = list[pivot]
|
|
local compared = comparator(value, element)
|
|
if compared == 0 then
|
|
return pivot
|
|
elseif compared > 0 then
|
|
min = pivot + 1
|
|
else
|
|
max = pivot - 1
|
|
end
|
|
end
|
|
return -min
|
|
end
|
|
end
|
|
|
|
function binary_search(
|
|
list -- sorted list
|
|
, value -- value to be be searched for
|
|
, less_than -- function(a, b) return a < b end
|
|
)
|
|
less_than = less_than or lt
|
|
local min, max = 1, #list
|
|
while min <= max do
|
|
local mid = math.floor((min + max) / 2)
|
|
local element = list[mid]
|
|
if less_than(value, element) then
|
|
max = mid - 1
|
|
elseif less_than(element, value) then
|
|
min = mid + 1
|
|
else -- neither smaller nor larger => must be equal
|
|
return mid -- index if found
|
|
end
|
|
end
|
|
return nil, min -- nil, insertion index if not found
|
|
end
|
|
|
|
--> whether the list is sorted in ascending order
|
|
function is_sorted(list, less_than --[[function(a, b) return a < b end]])
|
|
less_than = less_than or function(a, b) return a < b end
|
|
for index = 2, #list do
|
|
if less_than(list[index], list[index - 1]) then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function reverse(table)
|
|
local len = #table
|
|
for index = 1, len / 2 do
|
|
local index_from_end = len + 1 - index
|
|
table[index_from_end], table[index] = table[index], table[index_from_end]
|
|
end
|
|
return table
|
|
end
|
|
|
|
function repetition(value, count)
|
|
local table = {}
|
|
for index = 1, count do
|
|
table[index] = value
|
|
end
|
|
return table
|
|
end
|
|
|
|
function slice(list, from, to)
|
|
from, to = from or 1, to or #list
|
|
local res = {}
|
|
for i = from, to do
|
|
res[#res + 1] = list[i]
|
|
end
|
|
return res
|
|
end
|
|
|
|
-- JS-ish array splice
|
|
function splice(
|
|
list, -- to modify
|
|
start, -- index (inclusive) for where to start modifying the array (defaults to after the last element)
|
|
delete_count, -- how many elements to remove (defaults to `0`)
|
|
... -- elements to insert after `start`
|
|
)
|
|
start, delete_count = start or (#list + 1), delete_count or 0
|
|
if start < 0 then
|
|
start = start + #list + 1
|
|
end
|
|
|
|
local add_count = select("#", ...)
|
|
local shift = add_count - delete_count
|
|
if shift > 0 then -- shift up
|
|
for i = #list, start + delete_count, -1 do
|
|
list[i + shift] = list[i]
|
|
end
|
|
elseif shift < 0 then -- shift down
|
|
for i = start, #list do
|
|
list[i] = list[i - shift]
|
|
end
|
|
end
|
|
|
|
-- Add elements
|
|
for i = 1, add_count do
|
|
list[start + i - 1] = select(i, ...)
|
|
end
|
|
|
|
return list
|
|
end
|
|
|
|
-- Equivalent to to_list[to], ..., to_list[to + count] = from_list[from], ..., from_list[from + count]
|
|
function move(from_list, from, to, count, to_list)
|
|
from, to, count, to_list = from or 1, to or 1, count or #from_list, to_list or from_list
|
|
if to_list ~= from_list or to < from then
|
|
for i = 0, count do
|
|
to_list[to + i] = from_list[from + i]
|
|
end
|
|
else -- iterate in reverse order
|
|
for i = count, 0, -1 do
|
|
to_list[to + i] = from_list[from + i]
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Export environment
|
|
return _ENV |