Sure. (584 lines).
--[=[
Version 1.0.0
This is intended for Roblox ModuleScripts
BSD 2-Clause Licence
Copyright ©, 2020 - Blockzez (devforum.roblox.com/u/Blockzez and github.com/Blockzez)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WlistANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WlistANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
]=]--
local tup = { };
local list = { };
local module = setmetatable({ }, { __index = { tuple = tup, list = list }, __newindex = function() error("Attempt to modify a readonly table", 2) end, __metatable = "The metatable is locked" });
-- A pointer so it won't throw an error for index being nil
local nullpointer = newproxy();
-- A placeholder pointer for getting the data
local getdata = newproxy();
local proxy = { };
local addr = { };
local hashes = { };
--[=[ Tuple ]=]--
local function nullifnil(value)
if value == nil then
return nullpointer;
end;
return value;
end;
local function compare_list(left, right)
for i = 0, math.min(#left, #right) - 1 do
if left[i] ~= right[i] then
return (left[i] > right[i]) and 1 or -1;
end;
end;
return (#left > #right) and 1 or ((#left < #right) and -1 or 0);
end;
-- Operators
-- <= and =>
local function le(left, right)
return compare_list(left, right) <= 0;
end;
-- < and >
local function lt(left, right)
return compare_list(left, right) < 0;
end;
-- == (This is NOT rawequal)
local function eq(left, right)
if #left ~= #right then
return false;
end;
for i = 0, #left - 1 do
if left[i] ~= right[i] then
return false;
end;
end;
return true;
end;
-- + and ..
local function concat_tup(left, right)
if not (proxy[left] and proxy[right]) then
error("attempt to concatenate " .. typeof(right) .. " with " .. typeof(right), 2);
end;
local ret = { };
for _, v in ipairs(proxy[left].data) do
table.insert(ret, v);
end;
for _, v in ipairs(proxy[right].data) do
table.insert(ret, v);
end;
return tup.fromList(ret);
end;
-- *
local function mul_tup(left, right)
if proxy[left] and proxy[right] then
error("attempt to perform arithmetic (mul) on userdata", 2);
elseif type(left) ~= "number" and type(right) ~= "number" then
error("attempt to perform arithmetic (mul) on " .. typeof(left) .. " and " .. typeof(right), 2);
end;
local rep, tuple;
if type(right) == "number" then
rep = right;
tuple = proxy[left].data;
else
rep = left;
tuple = proxy[right].data;
end;
local ret = { };
for _ = 1, rep do
for _, v in ipairs(tuple) do
table.insert(ret, v);
end;
end;
return tup.fromList(ret);
end;
-- Other
local function len(self)
return #proxy[self].data;
end;
local function tostr_tup(self)
return table.concat(proxy[self].data, ', ');
end;
-- Index
local function getattr_tup(self, ind)
if type(ind) == "number" then
if ind >= #self or ind < -#self then
error("tuple index out of range", 2);
end;
if ind < 0 then
return proxy[self].data[(#self + ind) + 1];
end;
return proxy[self].data[ind + 1];
elseif type(ind) ~= "string" then
return;
end;
if ind:match('Item[1-9]%d*') then
return proxy[self].data[tonumber(ind:sub(5))];
end;
return (ind ~= "new" and ind ~= "fromList") and tup[ind];
end;
-- Constuctors
function tup.new(...)
return tup.fromList { ... };
end;
function tup.fromList(list)
if type(list) ~= "table" and not proxy[list] then
error("bad argument #1 (table expected, got" .. typeof(list) .. ")", 2);
end;
local position = hashes;
local list_copy = { };
if type(list) == "table" then
local i = 1;
for ind, val in next, list do
-- Check
if ind ~= i then
error("bad argument #1 (array expected, got dictionary)", 2);
end;
if not position[nullifnil(val)] then
position[nullifnil(val)] = { };
end;
position = position[nullifnil(val)];
list_copy[ind] = val;
i = i + 1;
end;
elseif proxy[list].list then
for _, val in list do
if not position[nullifnil(val)] then
position[nullifnil(val)] = { };
end;
position = position[nullifnil(val)];
table.insert(list_copy, val);
end;
else
return list;
end;
if position[getdata] then
return position[getdata];
end;
local pointer = newproxy(true);
position[getdata] = pointer;
proxy[pointer] = { list = false, data = list_copy };
addr[pointer] = tostring(pointer):sub(11);
local pointer_metatable = getmetatable(pointer);
pointer_metatable.__metatable = "The metatable is locked";
pointer_metatable.__index = getattr_tup;
-- Operators
pointer_metatable.__le = le;
pointer_metatable.__lt = lt;
pointer_metatable.__eq = eq;
pointer_metatable.__add = concat_tup;
pointer_metatable.__concat = concat_tup;
pointer_metatable.__mul = mul_tup;
-- Iterator
local ind = 0;
pointer_metatable.__call = function()
ind = ind + 1;
if ind > #proxy[pointer].data then
ind = 0;
return;
end;
return ind - 1, proxy[pointer].data[ind];
end;
-- Others
pointer_metatable.__len = len;
pointer_metatable.__tostring = tostr_tup;
return pointer;
end;
-- Methods
setmetatable(tup, { __newindex = function(self, ind, func) rawset(self, ind,
function(self1, ...) if (not proxy[self1]) then error(self == module and ("bad argument #1 (tuple expected, got " .. typeof(self) .. ')')
or ("Expected ':' not '.' calling member function " .. ind), 2); end; return func(self1, ...) end); end; });
function tup:unpack()
return unpack(proxy[self].data);
end;
function tup:filter(func, self_arg)
if type(func) ~= "function" then
error("bad argument #2 (function expected, got " .. typeof(func) .. ')', 2);
end;
local ret = { };
for _, val in ipairs(proxy[self].data) do
if self_arg and func(self, val) or func(val) then
table.insert(ret, val);
end;
end;
return tup.fromList(ret);
end;
function tup:index(val)
return table.find(proxy[self].data, val);
end;
function tup:count(val, self_arg)
if self_arg ~= nil then
if type(val) ~= "function" then
error("bad argument #2 (function expected, got " .. typeof(val) .. ')', 2);
end;
end;
local ret = 0;
for _, val1 in ipairs(proxy[self].data) do
if self_arg ~= nil then
if self_arg and val(self, val1) or val(val1) then
ret = ret + 1;
end;
elseif val == val1 then
ret = ret + 1;
end;
end;
return ret;
end;
function tup:pop(ind)
if type(ind) ~= "number" then
error("bad argument #2 (integer expected, got " .. typeof(ind) .. ')', 2);
elseif ind % 1 ~= 0 then
error("bad argument #2 (integer expected, got float)", 2);
end;
local ret = { };
ind = ind or -1;
if ind < 0 then
ind = #self + ind;
end;
for k, v in ipairs(proxy[self].data) do
if k - 1 ~= ind then
table.insert(ret, v);
end;
end;
return tup.fromList(ret);
end;
--[=[ Lists ]=]--
-- + and ..
local function concat_list(left, right)
if not (proxy[left] and proxy[right]) then
error("attempt to concatenate " .. typeof(right) .. " with " .. typeof(right), 2);
end;
local ret = { };
for _, v in ipairs(proxy[left].data) do
table.insert(ret, v);
end;
for _, v in ipairs(proxy[right].data) do
table.insert(ret, v);
end;
return list.new(ret);
end;
-- *
local function mul_list(left, right)
if proxy[left] and proxy[right] then
error("attempt to perform arithmetic (mul) on userdata", 2);
elseif type(left) ~= "number" and type(right) ~= "number" then
error("attempt to perform arithmetic (mul) on " .. typeof(left) .. " and " .. typeof(right), 2);
end;
local _list = list;
local rep, list;
if type(right) == "number" then
rep = right;
list = proxy[left].data;
else
rep = left;
list = proxy[right].data;
end;
local ret = { };
for _ = 1, rep do
for _, v in ipairs(list) do
table.insert(ret, v);
end;
end;
return _list.new(ret);
end;
-- Other
function tostr_list(self)
return "list: " .. addr[self];
end;
-- Index
local function getattr_list(self, ind)
if type(ind) == "number" then
if ind >= #self or ind < -#self then
error("list index out of range", 2);
end;
if ind < 0 then
return proxy[self].data[(#self + ind) + 1];
end;
return proxy[self].data[ind + 1];
elseif type(ind) ~= "string" then
return;
end;
return (ind ~= "new" and ind ~= "pack") and list[ind];
end;
local function setattr_list(self, ind, val)
if type(ind) ~= "number" then
if type(ind) ~= "string" then
error("attempt to index userdata with '" .. typeof(ind) .. "'", 2);
end;
error("attempt to index userdata with '" .. ind .. "'", 2);
end;
if ind >= #self or ind < -#self then
error("list index out of range", 2);
end;
if ind < 0 then
proxy[self].data[(#self + ind) + 1] = val;
end;
proxy[self].data[ind + 1] = val;
end;
-- Constructor
function list.new(arr)
if type(arr) ~= "table" and not proxy[arr] then
error("bad argument #1 (table expected, got" .. typeof(list) .. ")", 2);
end;
local arr_copy = { };
if type(list) == "table" then
local i = 1;
for ind, val in next, arr do
-- Check
if ind ~= i then
error("bad argument #1 (array expected, got dictionary)", 2);
end;
arr_copy[ind] = val;
i = i + 1;
end;
elseif proxy[arr].list then
return arr;
else
for _, val in arr do
table.insert(arr_copy, val);
end;
end;
local pointer = newproxy(true);
proxy[pointer] = { list = true, data = arr_copy };
addr[pointer] = tostring(pointer):sub(11);
local pointer_metatable = getmetatable(pointer);
pointer_metatable.__metatable = "The metatable is locked";
pointer_metatable.__index = getattr_list;
pointer_metatable.__newindex = setattr_list;
-- Operators
pointer_metatable.__le = le;
pointer_metatable.__lt = lt;
pointer_metatable.__eq = eq;
pointer_metatable.__add = concat_list;
pointer_metatable.__concat = concat_list;
pointer_metatable.__mul = mul_list;
-- Iterator
local ind = 0;
pointer_metatable.__call = function()
ind = ind + 1;
if ind > #proxy[pointer].data then
ind = 0;
return;
end;
return ind - 1, proxy[pointer].data[ind];
end;
-- Other
pointer_metatable.__len = len;
pointer_metatable.__tostring = tostr_list;
return pointer;
end;
function list.pack(...)
return list.new { ... };
end;
-- Methods
setmetatable(list, { __newindex = function(self, ind, func) rawset(self, ind,
function(self1, ...) if (not proxy[self1]) then error(self == module and ("bad argument #1 (list expected, got " .. typeof(self) .. ')')
or ("Expected ':' not '.' calling member function " .. ind), 2); end; return func(self1, ...) end); end; });
function list:append(value)
table.insert(proxy[self].data, value);
return self;
end;
function list:insert(ind, value)
if type(ind) ~= "number" then
error("bad argument #1 (integer expected, got" .. typeof(ind) .. ")", 2);
elseif ind % 1 ~= 0 then
error("bad argument #1 (integer expected, got float)", 2);
end;
if ind < 0 then
table.insert(proxy[self].data, math.max((#self + ind) + 1, 1), value);
else
table.insert(proxy[self].data, math.min(ind + 1, #self), value);
end;
end;
function list:pop(ind)
if type(ind) ~= "number" then
error("bad argument #1 (integer expected, got" .. typeof(ind) .. ")", 2);
elseif ind % 1 ~= 0 then
error("bad argument #1 (integer expected, got float)", 2);
end;
if ind < 0 then
return table.remove(proxy[self].data, math.min((#self + ind) + 1, 1));
end;
return table.remove(proxy[self].data, math.max(ind + 1, #self));
end;
function list:unpack()
return unpack(proxy[self].data);
end;
function list:filter(func, self_arg)
if type(func) ~= "function" then
error("bad argument #2 (function expected, got " .. typeof(func) .. ')', 2);
end;
local ret = { };
for _, val in ipairs(proxy[self].data) do
if self_arg and func(self, val) or func(val) then
table.insert(ret, val);
end;
end;
return list.new(ret);
end;
function list:index(val)
return table.find(proxy[self].data, val);
end;
function list:count(val, self_arg)
if self_arg ~= nil then
if type(val) ~= "function" then
error("bad argument #2 (function expected, got " .. typeof(val) .. ')', 2);
end;
end;
local ret = 0;
for _, val1 in ipairs(proxy[self].data) do
if self_arg ~= nil then
if self_arg and val(self, val1) or val(val1) then
ret = ret + 1;
end;
elseif val == val1 then
ret = ret + 1;
end;
end;
return ret;
end;
function list:sort(comp)
return list.new(table.sort(proxy[self].data, comp));
end;
function list:copy()
return list.new(table.move(proxy[self].data, 1, #self, 1, table.create(#self)));
end;
function list:reverse()
if #self > 1 then
for i = 0, math.floor(#self / 2) - 1 do
self[i], self[-(i + 1)] = self[-(i + 1)], self[i];
end;
end;
return self;
end;
function list:clear()
for _ = 1, #self do
table.remove(proxy[self].data);
end;
return self;
end;
function list:join(sep)
return table.concat(proxy[self].data, sep or ',');
end;
function list:splice(i, c, ...)
if type(i) ~= "number" then
error("bad argument #1 (integer expected, got" .. typeof(i) .. ")", 2);
elseif i % 1 ~= 0 then
error("bad argument #1 (integer expected, got float)", 2);
end;
if type(c) ~= "number" then
error("bad argument #2 (integer expected, got" .. typeof(c) .. ")", 2);
elseif c % 1 ~= 0 then
error("bad argument #2 (integer expected, got float)", 2);
end;
local args = { ... };
if i < 0 then
i = math.max((#self + i) + 1, 1);
else
i = math.min(i + 1, #self);
end;
for _ = 1, c do
table.remove(proxy[self].data, c);
end;
for ind = #args, 1, -1 do
table.insert(proxy[self].data, i, args[ind]);
end;
return self;
end;
function list:formatenglish()
if #self == 1 then
return self[0];
end;
return table.concat(proxy[self].data, ', ', 1, -2) .. ' and ' .. self[-1];
end;
return module;