From ab1edecfb7c73c82c2d5886cb8e270b44aca7d01 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Mon, 17 Apr 2023 12:54:19 -0600 Subject: feat(lua): add vim.iter (#23029) vim.iter wraps a table or iterator function into an `Iter` object with methods such as `filter`, `map`, and `fold` which can be chained to produce iterator pipelines that do not create new tables at each step. --- runtime/lua/vim/iter.lua | 836 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 836 insertions(+) create mode 100644 runtime/lua/vim/iter.lua (limited to 'runtime/lua/vim/iter.lua') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua new file mode 100644 index 0000000000..b73c03ba9a --- /dev/null +++ b/runtime/lua/vim/iter.lua @@ -0,0 +1,836 @@ +--- Iterator implementation. + +---@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 (table iterators only) +---@field _head number Index to the front of a table iterator (table iterators only) +---@field _tail number Index to the end of a table iterator (table iterators only) +local ListIter = {} +ListIter.__index = setmetatable(ListIter, Iter) +ListIter.__call = function(self) + return self:next() +end + +--- Special case implementations for iterators on non-list tables. +---@class TableIter : Iter +local TableIter = {} +TableIter.__index = setmetatable(TableIter, Iter) +TableIter.__call = function(self) + return self:next() +end + +---@private +local function unpack(t) + if type(t) == 'table' then + return _G.unpack(t) + end + return t +end + +---@private +local function pack(...) + if select('#', ...) > 1 then + return { ... } + end + return ... +end + +--- Add a filter step to the 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) + ---@private + local function fn(...) + local result = nil + if select(1, ...) ~= nil then + if not f(...) then + return true, nil + else + result = pack(...) + end + end + return false, result + end + + local next = self.next + self.next = function(this) + local cont, result + repeat + cont, result = fn(next(this)) + until not cont + return unpack(result) + end + return self +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 + +--- Add a map step to the iterator pipeline. +--- +--- 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 returned +--- are filtered from the output. +---@return Iter +function Iter.map(self, f) + ---@private + local function fn(...) + local result = nil + if select(1, ...) ~= nil then + result = pack(f(...)) + if result == nil then + return true, nil + end + end + return false, result + end + + local next = self.next + self.next = function(this) + local cont, result + repeat + cont, result = fn(next(this)) + until not cont + return unpack(result) + 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 + +--- Call a function once for each item in the pipeline. +--- +--- This is used for functions which have side effects. To modify the values in the iterator, use +--- |Iter:map()|. +--- +--- This function drains the iterator. +--- +---@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) + ---@private + 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. If a +--- map-like table was used as the initial source, then a map-like table is returned. +--- +--- 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 }
+--- 
+--- +---@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] = args + end + return t +end + +---@private +function ListIter.totable(self) + if self._head == 1 and self._tail == #self._table + 1 and self.next == ListIter.next then + return self._table + end + + return Iter.totable(self) +end + +---@private +function TableIter.totable(self) + local t = {} + for k, v in self do + t[k] = v + end + return t +end + +--- Fold an iterator or table into a single value. +--- +---@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 + ---@private + 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 + +--- Return 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 + +--- Reverse an iterator. +--- +--- Only supported for iterators on list-like tables. +--- +--- 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 + +--- Peek at the next value in the iterator without consuming it. +--- +--- Only supported for iterators on list-like tables. +--- +--- 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 + ---@private + 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 + +--- Find the first value in the iterator that satisfies the given predicate, starting from the end. +--- +--- Advances the iterator. Returns nil and drains the iterator if no value is found. +--- +--- Only supported for iterators on list-like tables. +--- +--- 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 + +--- Return the next value from the end of the iterator. +--- +--- Only supported for iterators on list-like tables. +--- +--- 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 + +--- Return the next value from the end of the iterator without consuming it. +--- +--- Only supported for iterators on list-like tables. +--- +--- 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 + +--- Skip values in the iterator. +--- +--- 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 + +--- Skip values in the iterator starting from the end. +--- +--- Only supported for iterators on list-like tables. +--- +--- 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 + +--- Return the nth value in the iterator. +--- +--- This function advances the iterator. +--- +--- 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 + +--- Return the nth value from the end of the iterator. +--- +--- This function advances the iterator. +--- +--- Only supported for iterators on list-like tables. +--- +--- 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 + +--- Slice an iterator, changing its start and end positions. +--- +--- This is equivalent to :skip(first - 1):skipback(len - last + 1) +--- +--- Only supported for iterators on list-like tables. +--- +---@param first number +---@param last number +---@return Iter +function Iter.slice(self, first, last) -- luacheck: no unused args + return self:skip(math.max(0, first - 1)):skipback(math.max(0, self._tail - last - 1)) +end + +--- Return 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 + ---@private + 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 + +--- Return true if all 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.all(self, pred) + local all = true + + ---@private + 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 + +--- Return the last item in the iterator. +--- +--- Drains the iterator. +--- +--- 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 + +--- Add an iterator stage that returns the current iterator count as well as the iterator value. +--- +--- For list tables, prefer +---
lua
+--- vim.iter(ipairs(t))
+--- 
+--- +--- over +--- +---
lua
+--- vim.iter(t):enumerate()
+--- 
+--- +--- as the former is faster. +--- +--- 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] = { i, v } + end + return self +end + +--- Create a new Iter object from a table or iterator. +--- +---@param src table|function Table or iterator to drain values from +---@return Iter +function Iter.new(src, ...) + local it = {} + if type(src) == 'table' then + local t = {} + + -- Check if source table can be treated like a list (indices are consecutive integers + -- starting from 1) + local count = 0 + for _ in pairs(src) do + count = count + 1 + local v = src[count] + if v == nil then + return TableIter.new(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 + ---@private + local function fn(...) + if select(1, ...) ~= nil then + var = select(1, ...) + return ... + end + end + + 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 + +--- Create a new TableIter +--- +---@param t table Table to iterate over. For list-like tables, use ListIter.new instead. +---@return Iter +---@private +function TableIter.new(t) + local it = {} + + local index = nil + function it.next() + local k, v = next(t, index) + if k ~= nil then + index = k + return k, v + end + end + + setmetatable(it, TableIter) + return it +end + +return Iter -- cgit From 6b96122453fda22dc44a581af1d536988c1adf41 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Wed, 19 Apr 2023 06:45:56 -0600 Subject: fix(iter): add tag to packed table If pack() is called with a single value, it does not create a table; it simply returns the value it is passed. When unpack is called with a table argument, it interprets that table as a list of values that were packed together into a table. This causes a problem when the single value being packed is _itself_ a table. pack() will not place it into another table, but unpack() sees the table argument and tries to unpack it. To fix this, we add a simple "tag" to packed table values so that unpack() only attempts to unpack tables that have this tag. Other tables are left alone. The tag is simply the length of the table. --- runtime/lua/vim/iter.lua | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim/iter.lua') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index b73c03ba9a..fff7644b6a 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -28,16 +28,17 @@ end ---@private local function unpack(t) - if type(t) == 'table' then - return _G.unpack(t) + if type(t) == 'table' and t.__n ~= nil then + return _G.unpack(t, 1, t.__n) end return t end ---@private local function pack(...) - if select('#', ...) > 1 then - return { ... } + local n = select('#', ...) + if n > 1 then + return { __n = n, ... } end return ... end @@ -210,6 +211,12 @@ function Iter.totable(self) if args == nil then break end + + if type(args) == 'table' then + -- Removed packed table tag if it exists + args.__n = nil + end + t[#t + 1] = args end return t @@ -218,6 +225,14 @@ end ---@private function ListIter.totable(self) if self._head == 1 and self._tail == #self._table + 1 and self.next == ListIter.next then + -- Remove any packed table tags + for i = 1, #self._table do + local v = self._table[i] + if type(v) == 'table' then + v.__n = nil + self._table[i] = v + end + end return self._table end @@ -747,7 +762,7 @@ 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] = { i, v } + self._table[i] = pack(i, v) end return self end -- cgit From 94894068794dbb99804cda689b6c37e70376c8ca Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Wed, 19 Apr 2023 07:05:04 -0600 Subject: fix(iter): remove special case totable for map-like tables This was originally meant as a convenience but prevents possible functionality. For example: -- Get the keys of the table with even values local t = { a = 1, b = 2, c = 3, d = 4 } vim.iter(t):map(function(k, v) if v % 2 == 0 then return k end end):totable() The example above would not work, because the map() function returns only a single value, and cannot be converted back into a table (there are many such examples like this). Instead, to convert an iterator into a map-like table, users can use fold(): vim.iter(t):fold({}, function(t, k, v) t[k] = v return t end) --- runtime/lua/vim/iter.lua | 67 +++++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 44 deletions(-) (limited to 'runtime/lua/vim/iter.lua') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index fff7644b6a..c5d5ef835b 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -18,14 +18,6 @@ ListIter.__call = function(self) return self:next() end ---- Special case implementations for iterators on non-list tables. ----@class TableIter : Iter -local TableIter = {} -TableIter.__index = setmetatable(TableIter, Iter) -TableIter.__call = function(self) - return self:next() -end - ---@private local function unpack(t) if type(t) == 'table' and t.__n ~= nil then @@ -185,10 +177,10 @@ 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. If a ---- map-like table was used as the initial source, then a map-like table is returned. +--- 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
@@ -199,9 +191,13 @@ end
 --- -- { { 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 }
+--- -- { { '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 = {} @@ -239,17 +235,21 @@ function ListIter.totable(self) return Iter.totable(self) end ----@private -function TableIter.totable(self) - local t = {} - for k, v in self do - t[k] = v - end - return t -end - --- Fold an iterator or table into a single value. --- +--- Examples: +---
+--- -- 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. @@ -783,7 +783,7 @@ function Iter.new(src, ...) count = count + 1 local v = src[count] if v == nil then - return TableIter.new(src) + return Iter.new(pairs(src)) end t[count] = v end @@ -827,25 +827,4 @@ function ListIter.new(t) return it end ---- Create a new TableIter ---- ----@param t table Table to iterate over. For list-like tables, use ListIter.new instead. ----@return Iter ----@private -function TableIter.new(t) - local it = {} - - local index = nil - function it.next() - local k, v = next(t, index) - if k ~= nil then - index = k - return k, v - end - end - - setmetatable(it, TableIter) - return it -end - return Iter -- cgit From f68af3c3bc92c12f7dbbd32f44df8ab57a58ac98 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Fri, 21 Apr 2023 16:13:39 -0600 Subject: refactor(iter): use metatable as packed table tag (#23254) This is a more robust method for tagging a packed table as it completely eliminates the possibility of mistaking an actual table key as the packed table tag. --- runtime/lua/vim/iter.lua | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) (limited to 'runtime/lua/vim/iter.lua') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index c5d5ef835b..2545853b41 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -18,10 +18,13 @@ ListIter.__call = function(self) return self:next() end +--- Packed tables use this as their metatable +local packedmt = {} + ---@private local function unpack(t) - if type(t) == 'table' and t.__n ~= nil then - return _G.unpack(t, 1, t.__n) + if getmetatable(t) == packedmt then + return _G.unpack(t, 1, t.n) end return t end @@ -30,11 +33,20 @@ end local function pack(...) local n = select('#', ...) if n > 1 then - return { __n = n, ... } + return setmetatable({ n = n, ... }, packedmt) end return ... end +---@private +local function sanitize(t) + if getmetatable(t) == packedmt then + -- Remove length tag + t.n = nil + end + return t +end + --- Add a filter step to the iterator pipeline. --- --- Example: @@ -208,12 +220,7 @@ function Iter.totable(self) break end - if type(args) == 'table' then - -- Removed packed table tag if it exists - args.__n = nil - end - - t[#t + 1] = args + t[#t + 1] = sanitize(args) end return t end @@ -221,12 +228,10 @@ end ---@private function ListIter.totable(self) if self._head == 1 and self._tail == #self._table + 1 and self.next == ListIter.next then - -- Remove any packed table tags - for i = 1, #self._table do - local v = self._table[i] - if type(v) == 'table' then - v.__n = nil - self._table[i] = v + -- Sanitize packed table values + if getmetatable(self._table[1]) == packedmt then + for i = 1, #self._table do + self._table[i] = sanitize(self._table[i]) end end return self._table -- cgit From 1e73891d696a00b046ab19d245001424b174c931 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Mon, 24 Apr 2023 19:57:40 -0600 Subject: refactor(iter): move helper functions under vim.iter vim.iter is now both a function and a module (similar to vim.version). --- runtime/lua/vim/iter.lua | 112 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim/iter.lua') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 2545853b41..c2e2c5bd9f 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -1,4 +1,56 @@ ---- Iterator implementation. +---@defgroup lua-iter +--- +--- The \*vim.iter\* module provides a generic "iterator" interface over tables and iterator +--- functions. +--- +--- \*vim.iter()\* wraps its 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 together to create iterator "pipelines". Each pipeline stage receives as input +--- the output values from the prior stage. The values used in the first stage of the pipeline +--- depend on the type passed to this function: +--- +--- - List tables pass only the value of each element +--- - Non-list tables pass both the key and value of each element +--- - Function iterators pass all of the values returned by their respective +--- function +--- +--- 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 }
+---
+---   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
+--- 
+--- +--- In addition to the |vim.iter()| function, the |vim.iter| module provides convenience functions +--- like |vim.iter.filter()| and |vim.iter.totable()|. + +local M = {} ---@class Iter local Iter = {} @@ -733,7 +785,6 @@ end --- --- --- over ---- ---
lua
 --- vim.iter(t):enumerate()
 --- 
@@ -776,6 +827,7 @@ end --- ---@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 @@ -807,6 +859,7 @@ function Iter.new(src, ...) end end + ---@private function it.next() return fn(src(s, var)) end @@ -832,4 +885,57 @@ function ListIter.new(t) return it end -return Iter +--- Collect an iterator into a table. +--- +--- This is a convenience function that performs: +---
lua
+--- vim.iter(f):totable()
+--- 
+--- +---@param f function Iterator function +---@return table +function M.totable(f, ...) + return Iter.new(f, ...):totable() +end + +--- Filter a table or iterator. +--- +--- This is a convenience function that performs: +---
lua
+--- 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 + +--- Map and filter a table or iterator. +--- +--- This is a convenience function that performs: +---
lua
+--- 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 + +return setmetatable(M, { + __call = function(_, ...) + return Iter.new(...) + end, +}) -- cgit From ef1801cc7c3d8fe9fd8524a3b677095d4437fc66 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Fri, 28 Apr 2023 11:37:53 -0600 Subject: perf(iter): reduce number of table allocations Packing and unpacking return values impairs performance considerably. In an attempt to avoid creating tables as much as possible we can instead pass return values between functions (which does not require knowing the number of values a function might return). This makes the code more complex, but improves benchmark numbers non-trivially. --- runtime/lua/vim/iter.lua | 164 +++++++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 62 deletions(-) (limited to 'runtime/lua/vim/iter.lua') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index c2e2c5bd9f..bda3508262 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -1,13 +1,14 @@ ---@defgroup lua-iter --- ---- The \*vim.iter\* module provides a generic "iterator" interface over tables and iterator ---- functions. +--- The \*vim.iter\* module provides a generic "iterator" interface over tables +--- and iterator functions. --- ---- \*vim.iter()\* wraps its 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 together to create iterator "pipelines". Each pipeline stage receives as input ---- the output values from the prior stage. The values used in the first stage of the pipeline ---- depend on the type passed to this function: +--- \*vim.iter()\* wraps its 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 together to create +--- iterator "pipelines". Each pipeline stage receives as input the output +--- values from the prior stage. The values used in the first stage of the +--- pipeline depend on the type passed to this function: --- --- - List tables pass only the value of each element --- - Non-list tables pass both the key and value of each element @@ -47,8 +48,8 @@ --- -- true --- --- ---- In addition to the |vim.iter()| function, the |vim.iter| module provides convenience functions ---- like |vim.iter.filter()| and |vim.iter.totable()|. +--- In addition to the |vim.iter()| function, the |vim.iter| module provides +--- convenience functions like |vim.iter.filter()| and |vim.iter.totable()|. local M = {} @@ -61,9 +62,9 @@ end --- Special case implementations for iterators on list tables. ---@class ListIter : Iter ----@field _table table Underlying table data (table iterators only) ----@field _head number Index to the front of a table iterator (table iterators only) ----@field _tail number Index to the end of a table iterator (table iterators only) +---@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 local ListIter = {} ListIter.__index = setmetatable(ListIter, Iter) ListIter.__call = function(self) @@ -75,7 +76,7 @@ local packedmt = {} ---@private local function unpack(t) - if getmetatable(t) == packedmt then + if type(t) == 'table' and getmetatable(t) == packedmt then return _G.unpack(t, 1, t.n) end return t @@ -92,13 +93,47 @@ end ---@private local function sanitize(t) - if getmetatable(t) == packedmt then + 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. +---@private +local function continue(...) + if select('#', ...) > 0 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 +---@private +local function apply(f, ...) + if select('#', ...) > 0 then + return continue(f(...)) + end + return false +end + --- Add a filter step to the iterator pipeline. --- --- Example: @@ -106,33 +141,16 @@ end --- 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. +---@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) - ---@private - local function fn(...) - local result = nil - if select(1, ...) ~= nil then - if not f(...) then - return true, nil - else - result = pack(...) - end + return self:map(function(...) + if f(...) then + return ... end - return false, result - end - - local next = self.next - self.next = function(this) - local cont, result - repeat - cont, result = fn(next(this)) - until not cont - return unpack(result) - end - return self + end) end ---@private @@ -165,31 +183,52 @@ end --- -- { 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 returned ---- are filtered from the output. +---@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 ---@private - local function fn(...) - local result = nil - if select(1, ...) ~= nil then - result = pack(f(...)) - if result == nil then - return true, nil - end + local function fn(cont, ...) + if cont then + return fn(apply(f, next(self))) end - return false, result + return ... end - local next = self.next - self.next = function(this) - local cont, result - repeat - cont, result = fn(next(this)) - until not cont - return unpack(result) + self.next = function() + return fn(apply(f, next(self))) end return self end @@ -211,17 +250,18 @@ end --- Call a function once for each item in the pipeline. --- ---- This is used for functions which have side effects. To modify the values in the iterator, use ---- |Iter:map()|. +--- This is used for functions which have side effects. To modify the values in +--- the iterator, use |Iter:map()|. --- --- This function drains the iterator. --- ----@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. +---@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) ---@private local function fn(...) - if select(1, ...) ~= nil then + if select('#', ...) > 0 then f(...) return true end -- cgit From aa130d0c7ea69a05330d0b054b414cc3a15dac45 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Fri, 2 Jun 2023 16:59:58 +0200 Subject: docs: small fixes (#23619) Co-authored-by: Evgeni Chasnovski Co-authored-by: Gustavo Ferreira Co-authored-by: Kai Moschcau Co-authored-by: Lampros --- runtime/lua/vim/iter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/iter.lua') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index bda3508262..9112b6c3bb 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -335,7 +335,7 @@ end --- Fold an iterator or table 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)
-- 
cgit 


From c65e2203f70cd5d66fcb8ffb26f8cef38f50e04f Mon Sep 17 00:00:00 2001
From: Sebastian Lyng Johansen 
Date: Sat, 3 Jun 2023 09:18:05 +0200
Subject: docs(iter): add emmylua type to iter module (#23845)

---
 runtime/lua/vim/iter.lua | 3 +++
 1 file changed, 3 insertions(+)

(limited to 'runtime/lua/vim/iter.lua')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 9112b6c3bb..56012e9b9f 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -51,6 +51,8 @@
 --- 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
@@ -974,6 +976,7 @@ function M.map(f, src, ...)
   return Iter.new(src, ...):map(f):totable()
 end
 
+---@type IterMod
 return setmetatable(M, {
   __call = function(_, ...)
     return Iter.new(...)
-- 
cgit 


From 40db569014471deb5bd17860be00d6833387be79 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Sat, 3 Jun 2023 11:06:10 +0100
Subject: perf(iter): make ListIter.totable more efficient (#23714)

---
 runtime/lua/vim/iter.lua | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)

(limited to 'runtime/lua/vim/iter.lua')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 56012e9b9f..0e98d0437e 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -66,7 +66,7 @@ end
 ---@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
+---@field _tail number Index to the end of a table iterator (exclusive)
 local ListIter = {}
 ListIter.__index = setmetatable(ListIter, Iter)
 ListIter.__call = function(self)
@@ -321,17 +321,33 @@ end
 
 ---@private
 function ListIter.totable(self)
-  if self._head == 1 and self._tail == #self._table + 1 and self.next == ListIter.next then
-    -- Sanitize packed table values
-    if getmetatable(self._table[1]) == packedmt then
-      for i = 1, #self._table do
-        self._table[i] = sanitize(self._table[i])
-      end
+  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
-    return self._table
   end
 
-  return Iter.totable(self)
+  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
 
 --- Fold an iterator or table into a single value.
-- 
cgit 


From 302d3cfb96d7f0c856262e1a4252d058e3300c8b Mon Sep 17 00:00:00 2001
From: Mathias Fußenegger 
Date: Sat, 10 Jun 2023 20:33:23 +0200
Subject: feat(lua): use callable table as iterator in vim.iter (#23957)

A table passed to `vim.iter` can be a class instance with a `__call`
implementation for the iterator protocol.
---
 runtime/lua/vim/iter.lua | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

(limited to 'runtime/lua/vim/iter.lua')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 0e98d0437e..204d22b9be 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -14,6 +14,8 @@
 --- - Non-list tables pass both the key and value of each element
 --- - Function iterators pass all of the values returned by their respective
 ---   function
+--- - Tables with a metatable implementing __call are treated as function
+---   iterators
 ---
 --- Examples:
 --- 
lua
@@ -46,6 +48,12 @@
 ---     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 @@ -889,6 +897,17 @@ end 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 = {} -- Check if source table can be treated like a list (indices are consecutive integers -- cgit From cee981bf09c81ab4b2fe6facf45076ea4bac46a5 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 19 Jun 2023 02:24:44 -0700 Subject: docs #22363 Co-authored by: zeertzjq Co-authored by: Steven Todd McIntyre II <114119064+stmii@users.noreply.github.com> Co-authored by: nobe4 - docs: mention --luadev-mod to run with lua runtime files When changing a lua file in the ./runtime folder, a new contributor might expect changes to be applied to the built Neovim binary. --- runtime/lua/vim/iter.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim/iter.lua') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 204d22b9be..9c7bd13164 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -1,7 +1,8 @@ ---@defgroup lua-iter --- ---- The \*vim.iter\* module provides a generic "iterator" interface over tables ---- and iterator functions. +--- @brief The \*vim.iter\* module provides a generic interface for working with +--- iterables: tables, lists, iterator functions, pair()/ipair()-like iterators, +--- and \`vim.iter()\` objects. --- --- \*vim.iter()\* wraps its table or function argument into an \*Iter\* object --- with methods (such as |Iter:filter()| and |Iter:map()|) that transform the -- cgit From 036da0d07921e67090d1a62c9a4e382ca09d8584 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 24 Jun 2023 13:47:10 +0200 Subject: fix(docs): vimdoc syntax errors gen_help_html: truncate parse-error sample text --- runtime/lua/vim/iter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim/iter.lua') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 9c7bd13164..245a33625e 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -359,7 +359,7 @@ function ListIter.totable(self) return self._table end ---- Fold an iterator or table into a single value. +--- Fold ("reduce") an iterator or table into a single value. --- --- Examples: ---
lua
-- 
cgit 


From c2d7c2826ca77b0ca31bec511fdcdf1e4abaf946 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Mon, 17 Jul 2023 15:13:54 +0100
Subject: docs(lua): change *lua-foo* -> *vim.foo*

---
 runtime/lua/vim/iter.lua | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'runtime/lua/vim/iter.lua')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 245a33625e..6c1afcad91 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -1,6 +1,6 @@
----@defgroup lua-iter
+---@defgroup vim.iter
 ---
---- @brief The \*vim.iter\* module provides a generic interface for working with
+--- This module provides a generic interface for working with
 --- iterables: tables, lists, iterator functions, pair()/ipair()-like iterators,
 --- and \`vim.iter()\` objects.
 ---
-- 
cgit 


From be74807eef13ff8c90d55cf8b22b01d6d33b1641 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Tue, 18 Jul 2023 15:42:30 +0100
Subject: docs(lua): more improvements (#24387)

* docs(lua): teach lua2dox how to table

* docs(lua): teach gen_vimdoc.py about local functions

No more need to mark local functions with @private

* docs(lua): mention @nodoc and @meta in dev-lua-doc

* fixup!

Co-authored-by: Justin M. Keyes 

---------

Co-authored-by: Justin M. Keyes 
---
 runtime/lua/vim/iter.lua | 12 ------------
 1 file changed, 12 deletions(-)

(limited to 'runtime/lua/vim/iter.lua')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 6c1afcad91..7bffcc9c20 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -85,7 +85,6 @@ end
 --- Packed tables use this as their metatable
 local packedmt = {}
 
----@private
 local function unpack(t)
   if type(t) == 'table' and getmetatable(t) == packedmt then
     return _G.unpack(t, 1, t.n)
@@ -93,7 +92,6 @@ local function unpack(t)
   return t
 end
 
----@private
 local function pack(...)
   local n = select('#', ...)
   if n > 1 then
@@ -102,7 +100,6 @@ local function pack(...)
   return ...
 end
 
----@private
 local function sanitize(t)
   if type(t) == 'table' and getmetatable(t) == packedmt then
     -- Remove length tag
@@ -120,7 +117,6 @@ end
 ---@param ... any Function arguments.
 ---@return boolean True if the iterator stage should continue, false otherwise
 ---@return any Function arguments.
----@private
 local function continue(...)
   if select('#', ...) > 0 then
     return false, ...
@@ -137,7 +133,6 @@ end
 ---@param ... any Arguments to apply to f
 ---@return boolean True if the iterator pipeline should continue, false otherwise
 ---@return any Return values of f
----@private
 local function apply(f, ...)
   if select('#', ...) > 0 then
     return continue(f(...))
@@ -230,7 +225,6 @@ function Iter.map(self, f)
   ---                    values passed.
   ---@param ... any Values to return if cont is false.
   ---@return any
-  ---@private
   local function fn(cont, ...)
     if cont then
       return fn(apply(f, next(self)))
@@ -270,7 +264,6 @@ end
 ---                       Takes all of the values returned by the previous stage
 ---                       in the pipeline as arguments.
 function Iter.each(self, f)
-  ---@private
   local function fn(...)
     if select('#', ...) > 0 then
       f(...)
@@ -383,7 +376,6 @@ function Iter.fold(self, init, f)
   local acc = init
 
   --- Use a closure to handle var args returned from iterator
-  ---@private
   local function fn(...)
     if select(1, ...) ~= nil then
       acc = f(acc, ...)
@@ -525,7 +517,6 @@ function Iter.find(self, f)
   local result = nil
 
   --- Use a closure to handle var args returned from iterator
-  ---@private
   local function fn(...)
     if select(1, ...) ~= nil then
       if f(...) then
@@ -768,7 +759,6 @@ function Iter.any(self, pred)
   local any = false
 
   --- Use a closure to handle var args returned from iterator
-  ---@private
   local function fn(...)
     if select(1, ...) ~= nil then
       if pred(...) then
@@ -792,7 +782,6 @@ end
 function Iter.all(self, pred)
   local all = true
 
-  ---@private
   local function fn(...)
     if select(1, ...) ~= nil then
       if not pred(...) then
@@ -929,7 +918,6 @@ function Iter.new(src, ...)
     local s, var = ...
 
     --- Use a closure to handle var args returned from iterator
-    ---@private
     local function fn(...)
       if select(1, ...) ~= nil then
         var = select(1, ...)
-- 
cgit 


From d2f81330247ee060d557330b2716ccea8f789a50 Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Wed, 12 Jul 2023 19:27:14 +0200
Subject: docs: misc

Co-authored-by: Kevin Pham 
---
 runtime/lua/vim/iter.lua | 23 ++++++++---------------
 1 file changed, 8 insertions(+), 15 deletions(-)

(limited to 'runtime/lua/vim/iter.lua')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 7bffcc9c20..a278e96a5f 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -1,22 +1,15 @@
 ---@defgroup vim.iter
 ---
---- This module provides a generic interface for working with
---- iterables: tables, lists, iterator functions, pair()/ipair()-like iterators,
---- and \`vim.iter()\` objects.
----
---- \*vim.iter()\* wraps its 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 together to create
---- iterator "pipelines". Each pipeline stage receives as input the output
---- values from the prior stage. The values used in the first stage of the
---- pipeline depend on the type passed to this function:
+--- \*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 together to create iterator "pipelines".
+--- Each pipeline stage receives as input the output values from the prior stage. The values used in
+--- the first stage of the pipeline depend on the type passed to this function:
 ---
 --- - List tables pass only the value of each element
---- - Non-list tables pass both the key and value of each element
---- - Function iterators pass all of the values returned by their respective
----   function
---- - Tables with a metatable implementing __call are treated as function
----   iterators
+--- - Non-list (dict) tables pass both the key and value of each element
+--- - Function |iterator|s pass all of the values returned by their respective function
+--- - Tables with a metatable implementing |__call()| are treated as function iterators
 ---
 --- Examples:
 --- 
lua
-- 
cgit 


From 2ee8ace217b8e4405822d3ab1bed5a20bedc4b04 Mon Sep 17 00:00:00 2001
From: Gregory Anders <8965202+gpanders@users.noreply.github.com>
Date: Wed, 9 Aug 2023 15:41:45 -0500
Subject: fix(iter): make pipeline termination conditions consistent (#24614)

If an iterator pipeline stage returns nil as its first return value, the
other return values are ignored and it is treated as if that stage
returned only nil (the semantics of returning nil are different between
different stages). This is consistent with how for loops work in Lua
more generally, where the for loop breaks when the first return value
from the function iterator is nil (see :h for-in for details).
---
 runtime/lua/vim/iter.lua | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

(limited to 'runtime/lua/vim/iter.lua')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index a278e96a5f..56c130dd0c 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -6,11 +6,14 @@
 --- Each pipeline stage receives as input the output values from the prior stage. The values used in
 --- the first stage of the pipeline depend on the type passed to this function:
 ---
---- - List tables pass only the value of each element
---- - Non-list (dict) tables pass both the key and value of each element
+--- - List tables (arrays) pass only the value of each element
+--- - Non-list tables (dictionaries) pass both the key and value of each element
 --- - Function |iterator|s pass all of the values returned by their respective function
 --- - Tables with a metatable implementing |__call()| are treated as function iterators
 ---
+--- The iterator pipeline terminates when the original table or function iterator runs out of values
+--- (for function iterators, this means that the first value returned by the function is nil).
+---
 --- Examples:
 --- 
lua
 ---   local it = vim.iter({ 1, 2, 3, 4, 5 })
@@ -22,6 +25,7 @@
 ---   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()
@@ -111,7 +115,7 @@ end
 ---@return boolean True if the iterator stage should continue, false otherwise
 ---@return any Function arguments.
 local function continue(...)
-  if select('#', ...) > 0 then
+  if select(1, ...) ~= nil then
     return false, ...
   end
   return true
@@ -127,7 +131,7 @@ end
 ---@return boolean True if the iterator pipeline should continue, false otherwise
 ---@return any Return values of f
 local function apply(f, ...)
-  if select('#', ...) > 0 then
+  if select(1, ...) ~= nil then
     return continue(f(...))
   end
   return false
@@ -258,7 +262,7 @@ end
 ---                       in the pipeline as arguments.
 function Iter.each(self, f)
   local function fn(...)
-    if select('#', ...) > 0 then
+    if select(1, ...) ~= nil then
       f(...)
       return true
     end
@@ -740,6 +744,12 @@ end
 ---@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
 
@@ -912,6 +922,8 @@ function Iter.new(src, ...)
 
     --- 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 ...
-- 
cgit 


From 27a566f3f8e07a4cebb426674800bdf9a7f4f222 Mon Sep 17 00:00:00 2001
From: Gregory Anders <8965202+gpanders@users.noreply.github.com>
Date: Wed, 13 Sep 2023 08:38:28 -0500
Subject: feat(vimdoc): support Markdown code blocks (#25127)

Support Markdown code blocks in addition to 
 blocks in Doxygen doc
comments.

Update doc comments in iter.lua as a test.
---
 runtime/lua/vim/iter.lua | 187 ++++++++++++++++++++++++++---------------------
 1 file changed, 105 insertions(+), 82 deletions(-)

(limited to 'runtime/lua/vim/iter.lua')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 56c130dd0c..595baa7019 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -15,44 +15,45 @@
 --- (for function iterators, this means that the first value returned by the function is nil).
 ---
 --- 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" }
---- 
+--- +--- ```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()|. @@ -140,9 +141,10 @@ end --- Add a filter step to the iterator pipeline. --- --- Example: ----
lua
+---
+--- ```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 @@ -176,7 +178,8 @@ end --- If the map function returns nil, the value is filtered from the iterator. --- --- Example: ----
lua
+---
+--- ```lua
 --- local it = vim.iter({ 1, 2, 3, 4 }):map(function(v)
 ---   if v % 2 == 0 then
 ---     return v * 3
@@ -184,7 +187,7 @@ 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 @@ -288,7 +291,8 @@ end --- pipeline, each value will be included in a table. --- --- Examples: ----
lua
+---
+--- ```lua
 --- vim.iter(string.gmatch('100 20 50', '%d+')):map(tonumber):totable()
 --- -- { 100, 20, 50 }
 ---
@@ -297,7 +301,7 @@ end
 ---
 --- 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()|. @@ -352,7 +356,8 @@ end --- Fold ("reduce") an iterator or table into a single value. --- --- Examples: ----
lua
+---
+--- ```lua
 --- -- Create a new table with only even values
 --- local t = { a = 1, b = 2, c = 3, d = 4 }
 --- local it = vim.iter(t)
@@ -362,7 +367,7 @@ end
 ---   return t
 --- end)
 --- -- { b = 2, d = 4 }
---- 
+--- ``` --- ---@generic A --- @@ -398,7 +403,8 @@ end --- Return the next value from the iterator. --- --- Example: ----
lua
+---
+--- ```lua
 ---
 --- local it = vim.iter(string.gmatch('1 2 3', '%d+')):map(tonumber)
 --- it:next()
@@ -408,7 +414,7 @@ end
 --- it:next()
 --- -- 3
 ---
---- 
+--- ``` --- ---@return any function Iter.next(self) -- luacheck: no unused args @@ -431,13 +437,14 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ----
lua
+---
+--- ```lua
 ---
 --- local it = vim.iter({ 3, 6, 9, 12 }):rev()
 --- it:totable()
 --- -- { 12, 9, 6, 3 }
 ---
---- 
+--- ``` --- ---@return Iter function Iter.rev(self) @@ -457,7 +464,8 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ----
lua
+---
+--- ```lua
 ---
 --- local it = vim.iter({ 3, 6, 9, 12 })
 --- it:peek()
@@ -467,7 +475,7 @@ end
 --- it:next()
 --- -- 3
 ---
---- 
+--- ``` --- ---@return any function Iter.peek(self) -- luacheck: no unused args @@ -486,7 +494,8 @@ end --- Advances the iterator. Returns nil and drains the iterator if no value is found. --- --- Examples: ----
lua
+---
+--- ```lua
 ---
 --- local it = vim.iter({ 3, 6, 9, 12 })
 --- it:find(12)
@@ -500,7 +509,7 @@ end
 --- it:find(function(v) return v % 4 == 0 end)
 --- -- 12
 ---
---- 
+--- ``` --- ---@return any function Iter.find(self, f) @@ -536,7 +545,8 @@ end --- Only supported for iterators on list-like tables. --- --- Examples: ----
lua
+---
+--- ```lua
 ---
 --- local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate()
 --- it:rfind(1)
@@ -544,7 +554,7 @@ end
 --- it:rfind(1)
 --- -- 1	1
 ---
---- 
+--- ``` --- ---@see Iter.find --- @@ -578,13 +588,14 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ----
lua
+---
+--- ```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 @@ -604,7 +615,8 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ----
lua
+---
+--- ```lua
 --- local it = vim.iter({1, 2, 3, 4})
 --- it:peekback()
 --- -- 4
@@ -612,7 +624,7 @@ end
 --- -- 4
 --- it:nextback()
 --- -- 4
---- 
+--- ``` --- ---@return any function Iter.peekback(self) -- luacheck: no unused args @@ -629,13 +641,14 @@ end --- Skip values in the iterator. --- --- Example: ----
lua
+---
+--- ```lua
 ---
 --- local it = vim.iter({ 3, 6, 9, 12 }):skip(2)
 --- it:next()
 --- -- 9
 ---
---- 
+--- ``` --- ---@param n number Number of values to skip. ---@return Iter @@ -661,13 +674,14 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ----
lua
+---
+--- ```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 @@ -691,7 +705,8 @@ end --- This function advances the iterator. --- --- Example: ----
lua
+---
+--- ```lua
 ---
 --- local it = vim.iter({ 3, 6, 9, 12 })
 --- it:nth(2)
@@ -699,7 +714,7 @@ end
 --- it:nth(2)
 --- -- 12
 ---
---- 
+--- ``` --- ---@param n number The index of the value to return. ---@return any @@ -716,7 +731,8 @@ end --- Only supported for iterators on list-like tables. --- --- Example: ----
lua
+---
+--- ```lua
 ---
 --- local it = vim.iter({ 3, 6, 9, 12 })
 --- it:nthback(2)
@@ -724,7 +740,7 @@ end
 --- it:nthback(2)
 --- -- 3
 ---
---- 
+--- ``` --- ---@param n number The index of the value to return. ---@return any @@ -805,7 +821,8 @@ end --- Drains the iterator. --- --- Example: ----
lua
+---
+--- ```lua
 ---
 --- local it = vim.iter(vim.gsplit('abcdefg', ''))
 --- it:last()
@@ -815,7 +832,7 @@ end
 --- it:last()
 --- -- 15
 ---
---- 
+--- ``` --- ---@return any function Iter.last(self) @@ -839,19 +856,22 @@ end --- Add an iterator stage that returns the current iterator count as well as the iterator value. --- --- For list tables, prefer ----
lua
+---
+--- ```lua
 --- vim.iter(ipairs(t))
---- 
+--- ``` --- --- over ----
lua
+---
+--- ```lua
 --- vim.iter(t):enumerate()
---- 
+--- ``` --- --- as the former is faster. --- --- Example: ----
lua
+---
+--- ```lua
 ---
 --- local it = vim.iter(vim.gsplit('abc', '')):enumerate()
 --- it:next()
@@ -861,7 +881,7 @@ end
 --- it:next()
 --- -- 3	'c'
 ---
---- 
+--- ``` --- ---@return Iter function Iter.enumerate(self) @@ -959,9 +979,10 @@ end --- Collect an iterator into a table. --- --- This is a convenience function that performs: ----
lua
+---
+--- ```lua
 --- vim.iter(f):totable()
---- 
+--- ``` --- ---@param f function Iterator function ---@return table @@ -972,9 +993,10 @@ end --- Filter a table or iterator. --- --- This is a convenience function that performs: ----
lua
+---
+--- ```lua
 --- vim.iter(src):filter(f):totable()
---- 
+--- ``` --- ---@see |Iter:filter()| --- @@ -990,9 +1012,10 @@ end --- Map and filter a table or iterator. --- --- This is a convenience function that performs: ----
lua
+---
+--- ```lua
 --- vim.iter(src):map(f):totable()
---- 
+--- ``` --- ---@see |Iter:map()| --- -- cgit From fc4385ad94c0a12d2ce8b6d275d8336a740b04fd Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 25 Nov 2023 06:35:31 -0800 Subject: docs: vim.iter #26169 closes #24141 closes #24746 --- runtime/lua/vim/iter.lua | 117 +++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 70 deletions(-) (limited to 'runtime/lua/vim/iter.lua') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 595baa7019..874bdfb437 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -2,17 +2,23 @@ --- --- \*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 together to create iterator "pipelines". ---- Each pipeline stage receives as input the output values from the prior stage. The values used in ---- the first stage of the pipeline depend on the type passed to this function: +--- 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) pass only the value of each element ---- - Non-list tables (dictionaries) pass both the key and value of each element ---- - Function |iterator|s pass all of the values returned by their respective function ---- - Tables with a metatable implementing |__call()| are treated as function iterators +--- - 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 original table or function iterator runs out of values ---- (for function iterators, this means that the first value returned by the function is nil). +--- 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: --- @@ -138,7 +144,7 @@ local function apply(f, ...) return false end ---- Add a filter step to the iterator pipeline. +--- Filters an iterator pipeline. --- --- Example: --- @@ -173,7 +179,7 @@ function ListIter.filter(self, f) return self end ---- Add a map step to the iterator pipeline. +--- 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. --- @@ -253,12 +259,9 @@ function ListIter.map(self, f) return self end ---- Call a function once for each item in the pipeline. ---- ---- This is used for functions which have side effects. To modify the values in ---- the iterator, use |Iter:map()|. +--- Calls a function once for each item in the pipeline, draining the iterator. --- ---- This function drains 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 @@ -353,7 +356,7 @@ function ListIter.totable(self) return self._table end ---- Fold ("reduce") an iterator or table into a single value. +--- Folds ("reduces") an iterator into a single value. --- --- Examples: --- @@ -400,7 +403,7 @@ function ListIter.fold(self, init, f) return acc end ---- Return the next value from the iterator. +--- Gets the next value from the iterator. --- --- Example: --- @@ -432,9 +435,7 @@ function ListIter.next(self) end end ---- Reverse an iterator. ---- ---- Only supported for iterators on list-like tables. +--- Reverses a |list-iterator| pipeline. --- --- Example: --- @@ -459,9 +460,7 @@ function ListIter.rev(self) return self end ---- Peek at the next value in the iterator without consuming it. ---- ---- Only supported for iterators on list-like tables. +--- Gets the next value in a |list-iterator| without consuming it. --- --- Example: --- @@ -538,12 +537,10 @@ function Iter.find(self, f) return unpack(result) end ---- Find the first value in the iterator that satisfies the given predicate, starting from the 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. --- ---- Only supported for iterators on list-like tables. ---- --- Examples: --- --- ```lua @@ -583,9 +580,7 @@ function ListIter.rfind(self, f) -- luacheck: no unused args self._head = self._tail end ---- Return the next value from the end of the iterator. ---- ---- Only supported for iterators on list-like tables. +--- "Pops" a value from a |list-iterator| (gets the last value and decrements the tail). --- --- Example: --- @@ -610,9 +605,9 @@ function ListIter.nextback(self) end end ---- Return the next value from the end of the iterator without consuming it. +--- Gets the last value of a |list-iterator| without consuming it. --- ---- Only supported for iterators on list-like tables. +--- See also |Iter:last()|. --- --- Example: --- @@ -638,7 +633,7 @@ function ListIter.peekback(self) end end ---- Skip values in the iterator. +--- Skips `n` values of an iterator pipeline. --- --- Example: --- @@ -669,9 +664,7 @@ function ListIter.skip(self, n) return self end ---- Skip values in the iterator starting from the end. ---- ---- Only supported for iterators on list-like tables. +--- Skips `n` values backwards from the end of a |list-iterator| pipeline. --- --- Example: --- @@ -700,9 +693,7 @@ function ListIter.skipback(self, n) return self end ---- Return the nth value in the iterator. ---- ---- This function advances the iterator. +--- Gets the nth value of an iterator (and advances to it). --- --- Example: --- @@ -724,11 +715,7 @@ function Iter.nth(self, n) end end ---- Return the nth value from the end of the iterator. ---- ---- This function advances the iterator. ---- ---- Only supported for iterators on list-like tables. +--- Gets the nth value from the end of a |list-iterator| (and advances to it). --- --- Example: --- @@ -750,11 +737,9 @@ function Iter.nthback(self, n) end end ---- Slice an iterator, changing its start and end positions. +--- Sets the start and end of a |list-iterator| pipeline. --- ---- This is equivalent to :skip(first - 1):skipback(len - last + 1) ---- ---- Only supported for iterators on list-like tables. +--- Equivalent to `:skip(first - 1):skipback(len - last + 1)`. --- ---@param first number ---@param last number @@ -769,7 +754,7 @@ function ListIter.slice(self, first, last) return self:skip(math.max(0, first - 1)):skipback(math.max(0, self._tail - last - 1)) end ---- Return true if any of the items in the iterator match the given predicate. +--- 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 @@ -793,7 +778,7 @@ function Iter.any(self, pred) return any end ---- Return true if all of the items in the iterator match the given predicate. +--- 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 @@ -816,9 +801,7 @@ function Iter.all(self, pred) return all end ---- Return the last item in the iterator. ---- ---- Drains the iterator. +--- Drains the iterator and returns the last item. --- --- Example: --- @@ -853,22 +836,20 @@ function ListIter.last(self) return v end ---- Add an iterator stage that returns the current iterator count as well as the iterator value. +--- Yields the item index (count) and value for each item of an iterator pipeline. --- ---- For list tables, prefer +--- For list tables, this is more efficient: --- --- ```lua --- vim.iter(ipairs(t)) --- ``` --- ---- over +--- instead of: --- --- ```lua --- vim.iter(t):enumerate() --- ``` --- ---- as the former is faster. ---- --- Example: --- --- ```lua @@ -902,7 +883,7 @@ function ListIter.enumerate(self) return self end ---- Create a new Iter object from a table or iterator. +--- Creates a new Iter object from a table or other |iterable|. --- ---@param src table|function Table or iterator to drain values from ---@return Iter @@ -923,8 +904,7 @@ function Iter.new(src, ...) local t = {} - -- Check if source table can be treated like a list (indices are consecutive integers - -- starting from 1) + -- 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 @@ -976,11 +956,10 @@ function ListIter.new(t) return it end ---- Collect an iterator into a table. ---- ---- This is a convenience function that performs: +--- Collects an |iterable| into a table. --- --- ```lua +--- -- Equivalent to: --- vim.iter(f):totable() --- ``` --- @@ -990,11 +969,10 @@ function M.totable(f, ...) return Iter.new(f, ...):totable() end ---- Filter a table or iterator. ---- ---- This is a convenience function that performs: +--- Filters a table or other |iterable|. --- --- ```lua +--- -- Equivalent to: --- vim.iter(src):filter(f):totable() --- ``` --- @@ -1009,11 +987,10 @@ function M.filter(f, src, ...) return Iter.new(src, ...):filter(f):totable() end ---- Map and filter a table or iterator. ---- ---- This is a convenience function that performs: +--- Maps a table or other |iterable|. --- --- ```lua +--- -- Equivalent to: --- vim.iter(src):map(f):totable() --- ``` --- -- cgit