1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-02-04 16:40:06 +08:00
SpaceVim/bundle/plenary.nvim/lua/plenary/collections/py_list.lua
2022-05-16 22:20:10 +08:00

394 lines
9.7 KiB
Lua
Vendored

---@brief [[
--- This module implements python-like lists. It can be used like so:
--- <pre>
--- local List = require 'plenary.collections.py_list'
--- local l = List{3, 20, 44}
--- print(l) -- [3, 20, 44]
--- </pre>
---@brief ]]
local List = {}
---@class List @The base class for all list objects
---List constructor. Can be used in higher order functions
---@param tbl table: A list-like table containing the initial elements of the list
---@return List: A new list object
function List.new(tbl)
if type(tbl) == "table" then
local len = #tbl
local obj = setmetatable(tbl, List)
obj._len = len
return obj
end
error "List constructor must be called with table argument"
end
--- Checks whether the argument is a List object
--- @param tbl table: The object to test
--- @return boolean: Whether tbl is an instance of List
function List.is_list(tbl)
local meta = getmetatable(tbl) or {}
return meta == List
end
function List:__index(key)
if self ~= List then
local field = List[key]
if field then
return field
end
end
end
-- TODO: Similar to python, use [...] if the table references itself --
function List:__tostring()
local elements = self:join ", "
return "[" .. elements .. "]"
end
function List:__eq(other)
if #self ~= #other then
return false
end
for i = 1, #self do
if self[i] ~= other[i] then
return false
end
end
return true
end
function List:__mul(other)
local result = List.new {}
for i = 1, other do
result[i] = self
end
return result
end
function List:__len()
return self._len
end
function List:__concat(other)
return self:concat(other)
end
--- Pushes the element to the end of the list
--- @param other any: The object to append
--- @see List.pop
function List:push(other)
self[#self + 1] = other
self._len = self._len + 1
end
--- Pops the last element off the list and returns it
--- @return any: The (previously) last element from the list
--- @see List.push
function List:pop()
local result = table.remove(self)
self._len = self._len - 1
return result
end
--- Inserts other into the specified idx
--- @param idx number: The index that other will be inserted to
--- @param other any: The element to insert
--- @see List.remove
function List:insert(idx, other)
table.insert(self, idx, other)
self._len = self._len + 1
end
--- Removes the element at index idx and returns it
--- @param idx number: The index of the element to remove
--- @return any: The element previously at index idx
--- @see List.insert
function List:remove(idx)
self._len = self._len - 1
return table.remove(self, idx)
end
--- Can be used to compare elements with any list-like table. It only checks for
--- shallow equality
--- @param other any: The element to test for
--- @return boolean: True if other is a list object and all it's elements are equal
--- @see List.deep_equal
function List:equal(other)
return self:__eq(other)
end
--- Checks for deep equality between lists. This uses vim.deep_equal for testing
--- @param other any: The element to test for
--- @return boolean: True if all elements and their children are equal
--- @see List.equal
--- @see vim.deep_equal
function List:deep_equal(other)
return vim.deep_equal(self, other)
end
--- Returns a copy of the list with elements between a and b, inclusive
--- <pre>
--- local list = List{1, 2, 3, 4}
--- local slice = list:slice(2, 3)
--- print(slice) -- [2, 3]
--- </pre>
--- @param a number: The low end of the slice
--- @param b number: The high end of the slice
--- @return List: A list with elements between a and b
function List:slice(a, b)
return List.new(vim.list_slice(self, a, b))
end
--- Similar to slice, but with every element. It only makes a shallow copy
--- @return List: A slice from 1 to #self, i.e., a complete copy of the list
--- @see List.deep_copy
function List:copy()
return self:slice(1, #self)
end
--- Similar to copy, but makes a deep copy instead
--- @return List: A deep copy of the object
--- @see List.copy
--- @see vim.deep_copy
function List:deep_copy()
return vim.deep_copy(self)
end
--- Reverses the list in place. If you don't want this, you could do something
--- like this
--- <pre>
--- local list = List{1, 2, 3, 4}
--- local reversed = list:copy():reverse()
--- </pre>
--- @return List: The list itself, so you can chain method calls
--- @see List.copy
--- @see List.deep_copy
function List:reverse()
local n = #self
local i = 1
while i < n do
self[i], self[n] = self[n], self[i]
i = i + 1
n = n - 1
end
return self
end
--- Concatenates the elements whithin the list separated by the given string
--- <pre>
--- local list = List{1, 2, 3, 4}
--- print(list:join('-')) -- 1-2-3-4
--- </pre>
--- @param sep string: The separator to place between the elements. Default ''
--- @return string: The elements in the list separated by sep
function List:join(sep)
sep = sep or ""
local result = ""
for i, v in self:iter() do
result = result .. tostring(v)
if i ~= #self then
result = result .. sep
end
end
return result
end
--- Returns a list with the elements of self concatenated with those in the
--- given arguments
--- @vararg table|List: The sequences to concatenate to this one
--- @return List
function List:concat(...)
local result = self:copy()
local others = { ... }
for _, other in ipairs(others) do
for _, v in ipairs(other) do
result:push(v)
end
end
return result
end
--- Moves the elements between from and from+len in self, to positions between
--- to and to+len in other, like so
--- <pre>
--- other[to], other[to+1]... other[to+len] = self[from], self[from+1]... self[from+len]
--- </pre>
--- @param from number: The first index of the origin slice
--- @param len number: The length of the slices
--- @param to number: The first index of the destination slice
--- @param other table|List: The destination list. Defaults to self
--- @see table.move
function List:move(from, len, to, other)
return table.move(self, from, len, to, other)
end
--- Packs the given elements into a list. Similar to lua 5.3's table.pack
--- @vararg any: The elements to pack
--- @return List: a list containing all the given elements
--- @see table.pack
function List.pack(...)
return List.new { ... }
end
--- Unpacks the elements from this list and returns them
--- @return ...any: All the elements from self[1] to self[#self]
function List:unpack()
return unpack(self, 1, #self)
end
-- Iterator stuff
local Iter = require "plenary.iterators"
local itermetatable = getmetatable(Iter:wrap())
local function forward_list_gen(param, state)
state = state + 1
local v = param[state]
if v ~= nil then
return state, v
end
end
local function backward_list_gen(param, state)
state = state - 1
local v = param[state]
if v ~= nil then
return state, v
end
end
--- Run the given predicate through all the elements pointed by this iterator,
--- and classify them into two lists. The first one holds the elements for which
--- predicate returned a truthy value, and the second holds the rest. For
--- example:
---
--- <pre>
--- local list = List{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
--- local evens, odds = list:iter():partition(function(e)
--- return e % 2 == 0
--- end)
--- print(evens, odds)
--- </pre>
---
--- Would print
---
--- <pre>
--- [0, 2, 4, 6, 8] [1, 3, 5, 7, 9]
--- </pre>
---@param predicate function: The predicate to classify the elements
---@return List,List
local function partition(self, predicate)
local list1, list2 = List.new {}, List.new {}
for _, v in self do
if predicate(v) then
list1:push(v)
else
list2:push(v)
end
end
return list1, list2
end
local function wrap_iter(f, l, n)
local iter = Iter.wrap(f, l, n)
iter.partition = partition
return iter
end
--- Counts the occurrences of e inside the list
--- @param e any: The element to test for
--- @return number: The number of occurrences of e
function List:count(e)
local count = 0
for _, v in self:iter() do
if e == v then
count = count + 1
end
end
return count
end
--- Appends the elements in the given iterator to the list
--- @param other table: An iterator object
function List:extend(other)
if type(other) == "table" and getmetatable(other) == itermetatable then
for _, v in other do
self:push(v)
end
else
error "Argument must be an iterator"
end
end
--- Checks whether there is an occurence of the given element in the list
--- @param e any: The object to test for
--- @return boolean: True if e is present
function List:contains(e)
for _, v in self:iter() do
if v == e then
return true
end
end
return false
end
--- Creates an iterator for the list. For example:
--- <pre>
--- local list = List{8, 4, 7, 9}
--- for i, v in list:iter() do
--- print(i, v)
--- end
--- </pre>
--- Would print:
--- <pre>
--- 1 8
--- 2 4
--- 3 7
--- 4 9
--- </pre>
--- @return table: An iterator object
function List:iter()
return wrap_iter(forward_list_gen, self, 0)
end
--- Creates a reverse iterator for the list. For example:
--- <pre>
--- local list = List{8, 4, 7, 9}
--- for i, v in list:riter() do
--- print(i, v)
--- end
--- </pre>
--- Would print:
--- <pre>
--- 4 9
--- 3 7
--- 2 4
--- 1 8
--- </pre>
--- @return table: An iterator object
function List:riter()
return wrap_iter(backward_list_gen, self, #self + 1)
end
-- Miscellaneous
--- Create a list from the elements pointed at by the given iterator.
--- @param iter table: An iterator object
--- @return List
function List.from_iter(iter)
local result = List.new {}
for _, v in iter do
result:push(v)
end
return result
end
return setmetatable({}, {
__call = function(_, tbl)
return List.new(tbl)
end,
__index = List,
})