aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/iter.lua
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:40:31 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-29 22:40:31 +0000
commit339e2d15cc26fe86988ea06468d912a46c8d6f29 (patch)
treea6167fc8fcfc6ae2dc102f57b2473858eac34063 /runtime/lua/vim/iter.lua
parent067dc73729267c0262438a6fdd66e586f8496946 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.gz
rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.tar.bz2
rneovim-339e2d15cc26fe86988ea06468d912a46c8d6f29.zip
Merge remote-tracking branch 'upstream/master' into fix_repeatcmdline
Diffstat (limited to 'runtime/lua/vim/iter.lua')
-rw-r--r--runtime/lua/vim/iter.lua1013
1 files changed, 1013 insertions, 0 deletions
diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
new file mode 100644
index 0000000000..874bdfb437
--- /dev/null
+++ b/runtime/lua/vim/iter.lua
@@ -0,0 +1,1013 @@
+---@defgroup vim.iter
+---
+--- \*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()`:
+---
+--- - List tables (arrays, |lua-list|) yield only the value of each element.
+--- - 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" }
+--- ```
+---
+--- In addition to the |vim.iter()| function, the |vim.iter| module provides
+--- convenience functions like |vim.iter.filter()| and |vim.iter.totable()|.
+
+---@class IterMod
+---@operator call:Iter
+local M = {}
+
+---@class Iter
+local Iter = {}
+Iter.__index = Iter
+Iter.__call = function(self)
+ return self:next()
+end
+
+--- Special case implementations for iterators on list tables.
+---@class ListIter : 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 ListIter = {}
+ListIter.__index = setmetatable(ListIter, Iter)
+ListIter.__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
+ t.n = nil
+ end
+ return t
+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 function(...):bool 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(self, f)
+ return self:map(function(...)
+ if f(...) then
+ return ...
+ end
+ end)
+end
+
+---@private
+function ListIter.filter(self, 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
+
+--- 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 function(...):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(self, 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 ListIter.map(self, 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 function(...) 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(self, f)
+ local function fn(...)
+ if select(1, ...) ~= nil then
+ f(...)
+ return true
+ end
+ end
+ while fn(self:next()) do
+ end
+end
+
+---@private
+function ListIter.each(self, 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.
+--- List-like tables and function iterators will be collected into a list-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 a list-like table with consecutive, numeric indices.
+--- To create a map-like table with arbitrary keys, use |Iter:fold()|.
+---
+---
+---@return table
+function Iter.totable(self)
+ 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 ListIter.totable(self)
+ if self.next ~= ListIter.next or self._head >= self._tail then
+ return Iter.totable(self)
+ end
+
+ local needs_sanitize = getmetatable(self._table[1]) == 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
+
+--- Folds ("reduces") an iterator into a single value.
+---
+--- Examples:
+---
+--- ```lua
+--- -- Create a new table with only even values
+--- local t = { a = 1, b = 2, c = 3, d = 4 }
+--- local it = vim.iter(t)
+--- it:filter(function(k, v) return v % 2 == 0 end)
+--- it:fold({}, function(t, k, v)
+--- t[k] = v
+--- return t
+--- end)
+--- -- { b = 2, d = 4 }
+--- ```
+---
+---@generic A
+---
+---@param init A Initial value of the accumulator.
+---@param f function(acc:A, ...):A Accumulation function.
+---@return A
+function Iter.fold(self, 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 ListIter.fold(self, 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(self) -- luacheck: no unused args
+ -- This function is provided by the source iterator in Iter.new. This definition exists only for
+ -- the docstring
+end
+
+---@private
+function ListIter.next(self)
+ 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(self)
+ error('rev() requires a list-like table')
+ return self
+end
+
+---@private
+function ListIter.rev(self)
+ 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(self) -- luacheck: no unused args
+ error('peek() requires a list-like table')
+end
+
+---@private
+function ListIter.peek(self)
+ 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
+---
+--- ```
+---
+---@return any
+function Iter.find(self, 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 in a |list-iterator| that satisfies a predicate, starting from the end.
+---
+--- 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
+---
+---@return any
+function Iter.rfind(self, f) -- luacheck: no unused args
+ error('rfind() requires a list-like table')
+end
+
+---@private
+function ListIter.rfind(self, f) -- luacheck: no unused args
+ 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
+
+--- "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:nextback()
+--- -- 4
+--- it:nextback()
+--- -- 3
+--- ```
+---
+---@return any
+function Iter.nextback(self) -- luacheck: no unused args
+ error('nextback() requires a list-like table')
+end
+
+function ListIter.nextback(self)
+ 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.
+---
+--- See also |Iter:last()|.
+---
+--- Example:
+---
+--- ```lua
+--- local it = vim.iter({1, 2, 3, 4})
+--- it:peekback()
+--- -- 4
+--- it:peekback()
+--- -- 4
+--- it:nextback()
+--- -- 4
+--- ```
+---
+---@return any
+function Iter.peekback(self) -- luacheck: no unused args
+ error('peekback() requires a list-like table')
+end
+
+function ListIter.peekback(self)
+ 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(self, n)
+ for _ = 1, n do
+ local _ = self:next()
+ end
+ return self
+end
+
+---@private
+function ListIter.skip(self, 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
+
+--- Skips `n` values backwards from the end of a |list-iterator| pipeline.
+---
+--- Example:
+---
+--- ```lua
+--- local it = vim.iter({ 1, 2, 3, 4, 5 }):skipback(2)
+--- it:next()
+--- -- 1
+--- it:nextback()
+--- -- 3
+--- ```
+---
+---@param n number Number of values to skip.
+---@return Iter
+function Iter.skipback(self, n) -- luacheck: no unused args
+ error('skipback() requires a list-like table')
+ return self
+end
+
+---@private
+function ListIter.skipback(self, 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).
+---
+--- Example:
+---
+--- ```lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:nth(2)
+--- -- 6
+--- it:nth(2)
+--- -- 12
+---
+--- ```
+---
+---@param n number The index of the value to return.
+---@return any
+function Iter.nth(self, n)
+ if n > 0 then
+ return self:skip(n - 1):next()
+ end
+end
+
+--- Gets the nth value from the end of a |list-iterator| (and advances to it).
+---
+--- Example:
+---
+--- ```lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:nthback(2)
+--- -- 9
+--- it:nthback(2)
+--- -- 3
+---
+--- ```
+---
+---@param n number The index of the value to return.
+---@return any
+function Iter.nthback(self, n)
+ if n > 0 then
+ return self:skipback(n - 1):nextback()
+ end
+end
+
+--- Sets the start and end of a |list-iterator| pipeline.
+---
+--- Equivalent to `:skip(first - 1):skipback(len - last + 1)`.
+---
+---@param first number
+---@param last number
+---@return Iter
+function Iter.slice(self, first, last) -- luacheck: no unused args
+ error('slice() requires a list-like table')
+ return self
+end
+
+---@private
+function ListIter.slice(self, first, last)
+ return self:skip(math.max(0, first - 1)):skipback(math.max(0, self._tail - last - 1))
+end
+
+--- Returns true if any of the items in the iterator match the given predicate.
+---
+---@param pred function(...):bool 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(self, 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 function(...):bool 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(self, 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
+---
+--- ```
+---
+---@return any
+function Iter.last(self)
+ local last = self:next()
+ local cur = self:next()
+ while cur do
+ last = cur
+ cur = self:next()
+ end
+ return last
+end
+
+---@private
+function ListIter.last(self)
+ 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(self)
+ local i = 0
+ return self:map(function(...)
+ i = i + 1
+ return i, ...
+ end)
+end
+
+---@private
+function ListIter.enumerate(self)
+ 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 a list (consecutive integer indices 1…n).
+ local count = 0
+ for _ in pairs(src) do
+ count = count + 1
+ local v = src[count]
+ if v == nil then
+ return Iter.new(pairs(src))
+ end
+ t[count] = v
+ end
+ return ListIter.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 ListIter
+---
+---@param t table List-like table. Caller guarantees that this table is a valid list.
+---@return Iter
+---@private
+function ListIter.new(t)
+ local it = {}
+ it._table = t
+ it._head = 1
+ it._tail = #t + 1
+ setmetatable(it, ListIter)
+ return it
+end
+
+--- Collects an |iterable| into a table.
+---
+--- ```lua
+--- -- Equivalent to:
+--- vim.iter(f):totable()
+--- ```
+---
+---@param f function Iterator function
+---@return table
+function M.totable(f, ...)
+ return Iter.new(f, ...):totable()
+end
+
+--- Filters a table or other |iterable|.
+---
+--- ```lua
+--- -- Equivalent to:
+--- vim.iter(src):filter(f):totable()
+--- ```
+---
+---@see |Iter:filter()|
+---
+---@param f function(...):bool Filter function. Accepts the current iterator or table values as
+--- arguments and returns true if those values should be kept in the
+--- final table
+---@param src table|function Table or iterator function to filter
+---@return table
+function M.filter(f, src, ...)
+ return Iter.new(src, ...):filter(f):totable()
+end
+
+--- Maps a table or other |iterable|.
+---
+--- ```lua
+--- -- Equivalent to:
+--- vim.iter(src):map(f):totable()
+--- ```
+---
+---@see |Iter:map()|
+---
+---@param f function(...):?any Map function. Accepts the current iterator or table values as
+--- arguments and returns one or more new values. Nil values are removed
+--- from the final table.
+---@param src table|function Table or iterator function to filter
+---@return table
+function M.map(f, src, ...)
+ return Iter.new(src, ...):map(f):totable()
+end
+
+---@type IterMod
+return setmetatable(M, {
+ __call = function(_, ...)
+ return Iter.new(...)
+ end,
+})