iter.lua (30074B)
1 --- @brief 2 --- 3 --- [vim.iter()]() is an interface for [iterable]s: it wraps a table or function argument into an 4 --- [Iter]() object with methods (such as [Iter:filter()] and [Iter:map()]) that transform the 5 --- underlying source data. These methods can be chained to create iterator "pipelines": the output 6 --- of each pipeline stage is input to the next stage. The first stage depends on the type passed to 7 --- `vim.iter()`: 8 --- 9 --- - Lists or arrays (|lua-list|) yield only the value of each element. 10 --- - Holes (nil values) are allowed (but discarded). 11 --- - Use pairs() to treat array/list tables as dicts (preserve holes and non-contiguous integer 12 --- keys): `vim.iter(pairs(…))`. 13 --- - Use |Iter:enumerate()| to also pass the index to the next stage. 14 --- - Or initialize with ipairs(): `vim.iter(ipairs(…))`. 15 --- - Non-list tables (|lua-dict|) yield both the key and value of each element. 16 --- - Function |iterator|s yield all values returned by the underlying function. 17 --- - Tables with a |__call()| metamethod are treated as function iterators. 18 --- 19 --- The iterator pipeline terminates when the underlying |iterable| is exhausted (for function 20 --- iterators this means it returned nil). 21 --- 22 --- Note: `vim.iter()` scans table input to decide if it is a list or a dict; to avoid this cost you 23 --- can wrap the table with an iterator e.g. `vim.iter(ipairs({…}))`, but that precludes the use of 24 --- |list-iterator| operations such as |Iter:rev()|). 25 --- 26 --- Examples: 27 --- 28 --- ```lua 29 --- local it = vim.iter({ 1, 2, 3, 4, 5 }) 30 --- it:map(function(v) 31 --- return v * 3 32 --- end) 33 --- it:rev() 34 --- it:skip(2) 35 --- it:totable() 36 --- -- { 9, 6, 3 } 37 --- 38 --- -- ipairs() is a function iterator which returns both the index (i) and the value (v) 39 --- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v) 40 --- if i > 2 then return v end 41 --- end):totable() 42 --- -- { 3, 4, 5 } 43 --- 44 --- local it = vim.iter(vim.gsplit('1,2,3,4,5', ',')) 45 --- it:map(function(s) return tonumber(s) end) 46 --- for i, d in it:enumerate() do 47 --- print(string.format("Column %d is %d", i, d)) 48 --- end 49 --- -- Column 1 is 1 50 --- -- Column 2 is 2 51 --- -- Column 3 is 3 52 --- -- Column 4 is 4 53 --- -- Column 5 is 5 54 --- 55 --- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v) 56 --- return k == 'z' 57 --- end) 58 --- -- true 59 --- 60 --- local rb = vim.ringbuf(3) 61 --- rb:push("a") 62 --- rb:push("b") 63 --- vim.iter(rb):totable() 64 --- -- { "a", "b" } 65 --- ``` 66 67 --- LuaLS is bad at generics which this module mostly deals with 68 --- @diagnostic disable:no-unknown 69 70 ---@nodoc 71 ---@class IterMod 72 ---@operator call:Iter 73 74 local M = {} 75 76 ---@nodoc 77 ---@class Iter 78 ---@field _peeked any 79 ---@field _next fun():... The underlying function that returns the next value(s) from the source. 80 local Iter = {} 81 Iter.__index = Iter 82 Iter.__call = function(self) 83 return self:next() 84 end 85 86 --- Special case implementations for iterators on list tables. 87 ---@nodoc 88 ---@class ArrayIter : Iter 89 ---@field _table table Underlying table data 90 ---@field _head number Index to the front of a table iterator 91 ---@field _tail number Index to the end of a table iterator (exclusive) 92 local ArrayIter = {} 93 ArrayIter.__index = setmetatable(ArrayIter, Iter) 94 ArrayIter.__call = function(self) 95 return self:next() 96 end 97 98 --- Packed tables use this as their metatable 99 local packedmt = {} 100 101 local function unpack(t) 102 if type(t) == 'table' and getmetatable(t) == packedmt then 103 return _G.unpack(t, 1, t.n) 104 end 105 return t 106 end 107 108 local function pack(...) 109 local n = select('#', ...) 110 if n > 1 then 111 return setmetatable({ n = n, ... }, packedmt) 112 end 113 return ... 114 end 115 116 local function sanitize(t) 117 if type(t) == 'table' and getmetatable(t) == packedmt then 118 -- Remove length tag and metatable 119 t.n = nil 120 setmetatable(t, nil) 121 end 122 return t 123 end 124 125 --- Flattens a single array-like table. Errors if it attempts to flatten a 126 --- dict-like table 127 ---@param t table table which should be flattened 128 ---@param max_depth number depth to which the table should be flattened 129 ---@param depth number current iteration depth 130 ---@param result table output table that contains flattened result 131 ---@return table|nil flattened table if it can be flattened, otherwise nil 132 local function flatten(t, max_depth, depth, result) 133 if depth < max_depth and type(t) == 'table' then 134 for k, v in pairs(t) do 135 if type(k) ~= 'number' or k <= 0 or math.floor(k) ~= k then 136 -- short-circuit: this is not a list like table 137 return nil 138 end 139 140 if flatten(v, max_depth, depth + 1, result) == nil then 141 return nil 142 end 143 end 144 elseif t ~= nil then 145 result[#result + 1] = t 146 end 147 148 return result 149 end 150 151 --- Determine if the current iterator stage should continue. 152 --- 153 --- If any arguments are passed to this function, then return those arguments 154 --- and stop the current iterator stage. Otherwise, return true to signal that 155 --- the current stage should continue. 156 --- 157 ---@param ... any Function arguments. 158 ---@return boolean True if the iterator stage should continue, false otherwise 159 ---@return any Function arguments. 160 local function continue(...) 161 if select(1, ...) ~= nil then 162 return false, ... 163 end 164 return true 165 end 166 167 --- If no input arguments are given return false, indicating the current 168 --- iterator stage should stop. Otherwise, apply the arguments to the function 169 --- f. If that function returns no values, the current iterator stage continues. 170 --- Otherwise, those values are returned. 171 --- 172 ---@param f function Function to call with the given arguments 173 ---@param ... any Arguments to apply to f 174 ---@return boolean True if the iterator pipeline should continue, false otherwise 175 ---@return any Return values of f 176 local function apply(f, ...) 177 if select(1, ...) ~= nil then 178 return continue(f(...)) 179 end 180 return false 181 end 182 183 --- Filters an iterator pipeline. 184 --- 185 --- Example: 186 --- 187 --- ```lua 188 --- local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded) 189 --- ``` 190 --- 191 ---@param f fun(...):boolean Takes all values returned from the previous stage 192 --- in the pipeline and returns false or nil if the 193 --- current iterator element should be removed. 194 ---@return Iter 195 function Iter:filter(f) 196 return self:map(function(...) 197 if f(...) then 198 return ... 199 end 200 end) 201 end 202 203 ---@private 204 function ArrayIter:filter(f) 205 local inc = self._head < self._tail and 1 or -1 206 local n = self._head 207 for i = self._head, self._tail - inc, inc do 208 local v = self._table[i] 209 if f(unpack(v)) then 210 self._table[n] = v 211 n = n + inc 212 end 213 end 214 self._tail = n 215 return self 216 end 217 218 --- Removes duplicate values from an iterator pipeline. 219 --- 220 --- Only the first occurrence of each value is kept. 221 --- 222 --- Accepts an optional `key` argument, which if provided is called for each 223 --- value in the iterator to compute a hash key for uniqueness comparison. This is 224 --- useful for deduplicating table values or complex objects. 225 --- If `key` returns `nil` for a value, that value will be considered unique, 226 --- even if multiple values return `nil`. 227 --- 228 --- If a function-based iterator returns multiple arguments, uniqueness is 229 --- checked based on the first return value. To change this behavior, specify 230 --- `key`. 231 --- 232 --- Examples: 233 --- 234 --- ```lua 235 --- vim.iter({ 1, 2, 2, 3, 2 }):unique():totable() 236 --- -- { 1, 2, 3 } 237 --- 238 --- vim.iter({ {id=1}, {id=2}, {id=1} }) 239 --- :unique(function(x) 240 --- return x.id 241 --- end) 242 --- :totable() 243 --- -- { {id=1}, {id=2} } 244 --- ``` 245 --- 246 ---@param key? fun(...):any Optional hash function to determine uniqueness of values. 247 ---@return Iter 248 ---@see |vim.list.unique()| 249 function Iter:unique(key) 250 local seen = {} --- @type table<any,boolean> 251 252 key = key or function(a) 253 return a 254 end 255 256 return self:filter(function(...) 257 local hash = key(...) 258 if hash == nil then 259 return true 260 elseif not seen[hash] then 261 seen[hash] = true 262 return true 263 else 264 return false 265 end 266 end) 267 end 268 269 --- Flattens a |list-iterator|, un-nesting nested values up to the given {depth}. 270 --- Errors if it attempts to flatten a dict-like value. 271 --- 272 --- Examples: 273 --- 274 --- ```lua 275 --- vim.iter({ 1, { 2 }, { { 3 } } }):flatten():totable() 276 --- -- { 1, 2, { 3 } } 277 --- 278 --- vim.iter({1, { { a = 2 } }, { 3 } }):flatten():totable() 279 --- -- { 1, { a = 2 }, 3 } 280 --- 281 --- vim.iter({ 1, { { a = 2 } }, { 3 } }):flatten(math.huge):totable() 282 --- -- error: attempt to flatten a dict-like table 283 --- ``` 284 --- 285 ---@param depth? number Depth to which |list-iterator| should be flattened 286 --- (defaults to 1) 287 ---@return Iter 288 ---@diagnostic disable-next-line:unused-local 289 function Iter:flatten(depth) -- luacheck: no unused args 290 error('flatten() requires an array-like table') 291 end 292 293 ---@private 294 function ArrayIter:flatten(depth) 295 depth = depth or 1 296 local inc = self._head < self._tail and 1 or -1 297 local target = {} 298 299 for i = self._head, self._tail - inc, inc do 300 local flattened = flatten(self._table[i], depth, 0, {}) 301 302 -- exit early if we try to flatten a dict-like table 303 if flattened == nil then 304 error('flatten() requires an array-like table') 305 end 306 307 for _, v in pairs(flattened) do 308 target[#target + 1] = v 309 end 310 end 311 312 self._head = 1 313 self._tail = #target + 1 314 self._table = target 315 return self 316 end 317 318 --- Maps the items of an iterator pipeline to the values returned by `f`. 319 --- 320 --- If the map function returns nil, the value is filtered from the iterator. 321 --- 322 --- Example: 323 --- 324 --- ```lua 325 --- local it = vim.iter({ 1, 2, 3, 4 }):map(function(v) 326 --- if v % 2 == 0 then 327 --- return v * 3 328 --- end 329 --- end) 330 --- it:totable() 331 --- -- { 6, 12 } 332 --- ``` 333 --- 334 ---@param f fun(...):...:any Mapping function. Takes all values returned from 335 --- the previous stage in the pipeline as arguments 336 --- and returns one or more new values, which are used 337 --- in the next pipeline stage. Nil return values 338 --- are filtered from the output. 339 ---@return Iter 340 function Iter:map(f) 341 -- Implementation note: the reader may be forgiven for observing that this 342 -- function appears excessively convoluted. The problem to solve is that each 343 -- stage of the iterator pipeline can return any number of values, and the 344 -- number of values could even change per iteration. And the return values 345 -- must be checked to determine if the pipeline has ended, so we cannot 346 -- naively forward them along to the next stage. 347 -- 348 -- A simple approach is to pack all of the return values into a table, check 349 -- for nil, then unpack the table for the next stage. However, packing and 350 -- unpacking tables is quite slow. There is no other way in Lua to handle an 351 -- unknown number of function return values than to simply forward those 352 -- values along to another function. Hence the intricate function passing you 353 -- see here. 354 355 local next = self.next 356 357 --- Drain values from the upstream iterator source until a value can be 358 --- returned. 359 --- 360 --- This is a recursive function. The base case is when the first argument is 361 --- false, which indicates that the rest of the arguments should be returned 362 --- as the values for the current iteration stage. 363 --- 364 ---@param cont boolean If true, the current iterator stage should continue to 365 --- pull values from its upstream pipeline stage. 366 --- Otherwise, this stage is complete and returns the 367 --- values passed. 368 ---@param ... any Values to return if cont is false. 369 ---@return any 370 local function fn(cont, ...) 371 if cont then 372 return fn(apply(f, next(self))) 373 end 374 return ... 375 end 376 377 self.next = function() 378 return fn(apply(f, next(self))) 379 end 380 return self 381 end 382 383 ---@private 384 function ArrayIter:map(f) 385 local inc = self._head < self._tail and 1 or -1 386 local n = self._head 387 for i = self._head, self._tail - inc, inc do 388 local v = pack(f(unpack(self._table[i]))) 389 if v ~= nil then 390 self._table[n] = v 391 n = n + inc 392 end 393 end 394 self._tail = n 395 return self 396 end 397 398 --- Calls a function once for each item in the pipeline, draining the iterator. 399 --- 400 --- For functions with side effects. To modify the values in the iterator, use |Iter:map()|. 401 --- 402 ---@param f fun(...) Function to execute for each item in the pipeline. 403 --- Takes all of the values returned by the previous stage 404 --- in the pipeline as arguments. 405 function Iter:each(f) 406 local function fn(...) 407 if select(1, ...) ~= nil then 408 f(...) 409 return true 410 end 411 end 412 while fn(self:next()) do 413 end 414 end 415 416 ---@private 417 function ArrayIter:each(f) 418 local inc = self._head < self._tail and 1 or -1 419 for i = self._head, self._tail - inc, inc do 420 f(unpack(self._table[i])) 421 end 422 self._head = self._tail 423 end 424 425 --- Collect the iterator into a table. 426 --- 427 --- The resulting table depends on the initial source in the iterator pipeline. 428 --- Array-like tables and function iterators will be collected into an array-like 429 --- table. If multiple values are returned from the final stage in the iterator 430 --- pipeline, each value will be included in a table. 431 --- 432 --- Examples: 433 --- 434 --- ```lua 435 --- vim.iter(string.gmatch('100 20 50', '%d+')):map(tonumber):totable() 436 --- -- { 100, 20, 50 } 437 --- 438 --- vim.iter({ 1, 2, 3 }):map(function(v) return v, 2 * v end):totable() 439 --- -- { { 1, 2 }, { 2, 4 }, { 3, 6 } } 440 --- 441 --- vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable() 442 --- -- { { 'a', 1 }, { 'c', 3 } } 443 --- ``` 444 --- 445 --- The generated table is an array-like table with consecutive, numeric indices. 446 --- To create a map-like table with arbitrary keys, use |Iter:fold()|. 447 --- 448 --- 449 ---@return table 450 function Iter:totable() 451 local t = {} 452 453 while true do 454 local args = pack(self:next()) 455 if args == nil then 456 break 457 end 458 459 t[#t + 1] = sanitize(args) 460 end 461 return t 462 end 463 464 ---@private 465 function ArrayIter:totable() 466 if self.next ~= ArrayIter.next or self._head >= self._tail then 467 return Iter.totable(self) 468 end 469 470 local needs_sanitize = getmetatable(self._table[self._head]) == packedmt 471 472 -- Reindex and sanitize. 473 local len = self._tail - self._head 474 475 if needs_sanitize then 476 for i = 1, len do 477 self._table[i] = sanitize(self._table[self._head - 1 + i]) 478 end 479 else 480 for i = 1, len do 481 self._table[i] = self._table[self._head - 1 + i] 482 end 483 end 484 485 for i = len + 1, table.maxn(self._table) do 486 self._table[i] = nil 487 end 488 489 self._head = 1 490 self._tail = len + 1 491 492 return self._table 493 end 494 495 --- Collect the iterator into a delimited string. 496 --- 497 --- Each element in the iterator is joined into a string separated by {delim}. 498 --- 499 --- Consumes the iterator. 500 --- 501 --- @param delim string Delimiter 502 --- @return string 503 function Iter:join(delim) 504 return table.concat(self:totable(), delim) 505 end 506 507 --- Folds ("reduces") an iterator into a single value. [Iter:reduce()]() 508 --- 509 --- Examples: 510 --- 511 --- ```lua 512 --- -- Create a new table with only even values 513 --- vim.iter({ a = 1, b = 2, c = 3, d = 4 }) 514 --- :filter(function(k, v) return v % 2 == 0 end) 515 --- :fold({}, function(acc, k, v) 516 --- acc[k] = v 517 --- return acc 518 --- end) --> { b = 2, d = 4 } 519 --- 520 --- -- Get the "maximum" item of an iterable. 521 --- vim.iter({ -99, -4, 3, 42, 0, 0, 7 }) 522 --- :fold({}, function(acc, v) 523 --- acc.max = math.max(v, acc.max or v) 524 --- return acc 525 --- end) --> { max = 42 } 526 --- ``` 527 --- 528 ---@generic A 529 --- 530 ---@param init A Initial value of the accumulator. 531 ---@param f fun(acc:A, ...):A Accumulation function. 532 ---@return A 533 function Iter:fold(init, f) 534 local acc = init 535 536 --- Use a closure to handle var args returned from iterator 537 local function fn(...) 538 if select(1, ...) ~= nil then 539 acc = f(acc, ...) 540 return true 541 end 542 end 543 544 while fn(self:next()) do 545 end 546 return acc 547 end 548 549 ---@private 550 function ArrayIter:fold(init, f) 551 local acc = init 552 local inc = self._head < self._tail and 1 or -1 553 for i = self._head, self._tail - inc, inc do 554 acc = f(acc, unpack(self._table[i])) 555 end 556 return acc 557 end 558 559 --- Gets the next value from the iterator. 560 --- 561 --- Example: 562 --- 563 --- ```lua 564 --- 565 --- local it = vim.iter(string.gmatch('1 2 3', '%d+')):map(tonumber) 566 --- it:next() 567 --- -- 1 568 --- it:next() 569 --- -- 2 570 --- it:next() 571 --- -- 3 572 --- 573 --- ``` 574 --- 575 ---@return any 576 function Iter:next() 577 if self._peeked then 578 local v = self._peeked 579 self._peeked = nil 580 581 return unpack(v) 582 end 583 584 return self._next() 585 end 586 587 ---@private 588 function ArrayIter:next() 589 if self._head ~= self._tail then 590 local v = self._table[self._head] 591 local inc = self._head < self._tail and 1 or -1 592 self._head = self._head + inc 593 return unpack(v) 594 end 595 end 596 597 --- Reverses a |list-iterator| pipeline. 598 --- 599 --- Example: 600 --- 601 --- ```lua 602 --- 603 --- local it = vim.iter({ 3, 6, 9, 12 }):rev() 604 --- it:totable() 605 --- -- { 12, 9, 6, 3 } 606 --- 607 --- ``` 608 --- 609 ---@return Iter 610 function Iter:rev() 611 error('rev() requires an array-like table') 612 end 613 614 ---@private 615 function ArrayIter:rev() 616 local inc = self._head < self._tail and 1 or -1 617 self._head, self._tail = self._tail - inc, self._head - inc 618 return self 619 end 620 621 --- Gets the next value from the iterator without consuming it. 622 --- 623 --- The value returned by |Iter:peek()| will be returned again by the next call 624 --- to |Iter:next()|. 625 --- 626 --- Example: 627 --- 628 --- ```lua 629 --- 630 --- local it = vim.iter({ 3, 6, 9, 12 }) 631 --- it:peek() 632 --- -- 3 633 --- it:peek() 634 --- -- 3 635 --- it:next() 636 --- -- 3 637 --- 638 --- ``` 639 --- 640 ---@return any 641 function Iter:peek() 642 if not self._peeked then 643 self._peeked = pack(self:next()) 644 end 645 646 return unpack(self._peeked) 647 end 648 649 ---@private 650 function ArrayIter:peek() 651 if self._head ~= self._tail then 652 return self._table[self._head] 653 end 654 end 655 656 --- Find the first value in the iterator that satisfies the given predicate. 657 --- 658 --- Advances the iterator. Returns nil and drains the iterator if no value is found. 659 --- 660 --- Examples: 661 --- 662 --- ```lua 663 --- 664 --- local it = vim.iter({ 3, 6, 9, 12 }) 665 --- it:find(12) 666 --- -- 12 667 --- 668 --- local it = vim.iter({ 3, 6, 9, 12 }) 669 --- it:find(20) 670 --- -- nil 671 --- 672 --- local it = vim.iter({ 3, 6, 9, 12 }) 673 --- it:find(function(v) return v % 4 == 0 end) 674 --- -- 12 675 --- 676 --- ``` 677 ---@param f any 678 ---@return any 679 function Iter:find(f) 680 if type(f) ~= 'function' then 681 local val = f 682 f = function(v) 683 return v == val 684 end 685 end 686 687 local result = nil 688 689 --- Use a closure to handle var args returned from iterator 690 local function fn(...) 691 if select(1, ...) ~= nil then 692 if f(...) then 693 result = pack(...) 694 else 695 return true 696 end 697 end 698 end 699 700 while fn(self:next()) do 701 end 702 return unpack(result) 703 end 704 705 --- Gets the first value satisfying a predicate, from the end of a |list-iterator|. 706 --- 707 --- Advances the iterator. Returns nil and drains the iterator if no value is found. 708 --- 709 --- Examples: 710 --- 711 --- ```lua 712 --- 713 --- local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate() 714 --- it:rfind(1) 715 --- -- 5 1 716 --- it:rfind(1) 717 --- -- 1 1 718 --- 719 --- ``` 720 --- 721 ---@see |Iter:find()| 722 --- 723 ---@param f any 724 ---@return any 725 ---@diagnostic disable-next-line: unused-local 726 function Iter:rfind(f) -- luacheck: no unused args 727 error('rfind() requires an array-like table') 728 end 729 730 ---@private 731 function ArrayIter:rfind(f) 732 if type(f) ~= 'function' then 733 local val = f 734 f = function(v) 735 return v == val 736 end 737 end 738 739 local inc = self._head < self._tail and 1 or -1 740 for i = self._tail - inc, self._head, -inc do 741 local v = self._table[i] 742 if f(unpack(v)) then 743 self._tail = i 744 return unpack(v) 745 end 746 end 747 self._head = self._tail 748 end 749 750 --- Transforms an iterator to yield only the first n values, or all values 751 --- satisfying a predicate. 752 --- 753 --- Example: 754 --- 755 --- ```lua 756 --- local it = vim.iter({ 1, 2, 3, 4 }):take(2) 757 --- it:next() 758 --- -- 1 759 --- it:next() 760 --- -- 2 761 --- it:next() 762 --- -- nil 763 --- 764 --- local function pred(x) return x < 2 end 765 --- local it2 = vim.iter({ 1, 2, 3, 4 }):take(pred) 766 --- it2:next() 767 --- -- 1 768 --- it2:next() 769 --- -- nil 770 --- ``` 771 --- 772 ---@param n integer|fun(...):boolean Number of values to take or a predicate. 773 ---@return Iter 774 function Iter:take(n) 775 local i = 0 776 local f = n 777 if type(n) ~= 'function' then 778 f = function() 779 return i < n 780 end 781 end 782 783 local stop = false 784 local function fn(...) 785 if not stop and select(1, ...) ~= nil and f(...) then 786 i = i + 1 787 return ... 788 else 789 stop = true 790 end 791 end 792 793 local next = self.next 794 self.next = function() 795 return fn(next(self)) 796 end 797 return self 798 end 799 800 ---@private 801 function ArrayIter:take(n) 802 if type(n) == 'function' then 803 local inc = self._head < self._tail and 1 or -1 804 for i = self._head, self._tail, inc do 805 if not n(unpack(self._table[i])) then 806 self._tail = i 807 break 808 end 809 end 810 return self 811 end 812 813 local inc = self._head < self._tail and n or -n 814 local cmp = self._head < self._tail and math.min or math.max 815 self._tail = cmp(self._tail, self._head + inc) 816 return self 817 end 818 819 --- "Pops" a value from a |list-iterator| (gets the last value and decrements the tail). 820 --- 821 --- Example: 822 --- 823 --- ```lua 824 --- local it = vim.iter({1, 2, 3, 4}) 825 --- it:pop() 826 --- -- 4 827 --- it:pop() 828 --- -- 3 829 --- ``` 830 --- 831 ---@return any 832 function Iter:pop() 833 error('pop() requires an array-like table') 834 end 835 836 --- @nodoc 837 function ArrayIter:pop() 838 if self._head ~= self._tail then 839 local inc = self._head < self._tail and 1 or -1 840 self._tail = self._tail - inc 841 return self._table[self._tail] 842 end 843 end 844 845 --- Gets the last value of a |list-iterator| without consuming it. 846 --- 847 --- Example: 848 --- 849 --- ```lua 850 --- local it = vim.iter({1, 2, 3, 4}) 851 --- it:rpeek() 852 --- -- 4 853 --- it:rpeek() 854 --- -- 4 855 --- it:pop() 856 --- -- 4 857 --- ``` 858 --- 859 ---@see |Iter:last()| 860 --- 861 ---@return any 862 function Iter:rpeek() 863 error('rpeek() requires an array-like table') 864 end 865 866 ---@nodoc 867 function ArrayIter:rpeek() 868 if self._head ~= self._tail then 869 local inc = self._head < self._tail and 1 or -1 870 return self._table[self._tail - inc] 871 end 872 end 873 874 --- Skips `n` values of an iterator pipeline, or skips values while a predicate returns |lua-truthy|. 875 --- 876 --- When a predicate is used, skipping stops at the first value for which the 877 --- predicate returns non-truthy. That value is not consumed and will be returned 878 --- by the next call to |Iter:next()| 879 --- 880 --- Example: 881 --- 882 --- ```lua 883 --- 884 --- local it = vim.iter({ 3, 6, 9, 12 }):skip(2) 885 --- it:next() 886 --- -- 9 887 --- 888 --- local function pred(x) return x < 10 end 889 --- local it2 = vim.iter({ 3, 6, 9, 12 }):skip(pred) 890 --- it2:next() 891 --- -- 12 892 --- ``` 893 --- 894 ---@param n integer|fun(...):boolean Number of values to skip or a predicate. 895 ---@return Iter 896 function Iter:skip(n) 897 if type(n) == 'number' then 898 for _ = 1, n do 899 self._peeked = nil 900 local _ = self:next() 901 end 902 elseif type(n) == 'function' then 903 local next = self.next 904 905 self.next = function() 906 while true do 907 local peeked = self._peeked or pack(next(self)) 908 909 if not peeked then 910 return nil 911 end 912 913 if not n(unpack(peeked)) then 914 self._peeked = nil 915 return unpack(peeked) 916 end 917 918 self._peeked = nil 919 end 920 end 921 end 922 return self 923 end 924 925 ---@private 926 function ArrayIter:skip(n) 927 if type(n) == 'function' then 928 while self._head ~= self._tail do 929 local v = self._table[self._head] 930 if not n(unpack(v)) then 931 break 932 end 933 934 self._head = self._head + (self._head < self._tail and 1 or -1) 935 end 936 return self 937 end 938 939 local inc = self._head < self._tail and n or -n 940 self._head = self._head + inc 941 if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then 942 self._head = self._tail 943 end 944 return self 945 end 946 947 --- Discards `n` values from the end of a |list-iterator| pipeline. 948 --- 949 --- Example: 950 --- 951 --- ```lua 952 --- local it = vim.iter({ 1, 2, 3, 4, 5 }):rskip(2) 953 --- it:next() 954 --- -- 1 955 --- it:pop() 956 --- -- 3 957 --- ``` 958 --- 959 ---@param n number Number of values to skip. 960 ---@return Iter 961 ---@diagnostic disable-next-line: unused-local 962 function Iter:rskip(n) -- luacheck: no unused args 963 error('rskip() requires an array-like table') 964 end 965 966 ---@private 967 function ArrayIter:rskip(n) 968 local inc = self._head < self._tail and n or -n 969 self._tail = self._tail - inc 970 if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then 971 self._head = self._tail 972 end 973 return self 974 end 975 976 --- Gets the nth value of an iterator (and advances to it). 977 --- 978 --- If `n` is negative, offsets from the end of a |list-iterator|. 979 --- 980 --- Example: 981 --- 982 --- ```lua 983 --- local it = vim.iter({ 3, 6, 9, 12 }) 984 --- it:nth(2) 985 --- -- 6 986 --- it:nth(2) 987 --- -- 12 988 --- 989 --- local it2 = vim.iter({ 3, 6, 9, 12 }) 990 --- it2:nth(-2) 991 --- -- 9 992 --- it2:nth(-2) 993 --- -- 3 994 --- ``` 995 --- 996 ---@param n number Index of the value to return. May be negative if the source is a |list-iterator|. 997 ---@return any 998 function Iter:nth(n) 999 if n > 0 then 1000 return self:skip(n - 1):next() 1001 elseif n < 0 then 1002 return self:rskip(math.abs(n) - 1):pop() 1003 end 1004 end 1005 1006 --- Sets the start and end of a |list-iterator| pipeline. 1007 --- 1008 --- Equivalent to `:skip(first - 1):rskip(len - last + 1)`. 1009 --- 1010 ---@param first number 1011 ---@param last number 1012 ---@return Iter 1013 ---@diagnostic disable-next-line: unused-local 1014 function Iter:slice(first, last) -- luacheck: no unused args 1015 error('slice() requires an array-like table') 1016 end 1017 1018 ---@private 1019 function ArrayIter:slice(first, last) 1020 return self:skip(math.max(0, first - 1)):rskip(math.max(0, self._tail - last - 1)) 1021 end 1022 1023 --- Returns true if any of the items in the iterator match the given predicate. 1024 --- 1025 ---@param pred fun(...):boolean Predicate function. Takes all values returned from the previous 1026 --- stage in the pipeline as arguments and returns true if the 1027 --- predicate matches. 1028 function Iter:any(pred) 1029 local any = false 1030 1031 --- Use a closure to handle var args returned from iterator 1032 local function fn(...) 1033 if select(1, ...) ~= nil then 1034 if pred(...) then 1035 any = true 1036 else 1037 return true 1038 end 1039 end 1040 end 1041 1042 while fn(self:next()) do 1043 end 1044 return any 1045 end 1046 1047 --- Returns true if all items in the iterator match the given predicate. 1048 --- 1049 ---@param pred fun(...):boolean Predicate function. Takes all values returned from the previous 1050 --- stage in the pipeline as arguments and returns true if the 1051 --- predicate matches. 1052 function Iter:all(pred) 1053 local all = true 1054 1055 local function fn(...) 1056 if select(1, ...) ~= nil then 1057 if not pred(...) then 1058 all = false 1059 else 1060 return true 1061 end 1062 end 1063 end 1064 1065 while fn(self:next()) do 1066 end 1067 return all 1068 end 1069 1070 --- Drains the iterator and returns the last item. 1071 --- 1072 --- Example: 1073 --- 1074 --- ```lua 1075 --- 1076 --- local it = vim.iter(vim.gsplit('abcdefg', '')) 1077 --- it:last() 1078 --- -- 'g' 1079 --- 1080 --- local it = vim.iter({ 3, 6, 9, 12, 15 }) 1081 --- it:last() 1082 --- -- 15 1083 --- 1084 --- ``` 1085 --- 1086 ---@see |Iter:rpeek()| 1087 --- 1088 ---@return any 1089 function Iter:last() 1090 local last = self:next() 1091 local cur = self:next() 1092 while cur do 1093 last = cur 1094 cur = self:next() 1095 end 1096 return last 1097 end 1098 1099 ---@private 1100 function ArrayIter:last() 1101 if self._head >= self._tail then 1102 return nil 1103 end 1104 local inc = self._head < self._tail and 1 or -1 1105 local v = self._table[self._tail - inc] 1106 self._head = self._tail 1107 return v 1108 end 1109 1110 --- Yields the item index (count) and value for each item of an iterator pipeline. 1111 --- 1112 --- For list tables, this is more efficient: 1113 --- 1114 --- ```lua 1115 --- vim.iter(ipairs(t)) 1116 --- ``` 1117 --- 1118 --- instead of: 1119 --- 1120 --- ```lua 1121 --- vim.iter(t):enumerate() 1122 --- ``` 1123 --- 1124 --- Example: 1125 --- 1126 --- ```lua 1127 --- 1128 --- local it = vim.iter(vim.gsplit('abc', '')):enumerate() 1129 --- it:next() 1130 --- -- 1 'a' 1131 --- it:next() 1132 --- -- 2 'b' 1133 --- it:next() 1134 --- -- 3 'c' 1135 --- 1136 --- ``` 1137 --- 1138 ---@return Iter 1139 function Iter:enumerate() 1140 local i = 0 1141 return self:map(function(...) 1142 i = i + 1 1143 return i, ... 1144 end) 1145 end 1146 1147 ---@private 1148 function ArrayIter:enumerate() 1149 local inc = self._head < self._tail and 1 or -1 1150 for i = self._head, self._tail - inc, inc do 1151 local v = self._table[i] 1152 self._table[i] = pack(i, v) 1153 end 1154 return self 1155 end 1156 1157 --- Creates a new Iter object from a table or other |iterable|. 1158 --- 1159 ---@param src table|function Table or iterator to drain values from 1160 ---@return Iter 1161 ---@private 1162 function Iter.new(src, ...) 1163 local it = {} 1164 if type(src) == 'table' then 1165 local mt = getmetatable(src) 1166 if mt and type(mt.__call) == 'function' then 1167 ---@private 1168 it._next = function() 1169 return src() 1170 end 1171 1172 setmetatable(it, Iter) 1173 return it 1174 end 1175 1176 local t = {} 1177 1178 -- O(n): scan the source table to decide if it is an array (only positive integer indices). 1179 for k, v in pairs(src) do 1180 if type(k) ~= 'number' or k <= 0 or math.floor(k) ~= k then 1181 return Iter.new(pairs(src)) 1182 end 1183 t[#t + 1] = v -- Coerce to list-like table. 1184 end 1185 return ArrayIter.new(t) 1186 end 1187 1188 if type(src) == 'function' then 1189 local s, var = ... 1190 1191 --- Use a closure to handle var args returned from iterator 1192 local function fn(...) 1193 -- Per the Lua 5.1 reference manual, an iterator is complete when the first returned value is 1194 -- nil (even if there are other, non-nil return values). See |for-in|. 1195 if select(1, ...) ~= nil then 1196 var = select(1, ...) 1197 return ... 1198 end 1199 end 1200 1201 ---@private 1202 it._next = function() 1203 return fn(src(s, var)) 1204 end 1205 1206 setmetatable(it, Iter) 1207 else 1208 error('src must be a table or function') 1209 end 1210 return it 1211 end 1212 1213 --- Create a new ArrayIter 1214 --- 1215 ---@param t table Array-like table. Caller guarantees that this table is a valid array. Can have 1216 --- holes (nil values). 1217 ---@return Iter 1218 ---@private 1219 function ArrayIter.new(t) 1220 local it = {} 1221 it._table = t 1222 it._head = 1 1223 it._tail = #t + 1 1224 setmetatable(it, ArrayIter) 1225 return it 1226 end 1227 1228 return setmetatable(M, { 1229 __call = function(_, ...) 1230 return Iter.new(...) 1231 end, 1232 }) --[[@as IterMod]]