mirror of
https://github.com/neovim/neovim.git
synced 2024-12-24 13:15:09 -07:00
1089 lines
26 KiB
Lua
1089 lines
26 KiB
Lua
--- @brief
|
|
---
|
|
--- [vim.iter()]() is an interface for [iterable]s: it wraps a table or function argument into an
|
|
--- [Iter]() object with methods (such as [Iter:filter()] and [Iter:map()]) that transform the
|
|
--- underlying source data. These methods can be chained to create iterator "pipelines": the output
|
|
--- of each pipeline stage is input to the next stage. The first stage depends on the type passed to
|
|
--- `vim.iter()`:
|
|
---
|
|
--- - Lists or arrays (|lua-list|) yield only the value of each element.
|
|
--- - Holes (nil values) are allowed (but discarded).
|
|
--- - Use pairs() to treat array/list tables as dicts (preserve holes and non-contiguous integer
|
|
--- keys): `vim.iter(pairs(…))`.
|
|
--- - Use |Iter:enumerate()| to also pass the index to the next stage.
|
|
--- - Or initialize with ipairs(): `vim.iter(ipairs(…))`.
|
|
--- - Non-list tables (|lua-dict|) yield both the key and value of each element.
|
|
--- - Function |iterator|s yield all values returned by the underlying function.
|
|
--- - Tables with a |__call()| metamethod are treated as function iterators.
|
|
---
|
|
--- The iterator pipeline terminates when the underlying |iterable| is exhausted (for function
|
|
--- iterators this means it returned nil).
|
|
---
|
|
--- Note: `vim.iter()` scans table input to decide if it is a list or a dict; to avoid this cost you
|
|
--- can wrap the table with an iterator e.g. `vim.iter(ipairs({…}))`, but that precludes the use of
|
|
--- |list-iterator| operations such as |Iter:rev()|).
|
|
---
|
|
--- Examples:
|
|
---
|
|
--- ```lua
|
|
--- local it = vim.iter({ 1, 2, 3, 4, 5 })
|
|
--- it:map(function(v)
|
|
--- return v * 3
|
|
--- end)
|
|
--- it:rev()
|
|
--- it:skip(2)
|
|
--- it:totable()
|
|
--- -- { 9, 6, 3 }
|
|
---
|
|
--- -- ipairs() is a function iterator which returns both the index (i) and the value (v)
|
|
--- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
|
|
--- if i > 2 then return v end
|
|
--- end):totable()
|
|
--- -- { 3, 4, 5 }
|
|
---
|
|
--- local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
|
|
--- it:map(function(s) return tonumber(s) end)
|
|
--- for i, d in it:enumerate() do
|
|
--- print(string.format("Column %d is %d", i, d))
|
|
--- end
|
|
--- -- Column 1 is 1
|
|
--- -- Column 2 is 2
|
|
--- -- Column 3 is 3
|
|
--- -- Column 4 is 4
|
|
--- -- Column 5 is 5
|
|
---
|
|
--- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
|
|
--- return k == 'z'
|
|
--- end)
|
|
--- -- true
|
|
---
|
|
--- local rb = vim.ringbuf(3)
|
|
--- rb:push("a")
|
|
--- rb:push("b")
|
|
--- vim.iter(rb):totable()
|
|
--- -- { "a", "b" }
|
|
--- ```
|
|
|
|
--- LuaLS is bad at generics which this module mostly deals with
|
|
--- @diagnostic disable:no-unknown
|
|
|
|
---@nodoc
|
|
---@class IterMod
|
|
---@operator call:Iter
|
|
|
|
local M = {}
|
|
|
|
---@nodoc
|
|
---@class Iter
|
|
local Iter = {}
|
|
Iter.__index = Iter
|
|
Iter.__call = function(self)
|
|
return self:next()
|
|
end
|
|
|
|
--- Special case implementations for iterators on list tables.
|
|
---@nodoc
|
|
---@class ArrayIter : Iter
|
|
---@field _table table Underlying table data
|
|
---@field _head number Index to the front of a table iterator
|
|
---@field _tail number Index to the end of a table iterator (exclusive)
|
|
local ArrayIter = {}
|
|
ArrayIter.__index = setmetatable(ArrayIter, Iter)
|
|
ArrayIter.__call = function(self)
|
|
return self:next()
|
|
end
|
|
|
|
--- Packed tables use this as their metatable
|
|
local packedmt = {}
|
|
|
|
local function unpack(t)
|
|
if type(t) == 'table' and getmetatable(t) == packedmt then
|
|
return _G.unpack(t, 1, t.n)
|
|
end
|
|
return t
|
|
end
|
|
|
|
local function pack(...)
|
|
local n = select('#', ...)
|
|
if n > 1 then
|
|
return setmetatable({ n = n, ... }, packedmt)
|
|
end
|
|
return ...
|
|
end
|
|
|
|
local function sanitize(t)
|
|
if type(t) == 'table' and getmetatable(t) == packedmt then
|
|
-- Remove length tag and metatable
|
|
t.n = nil
|
|
setmetatable(t, nil)
|
|
end
|
|
return t
|
|
end
|
|
|
|
--- Flattens a single array-like table. Errors if it attempts to flatten a
|
|
--- dict-like table
|
|
---@param t table table which should be flattened
|
|
---@param max_depth number depth to which the table should be flattened
|
|
---@param depth number current iteration depth
|
|
---@param result table output table that contains flattened result
|
|
---@return table|nil flattened table if it can be flattened, otherwise nil
|
|
local function flatten(t, max_depth, depth, result)
|
|
if depth < max_depth and type(t) == 'table' then
|
|
for k, v in pairs(t) do
|
|
if type(k) ~= 'number' or k <= 0 or math.floor(k) ~= k then
|
|
-- short-circuit: this is not a list like table
|
|
return nil
|
|
end
|
|
|
|
if flatten(v, max_depth, depth + 1, result) == nil then
|
|
return nil
|
|
end
|
|
end
|
|
elseif t ~= nil then
|
|
result[#result + 1] = t
|
|
end
|
|
|
|
return result
|
|
end
|
|
|
|
--- Determine if the current iterator stage should continue.
|
|
---
|
|
--- If any arguments are passed to this function, then return those arguments
|
|
--- and stop the current iterator stage. Otherwise, return true to signal that
|
|
--- the current stage should continue.
|
|
---
|
|
---@param ... any Function arguments.
|
|
---@return boolean True if the iterator stage should continue, false otherwise
|
|
---@return any Function arguments.
|
|
local function continue(...)
|
|
if select(1, ...) ~= nil then
|
|
return false, ...
|
|
end
|
|
return true
|
|
end
|
|
|
|
--- If no input arguments are given return false, indicating the current
|
|
--- iterator stage should stop. Otherwise, apply the arguments to the function
|
|
--- f. If that function returns no values, the current iterator stage continues.
|
|
--- Otherwise, those values are returned.
|
|
---
|
|
---@param f function Function to call with the given arguments
|
|
---@param ... any Arguments to apply to f
|
|
---@return boolean True if the iterator pipeline should continue, false otherwise
|
|
---@return any Return values of f
|
|
local function apply(f, ...)
|
|
if select(1, ...) ~= nil then
|
|
return continue(f(...))
|
|
end
|
|
return false
|
|
end
|
|
|
|
--- Filters an iterator pipeline.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
--- local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded)
|
|
--- ```
|
|
---
|
|
---@param f fun(...):boolean Takes all values returned from the previous stage
|
|
--- in the pipeline and returns false or nil if the
|
|
--- current iterator element should be removed.
|
|
---@return Iter
|
|
function Iter:filter(f)
|
|
return self:map(function(...)
|
|
if f(...) then
|
|
return ...
|
|
end
|
|
end)
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:filter(f)
|
|
local inc = self._head < self._tail and 1 or -1
|
|
local n = self._head
|
|
for i = self._head, self._tail - inc, inc do
|
|
local v = self._table[i]
|
|
if f(unpack(v)) then
|
|
self._table[n] = v
|
|
n = n + inc
|
|
end
|
|
end
|
|
self._tail = n
|
|
return self
|
|
end
|
|
|
|
--- Flattens a |list-iterator|, un-nesting nested values up to the given {depth}.
|
|
--- Errors if it attempts to flatten a dict-like value.
|
|
---
|
|
--- Examples:
|
|
---
|
|
--- ```lua
|
|
--- vim.iter({ 1, { 2 }, { { 3 } } }):flatten():totable()
|
|
--- -- { 1, 2, { 3 } }
|
|
---
|
|
--- vim.iter({1, { { a = 2 } }, { 3 } }):flatten():totable()
|
|
--- -- { 1, { a = 2 }, 3 }
|
|
---
|
|
--- vim.iter({ 1, { { a = 2 } }, { 3 } }):flatten(math.huge):totable()
|
|
--- -- error: attempt to flatten a dict-like table
|
|
--- ```
|
|
---
|
|
---@param depth? number Depth to which |list-iterator| should be flattened
|
|
--- (defaults to 1)
|
|
---@return Iter
|
|
---@diagnostic disable-next-line:unused-local
|
|
function Iter:flatten(depth) -- luacheck: no unused args
|
|
error('flatten() requires an array-like table')
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:flatten(depth)
|
|
depth = depth or 1
|
|
local inc = self._head < self._tail and 1 or -1
|
|
local target = {}
|
|
|
|
for i = self._head, self._tail - inc, inc do
|
|
local flattened = flatten(self._table[i], depth, 0, {})
|
|
|
|
-- exit early if we try to flatten a dict-like table
|
|
if flattened == nil then
|
|
error('flatten() requires an array-like table')
|
|
end
|
|
|
|
for _, v in pairs(flattened) do
|
|
target[#target + 1] = v
|
|
end
|
|
end
|
|
|
|
self._head = 1
|
|
self._tail = #target + 1
|
|
self._table = target
|
|
return self
|
|
end
|
|
|
|
--- Maps the items of an iterator pipeline to the values returned by `f`.
|
|
---
|
|
--- If the map function returns nil, the value is filtered from the iterator.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
--- local it = vim.iter({ 1, 2, 3, 4 }):map(function(v)
|
|
--- if v % 2 == 0 then
|
|
--- return v * 3
|
|
--- end
|
|
--- end)
|
|
--- it:totable()
|
|
--- -- { 6, 12 }
|
|
--- ```
|
|
---
|
|
---@param f fun(...):...:any Mapping function. Takes all values returned from
|
|
--- the previous stage in the pipeline as arguments
|
|
--- and returns one or more new values, which are used
|
|
--- in the next pipeline stage. Nil return values
|
|
--- are filtered from the output.
|
|
---@return Iter
|
|
function Iter:map(f)
|
|
-- Implementation note: the reader may be forgiven for observing that this
|
|
-- function appears excessively convoluted. The problem to solve is that each
|
|
-- stage of the iterator pipeline can return any number of values, and the
|
|
-- number of values could even change per iteration. And the return values
|
|
-- must be checked to determine if the pipeline has ended, so we cannot
|
|
-- naively forward them along to the next stage.
|
|
--
|
|
-- A simple approach is to pack all of the return values into a table, check
|
|
-- for nil, then unpack the table for the next stage. However, packing and
|
|
-- unpacking tables is quite slow. There is no other way in Lua to handle an
|
|
-- unknown number of function return values than to simply forward those
|
|
-- values along to another function. Hence the intricate function passing you
|
|
-- see here.
|
|
|
|
local next = self.next
|
|
|
|
--- Drain values from the upstream iterator source until a value can be
|
|
--- returned.
|
|
---
|
|
--- This is a recursive function. The base case is when the first argument is
|
|
--- false, which indicates that the rest of the arguments should be returned
|
|
--- as the values for the current iteration stage.
|
|
---
|
|
---@param cont boolean If true, the current iterator stage should continue to
|
|
--- pull values from its upstream pipeline stage.
|
|
--- Otherwise, this stage is complete and returns the
|
|
--- values passed.
|
|
---@param ... any Values to return if cont is false.
|
|
---@return any
|
|
local function fn(cont, ...)
|
|
if cont then
|
|
return fn(apply(f, next(self)))
|
|
end
|
|
return ...
|
|
end
|
|
|
|
self.next = function()
|
|
return fn(apply(f, next(self)))
|
|
end
|
|
return self
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:map(f)
|
|
local inc = self._head < self._tail and 1 or -1
|
|
local n = self._head
|
|
for i = self._head, self._tail - inc, inc do
|
|
local v = pack(f(unpack(self._table[i])))
|
|
if v ~= nil then
|
|
self._table[n] = v
|
|
n = n + inc
|
|
end
|
|
end
|
|
self._tail = n
|
|
return self
|
|
end
|
|
|
|
--- Calls a function once for each item in the pipeline, draining the iterator.
|
|
---
|
|
--- For functions with side effects. To modify the values in the iterator, use |Iter:map()|.
|
|
---
|
|
---@param f fun(...) Function to execute for each item in the pipeline.
|
|
--- Takes all of the values returned by the previous stage
|
|
--- in the pipeline as arguments.
|
|
function Iter:each(f)
|
|
local function fn(...)
|
|
if select(1, ...) ~= nil then
|
|
f(...)
|
|
return true
|
|
end
|
|
end
|
|
while fn(self:next()) do
|
|
end
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:each(f)
|
|
local inc = self._head < self._tail and 1 or -1
|
|
for i = self._head, self._tail - inc, inc do
|
|
f(unpack(self._table[i]))
|
|
end
|
|
self._head = self._tail
|
|
end
|
|
|
|
--- Collect the iterator into a table.
|
|
---
|
|
--- The resulting table depends on the initial source in the iterator pipeline.
|
|
--- Array-like tables and function iterators will be collected into an array-like
|
|
--- table. If multiple values are returned from the final stage in the iterator
|
|
--- pipeline, each value will be included in a table.
|
|
---
|
|
--- Examples:
|
|
---
|
|
--- ```lua
|
|
--- vim.iter(string.gmatch('100 20 50', '%d+')):map(tonumber):totable()
|
|
--- -- { 100, 20, 50 }
|
|
---
|
|
--- vim.iter({ 1, 2, 3 }):map(function(v) return v, 2 * v end):totable()
|
|
--- -- { { 1, 2 }, { 2, 4 }, { 3, 6 } }
|
|
---
|
|
--- vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable()
|
|
--- -- { { 'a', 1 }, { 'c', 3 } }
|
|
--- ```
|
|
---
|
|
--- The generated table is an array-like table with consecutive, numeric indices.
|
|
--- To create a map-like table with arbitrary keys, use |Iter:fold()|.
|
|
---
|
|
---
|
|
---@return table
|
|
function Iter:totable()
|
|
local t = {}
|
|
|
|
while true do
|
|
local args = pack(self:next())
|
|
if args == nil then
|
|
break
|
|
end
|
|
|
|
t[#t + 1] = sanitize(args)
|
|
end
|
|
return t
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:totable()
|
|
if self.next ~= ArrayIter.next or self._head >= self._tail then
|
|
return Iter.totable(self)
|
|
end
|
|
|
|
local needs_sanitize = getmetatable(self._table[self._head]) == packedmt
|
|
|
|
-- Reindex and sanitize.
|
|
local len = self._tail - self._head
|
|
|
|
if needs_sanitize then
|
|
for i = 1, len do
|
|
self._table[i] = sanitize(self._table[self._head - 1 + i])
|
|
end
|
|
else
|
|
for i = 1, len do
|
|
self._table[i] = self._table[self._head - 1 + i]
|
|
end
|
|
end
|
|
|
|
for i = len + 1, table.maxn(self._table) do
|
|
self._table[i] = nil
|
|
end
|
|
|
|
self._head = 1
|
|
self._tail = len + 1
|
|
|
|
return self._table
|
|
end
|
|
|
|
--- Collect the iterator into a delimited string.
|
|
---
|
|
--- Each element in the iterator is joined into a string separated by {delim}.
|
|
---
|
|
--- Consumes the iterator.
|
|
---
|
|
--- @param delim string Delimiter
|
|
--- @return string
|
|
function Iter:join(delim)
|
|
return table.concat(self:totable(), delim)
|
|
end
|
|
|
|
--- Folds ("reduces") an iterator into a single value. [Iter:reduce()]()
|
|
---
|
|
--- Examples:
|
|
---
|
|
--- ```lua
|
|
--- -- Create a new table with only even values
|
|
--- vim.iter({ a = 1, b = 2, c = 3, d = 4 })
|
|
--- :filter(function(k, v) return v % 2 == 0 end)
|
|
--- :fold({}, function(acc, k, v)
|
|
--- acc[k] = v
|
|
--- return acc
|
|
--- end) --> { b = 2, d = 4 }
|
|
---
|
|
--- -- Get the "maximum" item of an iterable.
|
|
--- vim.iter({ -99, -4, 3, 42, 0, 0, 7 })
|
|
--- :fold({}, function(acc, v)
|
|
--- acc.max = math.max(v, acc.max or v)
|
|
--- return acc
|
|
--- end) --> { max = 42 }
|
|
--- ```
|
|
---
|
|
---@generic A
|
|
---
|
|
---@param init A Initial value of the accumulator.
|
|
---@param f fun(acc:A, ...):A Accumulation function.
|
|
---@return A
|
|
function Iter:fold(init, f)
|
|
local acc = init
|
|
|
|
--- Use a closure to handle var args returned from iterator
|
|
local function fn(...)
|
|
if select(1, ...) ~= nil then
|
|
acc = f(acc, ...)
|
|
return true
|
|
end
|
|
end
|
|
|
|
while fn(self:next()) do
|
|
end
|
|
return acc
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:fold(init, f)
|
|
local acc = init
|
|
local inc = self._head < self._tail and 1 or -1
|
|
for i = self._head, self._tail - inc, inc do
|
|
acc = f(acc, unpack(self._table[i]))
|
|
end
|
|
return acc
|
|
end
|
|
|
|
--- Gets the next value from the iterator.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
---
|
|
--- local it = vim.iter(string.gmatch('1 2 3', '%d+')):map(tonumber)
|
|
--- it:next()
|
|
--- -- 1
|
|
--- it:next()
|
|
--- -- 2
|
|
--- it:next()
|
|
--- -- 3
|
|
---
|
|
--- ```
|
|
---
|
|
---@return any
|
|
function Iter:next()
|
|
-- This function is provided by the source iterator in Iter.new. This definition exists only for
|
|
-- the docstring
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:next()
|
|
if self._head ~= self._tail then
|
|
local v = self._table[self._head]
|
|
local inc = self._head < self._tail and 1 or -1
|
|
self._head = self._head + inc
|
|
return unpack(v)
|
|
end
|
|
end
|
|
|
|
--- Reverses a |list-iterator| pipeline.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
---
|
|
--- local it = vim.iter({ 3, 6, 9, 12 }):rev()
|
|
--- it:totable()
|
|
--- -- { 12, 9, 6, 3 }
|
|
---
|
|
--- ```
|
|
---
|
|
---@return Iter
|
|
function Iter:rev()
|
|
error('rev() requires an array-like table')
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:rev()
|
|
local inc = self._head < self._tail and 1 or -1
|
|
self._head, self._tail = self._tail - inc, self._head - inc
|
|
return self
|
|
end
|
|
|
|
--- Gets the next value in a |list-iterator| without consuming it.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
---
|
|
--- local it = vim.iter({ 3, 6, 9, 12 })
|
|
--- it:peek()
|
|
--- -- 3
|
|
--- it:peek()
|
|
--- -- 3
|
|
--- it:next()
|
|
--- -- 3
|
|
---
|
|
--- ```
|
|
---
|
|
---@return any
|
|
function Iter:peek()
|
|
error('peek() requires an array-like table')
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:peek()
|
|
if self._head ~= self._tail then
|
|
return self._table[self._head]
|
|
end
|
|
end
|
|
|
|
--- Find the first value in the iterator that satisfies the given predicate.
|
|
---
|
|
--- Advances the iterator. Returns nil and drains the iterator if no value is found.
|
|
---
|
|
--- Examples:
|
|
---
|
|
--- ```lua
|
|
---
|
|
--- local it = vim.iter({ 3, 6, 9, 12 })
|
|
--- it:find(12)
|
|
--- -- 12
|
|
---
|
|
--- local it = vim.iter({ 3, 6, 9, 12 })
|
|
--- it:find(20)
|
|
--- -- nil
|
|
---
|
|
--- local it = vim.iter({ 3, 6, 9, 12 })
|
|
--- it:find(function(v) return v % 4 == 0 end)
|
|
--- -- 12
|
|
---
|
|
--- ```
|
|
---@param f any
|
|
---@return any
|
|
function Iter:find(f)
|
|
if type(f) ~= 'function' then
|
|
local val = f
|
|
f = function(v)
|
|
return v == val
|
|
end
|
|
end
|
|
|
|
local result = nil
|
|
|
|
--- Use a closure to handle var args returned from iterator
|
|
local function fn(...)
|
|
if select(1, ...) ~= nil then
|
|
if f(...) then
|
|
result = pack(...)
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
while fn(self:next()) do
|
|
end
|
|
return unpack(result)
|
|
end
|
|
|
|
--- Gets the first value satisfying a predicate, from the end of a |list-iterator|.
|
|
---
|
|
--- Advances the iterator. Returns nil and drains the iterator if no value is found.
|
|
---
|
|
--- Examples:
|
|
---
|
|
--- ```lua
|
|
---
|
|
--- local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate()
|
|
--- it:rfind(1)
|
|
--- -- 5 1
|
|
--- it:rfind(1)
|
|
--- -- 1 1
|
|
---
|
|
--- ```
|
|
---
|
|
---@see Iter.find
|
|
---
|
|
---@param f any
|
|
---@return any
|
|
---@diagnostic disable-next-line: unused-local
|
|
function Iter:rfind(f) -- luacheck: no unused args
|
|
error('rfind() requires an array-like table')
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:rfind(f)
|
|
if type(f) ~= 'function' then
|
|
local val = f
|
|
f = function(v)
|
|
return v == val
|
|
end
|
|
end
|
|
|
|
local inc = self._head < self._tail and 1 or -1
|
|
for i = self._tail - inc, self._head, -inc do
|
|
local v = self._table[i]
|
|
if f(unpack(v)) then
|
|
self._tail = i
|
|
return unpack(v)
|
|
end
|
|
end
|
|
self._head = self._tail
|
|
end
|
|
|
|
--- Transforms an iterator to yield only the first n values.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
--- local it = vim.iter({ 1, 2, 3, 4 }):take(2)
|
|
--- it:next()
|
|
--- -- 1
|
|
--- it:next()
|
|
--- -- 2
|
|
--- it:next()
|
|
--- -- nil
|
|
--- ```
|
|
---
|
|
---@param n integer
|
|
---@return Iter
|
|
function Iter:take(n)
|
|
local next = self.next
|
|
local i = 0
|
|
self.next = function()
|
|
if i < n then
|
|
i = i + 1
|
|
return next(self)
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:take(n)
|
|
local inc = self._head < self._tail and n or -n
|
|
local cmp = self._head < self._tail and math.min or math.max
|
|
self._tail = cmp(self._tail, self._head + inc)
|
|
return self
|
|
end
|
|
|
|
--- "Pops" a value from a |list-iterator| (gets the last value and decrements the tail).
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
--- local it = vim.iter({1, 2, 3, 4})
|
|
--- it:pop()
|
|
--- -- 4
|
|
--- it:pop()
|
|
--- -- 3
|
|
--- ```
|
|
---
|
|
---@return any
|
|
function Iter:pop()
|
|
error('pop() requires an array-like table')
|
|
end
|
|
|
|
--- @nodoc
|
|
function ArrayIter:pop()
|
|
if self._head ~= self._tail then
|
|
local inc = self._head < self._tail and 1 or -1
|
|
self._tail = self._tail - inc
|
|
return self._table[self._tail]
|
|
end
|
|
end
|
|
|
|
--- Gets the last value of a |list-iterator| without consuming it.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
--- local it = vim.iter({1, 2, 3, 4})
|
|
--- it:rpeek()
|
|
--- -- 4
|
|
--- it:rpeek()
|
|
--- -- 4
|
|
--- it:pop()
|
|
--- -- 4
|
|
--- ```
|
|
---
|
|
---@see Iter.last
|
|
---
|
|
---@return any
|
|
function Iter:rpeek()
|
|
error('rpeek() requires an array-like table')
|
|
end
|
|
|
|
---@nodoc
|
|
function ArrayIter:rpeek()
|
|
if self._head ~= self._tail then
|
|
local inc = self._head < self._tail and 1 or -1
|
|
return self._table[self._tail - inc]
|
|
end
|
|
end
|
|
|
|
--- Skips `n` values of an iterator pipeline.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
---
|
|
--- local it = vim.iter({ 3, 6, 9, 12 }):skip(2)
|
|
--- it:next()
|
|
--- -- 9
|
|
---
|
|
--- ```
|
|
---
|
|
---@param n number Number of values to skip.
|
|
---@return Iter
|
|
function Iter:skip(n)
|
|
for _ = 1, n do
|
|
local _ = self:next()
|
|
end
|
|
return self
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:skip(n)
|
|
local inc = self._head < self._tail and n or -n
|
|
self._head = self._head + inc
|
|
if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then
|
|
self._head = self._tail
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Discards `n` values from the end of a |list-iterator| pipeline.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
--- local it = vim.iter({ 1, 2, 3, 4, 5 }):rskip(2)
|
|
--- it:next()
|
|
--- -- 1
|
|
--- it:pop()
|
|
--- -- 3
|
|
--- ```
|
|
---
|
|
---@param n number Number of values to skip.
|
|
---@return Iter
|
|
---@diagnostic disable-next-line: unused-local
|
|
function Iter:rskip(n) -- luacheck: no unused args
|
|
error('rskip() requires an array-like table')
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:rskip(n)
|
|
local inc = self._head < self._tail and n or -n
|
|
self._tail = self._tail - inc
|
|
if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then
|
|
self._head = self._tail
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Gets the nth value of an iterator (and advances to it).
|
|
---
|
|
--- If `n` is negative, offsets from the end of a |list-iterator|.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
--- local it = vim.iter({ 3, 6, 9, 12 })
|
|
--- it:nth(2)
|
|
--- -- 6
|
|
--- it:nth(2)
|
|
--- -- 12
|
|
---
|
|
--- local it2 = vim.iter({ 3, 6, 9, 12 })
|
|
--- it2:nth(-2)
|
|
--- -- 9
|
|
--- it2:nth(-2)
|
|
--- -- 3
|
|
--- ```
|
|
---
|
|
---@param n number Index of the value to return. May be negative if the source is a |list-iterator|.
|
|
---@return any
|
|
function Iter:nth(n)
|
|
if n > 0 then
|
|
return self:skip(n - 1):next()
|
|
elseif n < 0 then
|
|
return self:rskip(math.abs(n) - 1):pop()
|
|
end
|
|
end
|
|
|
|
--- Sets the start and end of a |list-iterator| pipeline.
|
|
---
|
|
--- Equivalent to `:skip(first - 1):rskip(len - last + 1)`.
|
|
---
|
|
---@param first number
|
|
---@param last number
|
|
---@return Iter
|
|
---@diagnostic disable-next-line: unused-local
|
|
function Iter:slice(first, last) -- luacheck: no unused args
|
|
error('slice() requires an array-like table')
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:slice(first, last)
|
|
return self:skip(math.max(0, first - 1)):rskip(math.max(0, self._tail - last - 1))
|
|
end
|
|
|
|
--- Returns true if any of the items in the iterator match the given predicate.
|
|
---
|
|
---@param pred fun(...):boolean Predicate function. Takes all values returned from the previous
|
|
--- stage in the pipeline as arguments and returns true if the
|
|
--- predicate matches.
|
|
function Iter:any(pred)
|
|
local any = false
|
|
|
|
--- Use a closure to handle var args returned from iterator
|
|
local function fn(...)
|
|
if select(1, ...) ~= nil then
|
|
if pred(...) then
|
|
any = true
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
while fn(self:next()) do
|
|
end
|
|
return any
|
|
end
|
|
|
|
--- Returns true if all items in the iterator match the given predicate.
|
|
---
|
|
---@param pred fun(...):boolean Predicate function. Takes all values returned from the previous
|
|
--- stage in the pipeline as arguments and returns true if the
|
|
--- predicate matches.
|
|
function Iter:all(pred)
|
|
local all = true
|
|
|
|
local function fn(...)
|
|
if select(1, ...) ~= nil then
|
|
if not pred(...) then
|
|
all = false
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
while fn(self:next()) do
|
|
end
|
|
return all
|
|
end
|
|
|
|
--- Drains the iterator and returns the last item.
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
---
|
|
--- local it = vim.iter(vim.gsplit('abcdefg', ''))
|
|
--- it:last()
|
|
--- -- 'g'
|
|
---
|
|
--- local it = vim.iter({ 3, 6, 9, 12, 15 })
|
|
--- it:last()
|
|
--- -- 15
|
|
---
|
|
--- ```
|
|
---
|
|
---@see Iter.rpeek
|
|
---
|
|
---@return any
|
|
function Iter:last()
|
|
local last = self:next()
|
|
local cur = self:next()
|
|
while cur do
|
|
last = cur
|
|
cur = self:next()
|
|
end
|
|
return last
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:last()
|
|
local inc = self._head < self._tail and 1 or -1
|
|
local v = self._table[self._tail - inc]
|
|
self._head = self._tail
|
|
return v
|
|
end
|
|
|
|
--- Yields the item index (count) and value for each item of an iterator pipeline.
|
|
---
|
|
--- For list tables, this is more efficient:
|
|
---
|
|
--- ```lua
|
|
--- vim.iter(ipairs(t))
|
|
--- ```
|
|
---
|
|
--- instead of:
|
|
---
|
|
--- ```lua
|
|
--- vim.iter(t):enumerate()
|
|
--- ```
|
|
---
|
|
--- Example:
|
|
---
|
|
--- ```lua
|
|
---
|
|
--- local it = vim.iter(vim.gsplit('abc', '')):enumerate()
|
|
--- it:next()
|
|
--- -- 1 'a'
|
|
--- it:next()
|
|
--- -- 2 'b'
|
|
--- it:next()
|
|
--- -- 3 'c'
|
|
---
|
|
--- ```
|
|
---
|
|
---@return Iter
|
|
function Iter:enumerate()
|
|
local i = 0
|
|
return self:map(function(...)
|
|
i = i + 1
|
|
return i, ...
|
|
end)
|
|
end
|
|
|
|
---@private
|
|
function ArrayIter:enumerate()
|
|
local inc = self._head < self._tail and 1 or -1
|
|
for i = self._head, self._tail - inc, inc do
|
|
local v = self._table[i]
|
|
self._table[i] = pack(i, v)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Creates a new Iter object from a table or other |iterable|.
|
|
---
|
|
---@param src table|function Table or iterator to drain values from
|
|
---@return Iter
|
|
---@private
|
|
function Iter.new(src, ...)
|
|
local it = {}
|
|
if type(src) == 'table' then
|
|
local mt = getmetatable(src)
|
|
if mt and type(mt.__call) == 'function' then
|
|
---@private
|
|
function it.next()
|
|
return src()
|
|
end
|
|
|
|
setmetatable(it, Iter)
|
|
return it
|
|
end
|
|
|
|
local t = {}
|
|
|
|
-- O(n): scan the source table to decide if it is an array (only positive integer indices).
|
|
for k, v in pairs(src) do
|
|
if type(k) ~= 'number' or k <= 0 or math.floor(k) ~= k then
|
|
return Iter.new(pairs(src))
|
|
end
|
|
t[#t + 1] = v -- Coerce to list-like table.
|
|
end
|
|
return ArrayIter.new(t)
|
|
end
|
|
|
|
if type(src) == 'function' then
|
|
local s, var = ...
|
|
|
|
--- Use a closure to handle var args returned from iterator
|
|
local function fn(...)
|
|
-- Per the Lua 5.1 reference manual, an iterator is complete when the first returned value is
|
|
-- nil (even if there are other, non-nil return values). See |for-in|.
|
|
if select(1, ...) ~= nil then
|
|
var = select(1, ...)
|
|
return ...
|
|
end
|
|
end
|
|
|
|
---@private
|
|
function it.next()
|
|
return fn(src(s, var))
|
|
end
|
|
|
|
setmetatable(it, Iter)
|
|
else
|
|
error('src must be a table or function')
|
|
end
|
|
return it
|
|
end
|
|
|
|
--- Create a new ArrayIter
|
|
---
|
|
---@param t table Array-like table. Caller guarantees that this table is a valid array. Can have
|
|
--- holes (nil values).
|
|
---@return Iter
|
|
---@private
|
|
function ArrayIter.new(t)
|
|
local it = {}
|
|
it._table = t
|
|
it._head = 1
|
|
it._tail = #t + 1
|
|
setmetatable(it, ArrayIter)
|
|
return it
|
|
end
|
|
|
|
return setmetatable(M, {
|
|
__call = function(_, ...)
|
|
return Iter.new(...)
|
|
end,
|
|
}) --[[@as IterMod]]
|