├── .github └── workflows │ └── docs.yml ├── .gitignore ├── LICENSE ├── README.md ├── benchmarks ├── closures.nim ├── config.nims └── timeit.nim ├── slicerator.nimble ├── src ├── slicerator.nim └── slicerator │ ├── chainimpl.nim │ ├── closures.nim │ └── itermacros.nim └── tests ├── config.nims ├── tchain.nim ├── test1.nim └── titermacros.nim /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | push: 4 | branches: 5 | - master 6 | env: 7 | nim-version: 'stable' 8 | nim-src: src/${{ github.event.repository.name }}.nim 9 | deploy-dir: .gh-pages 10 | jobs: 11 | docs: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: jiro4989/setup-nim-action@v1 16 | with: 17 | nim-version: ${{ env.nim-version }} 18 | - run: nimble install -Y 19 | - run: nimble doc --index:on --project --git.url:https://github.com/${{ github.repository }} --git.commit:master --out:${{ env.deploy-dir }} ${{ env.nim-src }} 20 | - name: "Copy to index.html" 21 | run: cp ${{ env.deploy-dir }}/${{ github.event.repository.name }}.html ${{ env.deploy-dir }}/index.html 22 | - name: Deploy documents 23 | uses: peaceiris/actions-gh-pages@v3 24 | with: 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | publish_dir: ${{ env.deploy-dir }} 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.* 3 | !*/ 4 | nimcache/ 5 | nimblecache/ 6 | htmldocs/ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jason Beetham 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # slicerator 2 | Iterator package aimed at more ergonomic and efficient iterators. 3 | Everything ranging from an `[]` iterator to converting inline iterators to closures. 4 | 5 | Docs can be found [here](https://www.jasonbeetham.ca/slicerator) 6 | -------------------------------------------------------------------------------- /benchmarks/closures.nim: -------------------------------------------------------------------------------- 1 | import slicerator 2 | import ../src/closures 3 | from itermacros as im import nil 4 | import timeit 5 | import std/[random, strutils, sequtils, streams] 6 | 7 | type MyObject = object 8 | a, b: int 9 | c: string 10 | d: seq[int] 11 | 12 | block: 13 | const runs = 1000 14 | 15 | timeit "closures", runs: 16 | var myData = newSeq[MyObject](300000) 17 | do: 18 | let myClos = myData.items.asClosure 19 | for x in myClos(): 20 | let y = x 21 | discard y 22 | 23 | timeit "inline iterators", runs: 24 | var myData = newSeq[MyObject](300000) 25 | do: 26 | for x in myData.items: 27 | let y = x 28 | discard y 29 | 30 | block: 31 | const runs = 1000 32 | var data = newSeq[string](300000) 33 | for x in data.mitems: 34 | x = $rand(0..1000000) 35 | 36 | proc filterProc(a: int): bool = a > 3000 37 | proc mapProc(a: int): int = a * 30 38 | 39 | timeit "closure complex statement", runs: 40 | var presentData = data 41 | do: 42 | for val in presentData.items.asClosure.map(parseInt).filter(filterProc).map(mapProc): 43 | let newVal = val 44 | discard newVal 45 | 46 | timeit "manual move closure complex statement", runs: 47 | var presentData = data 48 | do: 49 | var 50 | closA = presentData.items.asClosure 51 | closB = map(move closA, parseInt) 52 | closC = filter(move closB, filterProc) 53 | closD = map(move closC, mapProc) 54 | for val in closD: 55 | let newVal = val 56 | discard newVal 57 | 58 | timeit "itermacros and asClosure", runs: 59 | var presentData = data 60 | do: 61 | var closA = im.map(im.filter(im.map(presentData.items, parseInt), filterProc), mapProc).asClosure 62 | for val in closA: 63 | let newVal = val 64 | discard newVal 65 | 66 | timeit "sequtils complex statement", runs: 67 | var presentData = data 68 | do: 69 | for val in presentData.map(parseInt).filter(filterProc).map(mapProc): 70 | let newVal = val 71 | discard newVal 72 | 73 | timeit "manual statements", runs: 74 | var presentData = data 75 | do: 76 | for x in presentData: 77 | var myInt = parseInt(x) 78 | if myInt > 3000: 79 | let newVal = myInt * 30 80 | discard newVal 81 | 82 | timeit "chain macro", runs: 83 | var presentData = data 84 | do: 85 | for x in chain presentData.items.map(parseInt(x)).filter(x > 3000).map(x * 30): 86 | let newVal = x 87 | discard newVal 88 | 89 | timeit "iter macro", runs: 90 | var presentData = data 91 | do: 92 | for x in im.map(im.filter(im.map(presentData.items, parseInt), filterProc), mapProc): 93 | let newVal = x 94 | discard newVal 95 | 96 | 97 | when defined(writeoutput): 98 | block: 99 | var presentData = data 100 | var myFs = newFileStream("/tmp/closureComplexStmt.tst", fmWrite) 101 | for val in presentData.items.asClosure.map(parseInt).filter(filterProc).map(mapProc): 102 | myFs.writeLine($val) 103 | myFs.close() 104 | 105 | 106 | block: 107 | var presentData = data 108 | var myFs = newFileStream("/tmp/closureMoveComplexStmt.tst", fmWrite) 109 | var 110 | closA = presentData.items.asClosure 111 | closB = map(move closA, parseInt) 112 | closC = filter(move closB, filterProc) 113 | closD = map(move closC, mapProc) 114 | for val in closD: 115 | myFs.writeLine($val) 116 | myFs.close() 117 | 118 | block: 119 | var presentData = data 120 | var myFs = newFileStream("/tmp/manualloop.tst", fmWrite) 121 | for x in presentData: 122 | var myInt = parseInt(x) 123 | if myInt > 3000: 124 | let newVal = myInt * 30 125 | myFs.writeLine($newVal) 126 | myFs.close() 127 | 128 | -------------------------------------------------------------------------------- /benchmarks/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") 2 | --gc:arc 3 | --define:danger 4 | --define:lto 5 | -------------------------------------------------------------------------------- /benchmarks/timeit.nim: -------------------------------------------------------------------------------- 1 | import std/[monotimes, times] 2 | export `$` 3 | template timeit*(name: string, myRuns: int, pre, body: untyped) = 4 | var totalTime: Duration 5 | let myTimeProc = proc(): Duration = 6 | pre 7 | let start = getMonoTime() 8 | body 9 | result = getMonoTime() - start 10 | for _ in 0..= 2.0.0" 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/slicerator.nim: -------------------------------------------------------------------------------- 1 | import std/[macros, enumerate, macrocache, typetraits, genasts, strformat] 2 | import slicerator/closures 3 | export closures 4 | when defined(nimdoc): 5 | import slicerator/itermacros 6 | 7 | iterator `[]`*[T](a: openArray[T], slice: Slice[int]): T = 8 | ## Immutable slice iteration over an `openarray` 9 | for i in slice.a..slice.b: 10 | yield a[i] 11 | 12 | iterator `[]`*[T](a: openArray[T], slice: HSlice[int, BackwardsIndex]): T = 13 | ## Immutable slice iteration over an `openarray`, taking `BackwardsIndex` 14 | for x in a[slice.a .. a.len - slice.b.int]: 15 | yield x 16 | 17 | iterator pairs*[T](a: openArray[T], slice: HSlice[int, int]): (int, T) = 18 | ## Immutable slice iteration over an `openarray`, yielding index and element 19 | var i = slice.a 20 | for x in a[slice.a .. slice.b.int]: 21 | yield (i, x) 22 | inc i 23 | 24 | iterator pairs*[T](a: openArray[T], slice: HSlice[int, BackwardsIndex]): (int, T) = 25 | ## Immutable slice iteration over an `openarray`, taking `BackwardsIndex`, yielding index and element 26 | for x in a.pairs(slice.a .. a.len - slice.b.int): 27 | yield x 28 | 29 | iterator `{}`*[T](a: var openArray[T], slice: Slice[int]): var T = 30 | ## Mutable slice iteration over an `openarray` 31 | for i in slice.a..slice.b: 32 | yield a[i] 33 | 34 | iterator `{}`*[T](a: var openArray[T], slice: HSlice[int, BackwardsIndex]): var T = 35 | ## Mutable slice iteration over an `openarray`, taking `BackwardsIndex` 36 | for ch in a{slice.a .. a.len - slice.b.int}: 37 | yield ch 38 | 39 | iterator revItems*[T](a: openArray[T]): T = 40 | ## Reversed immutable items over an `openArray` 41 | for x in countdown(a.high, 0): 42 | yield a[x] 43 | 44 | iterator revMitems*[T](a: var openArray[T]): var T = 45 | ## Reversed mutable items over an `openArray` 46 | for x in countdown(a.high, 0): 47 | yield a[x] 48 | 49 | import slicerator/chainimpl 50 | export chain, colChain 51 | 52 | iterator findAll*[T](a: openArray[T], val: T): int = 53 | ## Iterates the `openArray` yielding indices that match `val` 54 | for i, x in a: 55 | if x == val: 56 | yield i 57 | 58 | iterator mFindAll*[T](a: var openArray[T], val: T): var T = 59 | ## Iterates the `openarray` yielding mutable values that match `val` 60 | for i, x in a: 61 | if x == val: 62 | yield a[i] 63 | 64 | iterator rFindAll*[T](a: openArray[T], val: T): int = 65 | ## Iterates the `openArray` backwards yield all indices that match `val` 66 | var i = a.high 67 | for x in a.revItems: 68 | if x == val: 69 | yield i 70 | dec i 71 | 72 | iterator rMFindAll*[T](a: var openArray[T], val: T): var T = 73 | ## Iterates the `openArray` backwards yielding all mutable values that match `val` 74 | for x in a.revMitems: 75 | if x == val: 76 | yield x 77 | 78 | template forMItems*[T](a: var openArray[T], indexName, valName, body: untyped): untyped = 79 | ## Sugar for iterating over mutable entries getting their indices and value 80 | var index = 0 81 | for valname in a.mitems: 82 | let indexName = index 83 | body 84 | inc index 85 | 86 | macro groups*(body: ForLoopStmt): untyped = 87 | ## Allows iterating over a user defined selection of values in an open array 88 | runnableExamples: 89 | for x, y in groups [100, 300]: 90 | assert [x, y] == [100, 300] 91 | for x, y in groups [10, 20, 30, 60, 50, 100, 90, 180]: 92 | assert x * 2 == y 93 | 94 | let 95 | val = body[^2][1] 96 | valueName = ident"value" 97 | assignment = newStmtList() 98 | count = newLit(body.len - 2) 99 | packed = # Do we yield every 4 steps or every step after the 4th 100 | if body[^2].len > 2: 101 | body[^2][^1] 102 | else: 103 | newLit(false) 104 | for i, x in body[0..^3]: 105 | assignment.add newLetStmt(x, nnkBracketExpr.newTree(valueName, newLit(i))) 106 | result = genAst(val, valueName, assignment, packed, count, bod = body[^1]): 107 | block: 108 | var valueName: array[count, elementType(val)] 109 | var pos = 0 110 | for x in val: 111 | when packed: 112 | if pos < count: # We need to buffer them all for packed method 113 | valueName[pos] = x 114 | inc pos 115 | if pos == count: 116 | assignment 117 | bod 118 | else: 119 | for i in 1..= count: 128 | assignment 129 | bod 130 | pos = 0 131 | 132 | template map*[T; Y](i: iterable[T], p: proc(x: T): Y): untyped = 133 | # Applys procedure to an iterator's yielded values 134 | var res: seq[Y] 135 | for x in i: 136 | res.add p(x) 137 | res 138 | 139 | template skipIter*(iter, val: untyped, toSkip: Natural, body: untyped) = 140 | ## Skip over a certain number of iterations 141 | for i, x in enumerate(iter): 142 | if i > toSkip: 143 | let val = x 144 | body 145 | 146 | template iterRange*(iter, val: untyped, rng: Slice[int], body: untyped) = 147 | ## Only runs code for a given range of iterations 148 | for i, x in enumerate(iter): 149 | if i in rng: 150 | let val = x 151 | body 152 | 153 | macro zip*(others: varargs[untyped]): untyped = 154 | ## Iterates over the iterators making a `seq[tuple]` of them all, 155 | ## `tuple` coresponds to the passed in iterators 156 | runnableExamples: 157 | assert zip([10, 20, 30], ['a', 'b', 'c']) == @[(10, 'a'), (20, 'b'), (30, 'c')] 158 | 159 | # Ideally this would take `iterable`, but nooooo not allowed 160 | if others.len == 1: 161 | error("zipping requires atleast 2 iterators", others) 162 | elif others.len == 0: 163 | error("zipping nothing is silly") 164 | 165 | let 166 | first = others[0] 167 | tupleConstr = nnkTupleConstr.newTree() 168 | elemType = bindSym"elementType" 169 | for other in others: 170 | tupleConstr.add newCall(elemType, other) 171 | 172 | let 173 | iSym = genSym(nskVar, "i") 174 | resSym = genSym(nskVar, "res") 175 | 176 | # First setup the first value 177 | result = genAst(first, iSym, resSym, tupleConstr): 178 | var resSym: seq[tupleConstr] 179 | var iSym = 0 180 | for x in first: 181 | resSym.add default(tupleConstr) 182 | resSym[^1][0] = x 183 | inc iSym 184 | iSym = 0 185 | 186 | for tuplInd, other in others: 187 | if tuplInd > 0: # For each other iterator add another 188 | result.add: 189 | genAst(iSym, resSym, tuplInd, other): 190 | for x in other: 191 | if iSym < resSym.len: 192 | resSym[iSym][tuplInd] = x 193 | else: 194 | break 195 | inc iSym 196 | resSym.setLen(iSym) 197 | iSym = 0 198 | 199 | result.add: 200 | genAst(resSym): 201 | resSym 202 | 203 | 204 | macro map*(forLoop: ForLoopStmt): untyped = 205 | ## Iterator based map, iterates over all values yielding the expression applied to the values. 206 | ## Can be used `for x in map(y, x + 1)` or `for i, x in map(y, x + 3)`. 207 | runnableExamples: 208 | let data = [10, 20, 30] 209 | for i, x in map(data, x * 3): 210 | assert data[i] * 3 == x 211 | 212 | for i, x in forLoop: 213 | if i > 1 and x.kind == nnkIdent: 214 | error("Invalid number of for loop variables for 'map'", x) 215 | 216 | if forLoop[^2].len != 3: 217 | error("No expression to map provided.", forLoop[^2]) 218 | 219 | let 220 | iter = forLoop[^2][1] 221 | expr = forLoop[^2][2] 222 | body = forLoop[^1] 223 | if forLoop[1].kind == nnkIdent: 224 | let 225 | indField = forLoop[0] 226 | iterField = forLoop[1] 227 | result = genAst(iter, expr, iterField, body, indField): 228 | block: 229 | var indField = 0 230 | for iterField in iter: 231 | let iterField = expr 232 | body 233 | inc indField 234 | 235 | else: 236 | let iterField = forLoop[0] 237 | result = genAst(iter, expr, iterField, body): 238 | for iterField in iter: 239 | let iterField = expr 240 | body 241 | 242 | macro filter*(forLoop: ForLoopStmt): untyped = 243 | ## Iterator based 'filter', runs the iterator yielding only on those that match the expression. 244 | ## Can be used `for x in filter(y, x == 1)` or `for i, x in filter(y, x == 3)`. 245 | runnableExamples: 246 | let data = [10, 20, 30] 247 | for i, x in filter(data, x == 10): 248 | assert x == 10 249 | 250 | for i, x in forLoop: 251 | if i > 1 and x.kind == nnkIdent: 252 | error("Invalid number of for loop variables for 'filter'", x) 253 | 254 | if forLoop[^2].len != 3: 255 | error("No expression to filter provided.", forLoop[^2]) 256 | 257 | let 258 | iter = forLoop[^2][1] 259 | expr = forLoop[^2][2] 260 | body = forLoop[^1] 261 | if forLoop[1].kind == nnkIdent: 262 | let 263 | indField = forLoop[0] 264 | iterField = forLoop[1] 265 | result = genAst(iter, expr, iterField, body, indField): 266 | block: 267 | var indField = 0 268 | for iterField in iter: 269 | if expr: 270 | body 271 | inc indField 272 | 273 | else: 274 | let iterField = forLoop[0] 275 | result = genAst(iter, expr, iterField, body): 276 | for iterField in iter: 277 | if expr: 278 | body 279 | 280 | macro zipIter*(forBody: ForLoopStmt): untyped = 281 | ## A version of `zip` that captures iterators as closures which can improve speed and 282 | ## reduce memory usage. 283 | ## Supports `for (x, y) in zipIter(a.items, b.items)` and `for x, y in zipIter(a.items, b.items)`. 284 | runnableExamples: 285 | let 286 | a = [10, 20, 30] 287 | b = "abcdef" 288 | var count = 0 289 | for (x, y) in zipIter(a.items, b.items): 290 | echo x, y # should run 3 times 291 | inc count 292 | assert count == 3 293 | 294 | 295 | let 296 | isVarTupl = forBody[0].kind == nnkVarTuple # Is it doing `(x, y) in zipiter`? 297 | got = forBody[^2].len - 1 # How many iterators did we get 298 | expected = # How many fields were passed 299 | if isVarTupl: 300 | forBody[0].len - 1 301 | else: 302 | forBody[0..^3].len 303 | 304 | if got != expected: 305 | error(fmt"Expecting {expected} iterators, but got {got}.", forBody[0]) 306 | 307 | if forBody[^2].len <= 2: 308 | error("Cannot zip a single iterator.", forBody[^2]) 309 | 310 | let asClos = bindSym"asClosure" 311 | var 312 | closNames: seq[NimNode] 313 | closDecls = newStmtList() 314 | 315 | for x in forBody[^2][1..^1]: # Convert all iters to closures 316 | let name = genSym(nskLet, "closIter") 317 | closNames.add name 318 | closDecls.add newLetStmt(name, newCall(asClos, x)) 319 | 320 | template finished(n: NimNode): untyped = newCall("finished", n) # Checks if iterator is finished 321 | 322 | var 323 | isFin = finished closNames[0] # are we there yet 324 | asgn = newStmtList() # The assignments to values before iter 325 | 326 | let varName = 327 | if isVarTupl: 328 | forBody[0][0] 329 | else: 330 | forBody[0] 331 | asgn.add newLetStmt(varName, newCall(closNames[0])) # first value is special kinda 332 | 333 | for name in closNames[1..^1]: 334 | let varName = 335 | if isVarTupl: 336 | forBody[0][asgn.len] 337 | else: 338 | forBody[asgn.len] 339 | asgn.add newLetStmt(varName, newCall(name)) 340 | isFin = infix(isFin, "or", finished name) # If any are finished we exit instead of yielding 341 | 342 | result = genAst(asgn, isFin, body = forBody[^1], closDecls): 343 | closDecls 344 | while(true): 345 | asgn 346 | if isFin: # Closures need to be called one more time to be "finished", which is why not `while(isFin)` 347 | break 348 | body 349 | -------------------------------------------------------------------------------- /src/slicerator/chainimpl.nim: -------------------------------------------------------------------------------- 1 | import std/[macros, genasts, strutils, strformat, sugar] 2 | 3 | type 4 | ChainableOps = enum 5 | coFilter = "filter" 6 | coMap = "map" 7 | coUnpack = "unpack" 8 | 9 | ChainOp = object 10 | op: NimNode 11 | isIter: bool 12 | kind: ChainableOps 13 | val: NimNode ## Only used for deepest 14 | 15 | macro unpackCheck(left: untyped, right: typed): untyped = 16 | ## Internal macro for emitting helpful errors on unpacking attempts 17 | let rightImpl = right.getType 18 | if rightImpl.len == 0 or not rightImpl[0].eqIdent"tuple": 19 | error(fmt"Can only unpack 'tuples': '{right.repr}' is '{rightImpl.repr}'", right) 20 | elif left.len != rightImpl.len - 1: 21 | error(fmt"Expected {right.getType.len - 1} variables to unpack into, but got {left.len}.", left[0]) 22 | else: 23 | result = nnkLetSection.newTree(nnkVarTuple.newNimNode) 24 | result[0].add left[0..^1] 25 | result[0].add newEmptyNode() 26 | result[0].add right 27 | 28 | proc getOps(call: NimNode): seq[ChainOp] = 29 | var n = call[1] 30 | while n.kind in {nnkCall, nnkDotExpr}: 31 | # Recurse down statement tree 32 | try: 33 | if n[0].kind notin {nnkCall, nnkDotExpr}: 34 | # it's in `lines("test.txt")` format 35 | result.add ChainOp(isIter: true, val: n) 36 | break 37 | elif n[0][0].kind notin {nnkCall, nnkDotExpr}: 38 | # it's in `"test.txt".lines()` format 39 | result.add ChainOp(isIter: true, val: n[0]) 40 | else: 41 | # It's a chained operation 42 | let opKind = parseEnum[ChainableOps](n[0][^1].strVal) 43 | result.add: 44 | case opKind 45 | of coUnpack: 46 | ChainOp(isIter: false, kind: opKind, op: newStmtList(n[1..^1])) 47 | else: 48 | ChainOp(isIter: false, kind: opKind, op: n[^1]) 49 | n = n[0][0] 50 | except: 51 | error(fmt"Invalid operation {n.repr}.", n) 52 | 53 | proc addOp(n, fieldName: NimNode, chainOp: ChainOp) = 54 | let op = chainOp.op 55 | 56 | proc addOpImpl: NimNode = 57 | case chainOp.kind: 58 | of coMap: 59 | genAst(n, fieldName, op): 60 | let fieldName = op 61 | of coFilter: 62 | # Add discard so we can add later 63 | genAst(n, fieldName, op): 64 | if op: discard 65 | of coUnpack: 66 | var varTup = nnkTupleConstr.newTree(op[0..^1]) 67 | newCall(bindSym"unpackCheck", varTup, fieldName) 68 | 69 | if n[^1].kind in {nnkLetSection, nnkCall}: 70 | n.add addOpImpl() 71 | elif n[^1].kind == nnkDiscardStmt: 72 | n[^1] = addOpImpl() 73 | else: 74 | n[^1].addOp(fieldName, chainOp) 75 | 76 | proc addBody(n, body: NimNode) = 77 | if n[^1].kind == nnkDiscardStmt: 78 | n[^1] = body 79 | elif n[^1].kind == nnkLetSection: 80 | n.add body 81 | else: 82 | n[^1].addBody(body) 83 | 84 | macro chain*(forloop: ForLoopStmt): untyped = 85 | ## Allows functional like chaining to iterators, 86 | ## presently supports `unpack`, `map`, `filter`. 87 | runnableExamples: 88 | var a = [10, 20, 30, 40, 50, 60] 89 | for i, x in chain a.items.filter(i > a.len div 2).map(x * 10): 90 | assert a[i] * 10 == x 91 | 92 | for i, x in chain a.pairs.unpack(y, z).filter(y > 3): 93 | assert i > 3 94 | assert i == y 95 | assert z in [40, 50, 60] 96 | 97 | 98 | let passedVars = block: 99 | var val = 0 100 | for n in forLoop: 101 | if n.kind != nnkIdent: 102 | break 103 | inc val 104 | val 105 | 106 | if passedVars > 2: 107 | error("Invalid variable count passed to 'chain'.", forLoop[2]) 108 | 109 | var ops = forloop[^2].getOps 110 | let 111 | varName = 112 | if passedVars == 2: 113 | forLoop[1] 114 | else: 115 | forLoop[0] 116 | body = forLoop[^1] 117 | iter = ops.pop 118 | lastOp = ops.pop 119 | 120 | case lastOp.kind: # Last op is kinda special 121 | of coMap: 122 | result = genAst(iter = iter.val, varName, op = lastOp.op): 123 | for varName in iter: 124 | let varName = op 125 | of coFilter: 126 | result = genAst(iter = iter.val, varName, op = lastOp.op): 127 | for varName in iter: 128 | if op: discard 129 | of coUnpack: 130 | var varTup = nnkTupleConstr.newTree(lastOp.op[0..^1]) 131 | varTup = newCall(bindSym"unpackCheck", varTup, varName) 132 | result = genAst(iter = iter.val, varName, varTup): 133 | for varName in iter: 134 | varTup 135 | 136 | while ops.len > 0: # Add remaining ops 137 | result.addOp(varName, ops.pop) 138 | result.addBody(body) 139 | if passedVars == 2: # We have an index variable, so emit calls for it 140 | result[^1].add newCall("inc", forLoop[0]) 141 | result = nnkBlockStmt.newTree(newEmptyNode(), newStmtList(newVarStmt(forLoop[0], newLit(0)), result)) 142 | 143 | macro colChain*(forloop: ForLoopStmt): untyped = 144 | ## Wrapper for collecting `chain`. 145 | ## Works the same as chain but collects the stmt of the for loop 146 | result = forLoop.copyNimTree 147 | if result[1].kind != nnkCommand: 148 | error("Too many iterator parameters provided", forLoop) 149 | result[1][0] = bindSym"chain" 150 | result = newCall(bindSym"collect", result) 151 | -------------------------------------------------------------------------------- /src/slicerator/closures.nim: -------------------------------------------------------------------------------- 1 | ## This module implements a bunch of sugar for closure iterators. 2 | 3 | import std/[macros, sugar, genasts, compilesettings] 4 | type Iterator[T] = (iterator: T) or (iterator: lent T) 5 | 6 | proc generateClosure(iter: NimNode): NimNode = 7 | let 8 | iter = 9 | if iter[0].kind == nnkIteratorDef: 10 | copyNimTree(iter[^1]) 11 | else: 12 | copyNimTree(iter) 13 | impl = getImpl(iter[0]) 14 | 15 | for i in countdown(impl[4].len - 1, 0): 16 | let x = impl[4][i] 17 | if x.eqIdent("closure"): 18 | error("cannot convert closure to closure", iter[0]) 19 | 20 | let 21 | procName = genSym(nskProc, "closureImpl") 22 | call = newCall(procName) 23 | 24 | for i in 1 .. iter.len - 1: # Unpacks the values if they're converted 25 | if iter[i].kind == nnkHiddenStdConv: 26 | iter[i] = iter[i][^1] 27 | call.add iter[i] 28 | 29 | var paramList = collect(newSeq): 30 | for i, x in impl[3]: 31 | let def = x.copyNimTree() 32 | if i > 0: 33 | def[^2] = nnkCommand.newTree(ident"sink", getTypeInst(iter[i])) 34 | def 35 | 36 | var vars = 1 # For each variable 37 | for i, defs in paramList: 38 | if i > 0: 39 | for j, def in defs[0..^3]: 40 | defs[j] = genSym(nskParam, $def) # Replace params with new symbol 41 | iter[vars] = defs[j] # Changes call parameter aswell 42 | inc vars 43 | 44 | let 45 | res = ident"result" 46 | body = genast(iter, res): 47 | when compiles(res = iterator(): lent typeof(iter) = 48 | for x in iter: 49 | yield x 50 | ): 51 | res = iterator(): lent typeof(iter) = 52 | for x in iter: 53 | yield x 54 | else: 55 | res = iterator(): typeof(iter) = 56 | for x in iter: 57 | yield x 58 | 59 | paramList[0] = ident"auto" # Set return type to auto 60 | 61 | result = newProc(procName, paramList, body) # make proc 62 | result[4] = nnkPragma.newTree(ident"inline") 63 | result = nnkBlockStmt.newTree(newEmptyNode(), newStmtList(result, call)) # make block statment 64 | 65 | macro asClosure*(iter: iterable): untyped = 66 | ## Takes a call to an iterator and captures it in a closure iterator for easy usage. 67 | iter.generateClosure() 68 | 69 | proc reset*[T](clos: var Iterator[T]) = 70 | ## Resets the closure so iterations can continue 71 | runnableExamples: 72 | var a = @[10, 20].items.asClosure 73 | for _ in a(): 74 | discard 75 | assert a.finished 76 | a.reset 77 | assert not a.finished 78 | 79 | cast[ptr UncheckedArray[int]](clos.rawEnv)[1] = 0 80 | 81 | iterator iterThenReset*[T](clos: var iterator: T): T = 82 | ## Iterates over `closure` resetting after done 83 | runnableExamples: 84 | var a = @[10, 20].items.asClosure 85 | for _ in a.iterThenReset: 86 | discard 87 | assert not a.finished 88 | for x in clos(): 89 | yield x 90 | reset(clos) 91 | 92 | iterator iterThenReset*[T](clos: var iterator: lent T): T = 93 | ## same as the other but for `lent T` result 94 | reset(clos) 95 | 96 | proc peek*[T](clos: var Iterator[T]): T {.error: "`peek` was incorrectly implemented, it's more complicated than thought".}= 97 | ## Gets the next value from a closure iterator. 98 | var data = default(array[10, int]) # Appears for our closures it's always 8 ints? 99 | 100 | let envPointer = cast[ptr UncheckedArray[int]](clos.rawEnv) 101 | copyMem(data.addr, envPointer[1].addr, sizeof(data)) 102 | result = clos() 103 | copyMem(envPointer[1].addr, data.addr, sizeof(data)) 104 | 105 | proc map*[T, Y](iter: Iterator[T], mapProc: proc(a: T): Y): iterator: Y = 106 | result = iterator: Y = 107 | for x in iter(): 108 | yield mapProc(x) 109 | 110 | proc collect*[T](iter: Iterator[T]): seq[T] = 111 | for x in iter: 112 | result.add x 113 | 114 | proc group*[T](iter: Iterator[T], count: static int): iterator: (int, array[count, T]) = 115 | result = iterator: (int, array[count, T]) = 116 | var result: (int, array[count, T]) 117 | for x in iter(): 118 | result[1][result[0]] = x 119 | inc result[0] 120 | if result[0] == count: 121 | yield result 122 | reset result 123 | if result[0] > 0: 124 | yield result 125 | 126 | proc filter*[T](iter: Iterator[T], cond: proc(a: T): bool): iterator: T = 127 | result = iterator: T = 128 | for x in iter: 129 | if cond(x): 130 | yield x 131 | 132 | -------------------------------------------------------------------------------- /src/slicerator/itermacros.nim: -------------------------------------------------------------------------------- 1 | ## itermacros 2 | ## ========== 3 | ## 4 | ## The **itermacros** module provides a set of templates for working with 5 | ## iterators and performing common operations such as filtering, mapping, 6 | ## accumulation, and element retrieval. These templates offer a convenient way 7 | ## to manipulate and process collections of data in a concise and expressive 8 | ## manner. They provide a declarative, more readable and arguably less 9 | ## error-prone alternative to writing manual imperative loops. 10 | ## 11 | runnableExamples: 12 | import std/[strutils, options, tables, sets] 13 | 14 | # Find the first element in a sequence of the transformed initial numbers 15 | # that is bigger than 35. 16 | # Note using `Slice[int].items` instead of `CountUp`. 17 | assert (-25..25).items.mapIt(it * 10 div 7).findIt(it > 35) == none(int) 18 | 19 | # Filter a table of philosophers by country of origin, compose a sentence 20 | # and join each to a string. 21 | 22 | let philosophers: Table[string, string] = { 23 | "Plato": "Greece", "Aristotle": "Greece", "Socrates": "Greece", 24 | "Confucius": "China", "Descartes": "France"}.toTable() 25 | 26 | const Phrase = "$1 is a famous philosopher from $2." 27 | let facts = philosophers.pairs() 28 | .filterIt(it[1] != "Greece") 29 | .mapIt([it[0], it[1]]) 30 | .mapIt(Phrase % it) 31 | .foldIt("", acc & it & '\n') 32 | assert facts == """ 33 | Confucius is a famous philosopher from China. 34 | Descartes is a famous philosopher from France. 35 | """ 36 | 37 | # Find expensive stocks, convert the company name to uppercase and collect 38 | # to a custom container type. 39 | 40 | let stocks: Table[string, tuple[symbol:string, price:float]] = { 41 | "Pineapple": (symbol: "PAPL", price: 148.32), 42 | "Foogle": (symbol: "FOOGL", price: 2750.62), 43 | "Visla": (symbol: "VSLA", price: 609.89), 44 | "Mehzon": (symbol: "MHZN", price: 3271.92), 45 | "Picohard": (symbol: "PCHD", price: 265.51), 46 | }.toTable() 47 | 48 | let shoutExpensive = stocks.pairs() 49 | .mapIt((name: it[0], price:it[1].price)) 50 | .filterIt(it.price > 1000.0) 51 | .mapIt(it.name).map(toUpperAscii) 52 | .collect(HashSet[string]) 53 | 54 | assert shoutExpensive == ["FOOGLE", "MEHZON"].toHashSet() 55 | 56 | 57 | import std/[macros, genasts, options] 58 | 59 | macro genIter*[T](iter: iterable[T], body: varargs[untyped]): untyped = 60 | ## Macro for generating templates wrapping iterators. 61 | ## Takes in optiona amount of block statements. 62 | ## With one block it's assumed to be the body. 63 | ## With two blocks the first is pre iterator ran code and the second is the body. 64 | ## With three blocks the last is the post iterator ran code, the second is the body, the first is the pre iterator ran code. 65 | 66 | if body.len == 0: 67 | error("Expected 1 or 2 arguments passed.", body) 68 | if body.len > 3: 69 | error("Too many arguments provided.", body) 70 | 71 | let 72 | iter = 73 | if iter.kind != nnkCall: 74 | iter[^1] 75 | else: 76 | iter 77 | 78 | pre = 79 | case body.len 80 | of 2, 3: 81 | body[0] 82 | else: 83 | newStmtList() 84 | 85 | 86 | 87 | post = 88 | case body.len 89 | of 3: 90 | body[^1] 91 | else: 92 | newStmtList() 93 | 94 | body = 95 | case body.len 96 | of 1: 97 | body[0] 98 | of 2, 3: 99 | body[1] 100 | else: 101 | newStmtList() 102 | 103 | let decl = genast(name = genSym(nskIterator, "name")): 104 | iterator name(): auto = 105 | discard 106 | 107 | let call = newCall(iter[0]) 108 | if iter.len > 1: 109 | for i, arg in iter[1..^1]: 110 | let name = ident "arg" & $i 111 | decl.params.add newIdentDefs(name, newCall("typeof", arg)) 112 | decl.add arg 113 | call.add name 114 | 115 | decl[^2] = genast(call, pre, post, body): 116 | pre 117 | for it {.inject.} in call: 118 | body 119 | post 120 | let iterInvoke = copyNimTree(iter) 121 | iterInvoke[0] = decl[0] 122 | result = newStmtList(decl, iterInvoke) 123 | 124 | #-------------------------------------------------------------------------- 125 | # Adaptors 126 | #-------------------------------------------------------------------------- 127 | 128 | when defined(nimdoc): 129 | template map*[T; Y](iter: iterable[T]; fn: proc(x: T): Y): untyped = 130 | ## Transforms elements of the iterator using a mapping function. 131 | ## 132 | ## `map` applies the mapping function to each element of the input 133 | ## iterator, yielding the returned values of that function. 134 | ## 135 | runnableExamples: 136 | let nums = [1, 2, 3, 4, 5] 137 | assert nums.items.map(proc(x: int): int = x * x).collect() == @[1, 4, 9, 16, 25] 138 | else: 139 | template map*[T; Y](iter: iterable[T]; fn: proc(x: T): Y): untyped = 140 | genIter(iter): 141 | yield fn(it) 142 | 143 | when defined(nimdoc): 144 | template map*[T; Y](iter: iterable[T]; fn: proc(x: T): Y {.inline.}): untyped = 145 | ## `map<#map.t,iterable[T],proc(T)>`_ overload that allows using inline 146 | ## procs for the mapping function. 147 | else: 148 | template map*[T; Y](iter: iterable[T]; fn: proc(x: T): Y {.inline.}): untyped = 149 | genIter(iter): 150 | yield fn(it) 151 | 152 | when defined(nimdoc): 153 | template mapIt*[T](iter: iterable[T]; expr: untyped): untyped = 154 | ## Transforms elements of the iterator using an expression. 155 | ## 156 | ## `mapIt` applies the expression `expr` to each element of the input 157 | ## iterator, yielding the results of the evaluation. 158 | ## 159 | runnableExamples: 160 | let nums = [1, 2, 3, 4, 5] 161 | assert nums.items.mapIt(it * 2).collect() == @[2, 4, 6, 8, 10] 162 | else: 163 | template mapIt*[T](iter: iterable[T]; expr: untyped): untyped = 164 | genIter(iter): 165 | yield expr 166 | 167 | when defined(nimdoc): 168 | template filter*[T](iter: iterable[T]; pred: proc(x: T): bool): untyped = 169 | ## Filters elements of the iterator using a predicate function. 170 | ## 171 | ## `filter` yields only the elements of the input iterator for which the 172 | ## predicate function `pred` returns `true`. 173 | ## 174 | runnableExamples: 175 | let nums = [1, 2, 3, 4, 5] 176 | assert nums.items.filter(proc(x: int): bool = x mod 2 == 0).collect() == @[2, 4] 177 | else: 178 | template filter*[T](iter: iterable[T]; pred: proc(x: T): bool): untyped = 179 | genIter(iter): 180 | if pred(it): 181 | yield it 182 | 183 | when defined(nimdoc): 184 | template filterIt*[T](iter: iterable[T]; expr: untyped): untyped = 185 | ## Filters elements of the iterator based on a specified condition. 186 | ## 187 | ## `filterIt` yields only the elements of the input iterator for which the 188 | ## specified expression `expr` evaluates to `true`. 189 | ## 190 | runnableExamples: 191 | let nums = [1, 2, 3, 4, 5] 192 | assert nums.items.filterIt(it mod 2 == 0).collect() == @[2, 4] 193 | else: 194 | template filterIt*[T](iter: iterable[T]; expr: untyped): untyped = 195 | genIter(iter): 196 | if expr: 197 | yield it 198 | 199 | macro genTuple(typ: untyped; amount: static int): untyped = 200 | result = nnkPar.newTree() 201 | for _ in 0..= amount: 256 | yield it 257 | inc counter 258 | 259 | when defined(nimdoc): 260 | template skipWhileIt*[T](iter: iterable[T]; expr: untyped): untyped = 261 | ## Skips elements of the iterator while the specified expression evaluates to 262 | ## `true`. 263 | ## 264 | ## `skipWhileIt` returns an iterator that starts yielding elements from the 265 | ## input iterator from the first element where the given expression 266 | ## evaluates to `false`. 267 | ## 268 | ## Once `expr` returns `false`, all subsequent elements are yielded. 269 | ## 270 | runnableExamples: 271 | let nums = [1, 2, 3, 4, 5] 272 | assert nums.items.skipWhileIt(it < 3).collect() == @[3, 4, 5] 273 | else: 274 | template skipWhileIt*[T](iter: iterable[T]; expr: untyped): untyped = 275 | genIter(iter): 276 | var skipping = true 277 | do: 278 | if skipping: 279 | skipping = expr 280 | if skipping: 281 | continue 282 | yield it 283 | 284 | when defined(nimdoc): 285 | template skipWhile*[T](iter: iterable[T]; pred: proc(x: T): bool): untyped = 286 | ## Skips elements of the iterator while the specified predicate function 287 | ## returns `true`. 288 | ## 289 | ## `skipWhile` returns an iterator that starts yielding elements from the 290 | ## input iterator from the first element where `pred` returns `false`. 291 | ## 292 | ## Once `pred` returns `false`, all subsequent elements are yielded. 293 | ## 294 | runnableExamples: 295 | let nums = [1, 2, 3, 4, 5] 296 | assert nums.items.skipWhile(proc(x: int): bool = x < 3).collect() == @[3, 4, 5] 297 | else: 298 | template skipWhile*[T](iter: iterable[T]; pred: proc(x: T): bool): untyped = 299 | skipWhileIt(iter, pred(it)) 300 | 301 | when defined(nimdoc): 302 | template take*[T](iter: iterable[T]; amount: Natural): untyped = 303 | ## Modifies the iterator to yield a specified number of elements. 304 | ## 305 | ## `take` produces an iterator that yields elements up to the specified 306 | ## `amount`. Once the specified number of elements is reached (or the iterator 307 | ## is exhausted), the iteration stops and no further elements are yielded. 308 | ## 309 | ## .. Note:: If the input iterator contains fewer elements than the specified 310 | ## `amount`, only that number of elements will be yielded. 311 | ## 312 | runnableExamples: 313 | let nums = [1, 2, 3, 4, 5] 314 | assert nums.items.take(3).collect() == @[1, 2, 3] 315 | else: 316 | template take*[T](iter: iterable[T]; amount: Natural): untyped = 317 | genIter(iter): 318 | var counter = 0 319 | do: 320 | if counter >= amount: 321 | break 322 | yield it 323 | inc counter 324 | 325 | when defined(nimdoc): 326 | template takeWhileIt*[T](iter: iterable[T]; expr: untyped): untyped = 327 | ## Modifies the iterator to yield elements as long as the specified expression 328 | ## evaluates to `true`. Once the expression evaluates to `false`, the 329 | ## iteration stops and no further elements are yielded. 330 | ## 331 | runnableExamples: 332 | let nums = [1, 2, 3, 4, 5] 333 | assert nums.items.takeWhileIt(it < 4).collect() == @[1, 2, 3] 334 | else: 335 | template takeWhileIt*[T](iter: iterable[T]; expr: untyped): untyped = 336 | genIter(iter): 337 | if not expr: 338 | break 339 | yield it 340 | 341 | when defined(nimdoc): 342 | template takeWhile*[T](iter: iterable[T]; pred: proc(x: T): bool): untyped = 343 | ## Modifies the iterator to yield elements as long as the specified predicate 344 | ## function returns `true`. Once `pred` returns `false`, the iteration stops 345 | ## and no further elements are yielded. 346 | ## 347 | runnableExamples: 348 | let nums = [1, 2, 3, 4, 5] 349 | assert nums.items.takeWhile(proc(x: int): bool = x < 4).collect() == @[1, 2, 3] 350 | else: 351 | template takeWhile*[T](iter: iterable[T]; pred: proc(x: T): bool): untyped = 352 | takeWhileIt(iter, pred(it)) 353 | 354 | when defined(nimdoc): 355 | template stepBy*[T](iter: iterable[T]; step: Positive): untyped = 356 | ## Modifies the iterator to yield elements stepping by a specified amount. 357 | ## 358 | ## `stepBy` takes an iterable and a step size and returns an iterator that 359 | ## yields elements at the specified step intervals. The resulting iterator 360 | ## skips `step-1` elements after yielding an element. 361 | ## 362 | ## The first element is always yielded, regardless of the step given. 363 | ## 364 | runnableExamples: 365 | let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9] 366 | assert nums.items.stepBy(2).collect() == @[1, 3, 5, 7, 9] 367 | else: 368 | template stepBy*[T](iter: iterable[T]; step: Positive): untyped = 369 | genIter(iter): 370 | var count = step 371 | do: 372 | if count == step: 373 | yield it 374 | count = 0 375 | inc count 376 | 377 | when defined nimdoc: 378 | template enumerate*[T](iter: iterable[T]): untyped = 379 | ## Modifies the iterator to provide the current iteration count and value. 380 | ## 381 | ## `enumerate` takes an iterable and returns an iterator that yields pairs of 382 | ## (count, element) of type `(int, T)`. 383 | ## 384 | ## .. Note:: The count starts from 0 for the first element. 385 | ## 386 | runnableExamples: 387 | let letters = ["Alpha", "Beta", "Gamma"] 388 | assert letters.items.enumerate().collect() == @[(0, "Alpha"), (1, "Beta"), (2, "Gamma")] 389 | else: 390 | template enumerate*[T](iter: iterable[T]): untyped = 391 | genIter(iter): 392 | var count = 0 393 | do: 394 | yield (count, it) 395 | inc count 396 | 397 | when defined(nimdoc): 398 | template flatten*[T](iter: iterable[T]): untyped = 399 | ## Flattens nested iterables into a single iterator. 400 | ## 401 | ## `flatten` is useful in situations when the initial iterator yields 402 | ## containers or iterators and it's required to iterate over the elements of 403 | ## each of them consecutively. 404 | ## 405 | runnableExamples: 406 | let nested = [@[1, 2, 3], @[4, 5], @[6]] 407 | let flattened = nested.items.flatten().collect() 408 | assert flattened == @[1, 2, 3, 4, 5, 6] 409 | else: 410 | template flatten*[T](iter: iterable[T]): untyped = 411 | genIter(iter): 412 | for inner in it: 413 | yield inner 414 | 415 | #-------------------------------------------------------------------------- 416 | # Consumers 417 | #-------------------------------------------------------------------------- 418 | 419 | type 420 | Comparable* = concept a 421 | (a < a) is bool 422 | Summable* = concept a, type T # Additive? 423 | (a + a) is T 424 | Multipliable* = concept a, type T # Multiplicative? 425 | (a * a) is T 426 | 427 | template collect*[T](iter: iterable[T]; size: Natural = 1): seq[T] = 428 | ## Collects the elements of the iterator into a new sequence. 429 | ## 430 | ## `collect` takes an iterator of elements and an optional size parameter. It 431 | ## creates a new sequence and adds each element from the iterator into the 432 | ## sequence. The resulting sequence is returned. 433 | ## 434 | ## For collecting into the user-specified type of container, see 435 | ## `collect<#collect.t,iterable[T],typedesc[U]>`_. 436 | ## 437 | ## .. Note:: The size parameter is optional and can be used to provide an initial 438 | ## capacity for the sequence. If not specified, the sequence will be created 439 | ## with a capacity for one element. 440 | ## 441 | runnableExamples: 442 | let nums = [1, 2, 3, 4, 5] 443 | let plusOne = nums.items.mapIt(it + 1).collect() 444 | assert plusOne == @[2, 3, 4, 5, 6] 445 | var val = newSeqOfCap[T](size) 446 | for x in iter: 447 | val.add x 448 | val 449 | 450 | template collect*[T, U](iter: iterable[T]; t: typeDesc[U]): U = 451 | ## Collects the elements of the iterator into a new container. 452 | ## 453 | ## `collect` takes an iterator and a type `U` for the container to put the 454 | ## elements of the iterator in. It creates a new collection and applies the 455 | ## first available proc of `add`, `incl` or `push`, with the current iteration 456 | ## element as an argument. The resulting collection is returned. 457 | ## 458 | ## .. Note:: The type `U` should be compatible with the elements in the iterator. 459 | ## If `U` is a reference type, a new instance is created. Otherwise, the 460 | ## default value of `U` is used. 461 | ## 462 | runnableExamples: 463 | let nums = [1, 2, 3, 4, 5] 464 | let minusOne = nums.items.mapIt(it - 1).collect(seq[int]) 465 | assert minusOne == @[0, 1, 2, 3, 4] 466 | # TODO? Replace `when compiles` with specialisation on concepts 467 | var acc = when t is ref: 468 | new t 469 | else: 470 | default t 471 | for x in iter: 472 | when compiles(acc.add(default(typeOf(T)))): 473 | acc.add x 474 | elif compiles(acc.incl(default(typeOf(T)))): 475 | acc.incl x 476 | elif compiles(acc.push(default(typeOf(T)))): 477 | acc.push x 478 | acc 479 | 480 | template fold*[T, U](iter: iterable[T]; init: U; fn: proc(acc: sink U; it: T): U): U = 481 | ## Accumulates the values of the iterator using an accumulation function `fn`. 482 | ## This operation is also commonly known as "reduce" and is useful for 483 | ## producing a single value from a collection. 484 | ## 485 | ## `fold` takes an iterable of elements, an initial value `init`, and an 486 | ## accumulation function `fn`. It updates the accumulating variable `acc` with 487 | ## the results of applying the accumulating function to the accumulator and 488 | ## the current element of the iterator. The final value of `acc` is returned. 489 | ## 490 | ## .. Note:: `fold` loops through every element of the iterator and thus will not 491 | ## terminate for infinite iterators. 492 | ## 493 | runnableExamples: 494 | let nums = [1, 2, 3, 4, 5] 495 | var sum = nums.items.fold(0, proc(acc: sink int; it: int): int = acc + it) 496 | assert sum == 15 497 | var acc: U = init 498 | for it in iter: 499 | acc = fn(acc, it) 500 | acc 501 | 502 | template fold*[T, U](iter: iterable[T]; init: U; fn: proc(acc: var U; it: T)): U = 503 | ## Accumulates the values of the iterator using an accumulation function `fn`. 504 | ## 505 | ## See `fold<#fold.t,iterable[T],U,proc(sinkU,T)>`_ for general description. 506 | ## This version of `fold` requires an accumulation function that 507 | ## takes the current accumulated value as a var parameter for in-place 508 | ## modification. 509 | ## 510 | ## .. Note:: `fold` loops through every element of the iterator and thus will not 511 | ## terminate for infinite iterators. 512 | ## 513 | runnableExamples: 514 | let nums = [1, 2, 3, 4, 5] 515 | var product = nums.items.fold(1, proc(acc: var int; it: int) = acc *= it) 516 | assert product == 120 517 | var acc: U = init 518 | for it in iter: 519 | fn(acc, it) 520 | acc 521 | 522 | template foldIt*[T, U](iter: iterable[T]; init: U; expr: untyped): U = 523 | ## Accumulates the values of the iterator using an expression. 524 | ## 525 | ## `foldIt` takes an iterable of elements, an initial value `init`, and an 526 | ## expression that defines the accumulation logic. The expression should 527 | ## evaluate to the updated value of the accumulating variable `acc`. 528 | ## The final value of `acc` is returned. 529 | ## 530 | ## .. Note:: `expr` should assign the updated accumulated value to the `acc` variable. 531 | ## 532 | ## .. Note:: `foldIt` loops through every element of the iterator and thus will not 533 | ## terminate for infinite iterators. 534 | ## 535 | runnableExamples: 536 | let nums = [1, 2, 3, 4, 5] 537 | var sum = nums.items.foldIt(1, acc + it) 538 | assert sum == 16 539 | block: 540 | var acc {.inject.}: U = init 541 | for it {.inject.} in iter: 542 | acc = expr 543 | acc 544 | 545 | template selectByCmp[T: Comparable](iter: iterable[T]; expr: untyped): T = 546 | var 547 | requiresInit = true 548 | acc {.inject.}: T 549 | for it {.inject.} in iter: 550 | if unlikely(requiresInit): 551 | acc = it 552 | requiresInit = false 553 | if expr: 554 | acc = it 555 | acc 556 | 557 | template min*[T: Comparable](iter: iterable[T]): T = 558 | ## Finds the minimum element in the iterator. 559 | ## 560 | ## `min` takes an iterable of elements that support comparison (satisfy the 561 | ## `Comparable` concept) and returns the minimal element. 562 | ## 563 | runnableExamples: 564 | let nums = [5, 2, 8, 1, 9] 565 | assert nums.items.min() == 1 566 | selectByCmp(iter, it < acc) 567 | 568 | template max*[T: Comparable](iter: iterable[T]): T = 569 | ## Finds the maximum element in the iterator. 570 | ## 571 | ## `max` takes an iterable of elements that support comparison (satisfy the 572 | ## `Comparable` concept) and returns the maximal element. 573 | ## 574 | runnableExamples: 575 | let nums = [5, 2, 8, 1, 9] 576 | assert nums.items.max() == 9 577 | selectByCmp(iter, acc < it) 578 | 579 | template count*[T](iter: iterable[T]): Natural = 580 | ## Counts the number of elements in the iterator. 581 | ## 582 | ## `count` takes an iterable and returns the total number of elements. 583 | ## 584 | ## .. Note:: The count iterates through and discards all elements of the iterator. 585 | ## 586 | runnableExamples: 587 | let nums = [1, 2, 3, 4, 5] 588 | assert nums.items.count() == 5 589 | iter.foldIt(0, acc+1) 590 | 591 | template sum*[T: Summable](iter: iterable[T]): T = 592 | ## Calculates the sum of all elements in the iterator. 593 | ## 594 | ## `sum` takes an iterable of elements that support addition 595 | ## (satisfy the `Summable` concept). It adds up all the elements 596 | ## together and returns the resulting sum. 597 | ## 598 | ## .. Note:: An empty iterator returns the default value for the type `T`. 599 | ## 600 | runnableExamples: 601 | let nums = [1, 2, 3, 4, 5] 602 | assert nums.items.sum() == 15 603 | iter.foldIt(default(typeOf(T)), acc + it) 604 | 605 | template product*[T: Multipliable](iter: iterable[T]): T = 606 | ## Calculates the product of all elements in the iterator. 607 | ## 608 | ## `product` takes an iterable of elements that support multiplication 609 | ## (satisfy the `Multipliable` concept). It multiplies all the elements 610 | ## together and returns the resulting product. 611 | ## 612 | ## .. Note:: An empty iterator returns the default value for the type `T`. 613 | ## 614 | runnableExamples: 615 | let nums = [1, 2, 3, 4, 5] 616 | assert nums.items.product() == 120 617 | var 618 | requiresInit = true 619 | acc: T 620 | for it in iter: 621 | if unlikely(requiresInit): 622 | acc = it 623 | requiresInit = false 624 | continue 625 | acc = acc * it 626 | acc 627 | 628 | template anyIt*[T](iter: iterable[T]; expr: untyped): bool = 629 | ## Checks if for at least one element of the iterator the expression evaluates 630 | ## to `true`. 631 | ## 632 | ## `anyIt` is short-circuiting; in other words, it will stop processing 633 | ## as soon as it encounters `true`. 634 | ## 635 | ## .. Note:: An empty iterator returns `false`. 636 | ## 637 | runnableExamples: 638 | let nums = [1, 2, 3, 4, 5] 639 | assert nums.items.anyIt(it > 3) 640 | assert "".items.anyIt(it is char) == false 641 | var result = false 642 | for it {.inject.} in iter: 643 | if expr: 644 | result = true 645 | break 646 | result 647 | 648 | template any*[T](iter: iterable[T]; pred: proc(x: T): bool): bool = 649 | ## Checks if for at least one element of the iterator the specified predicate 650 | ## function returns `true`. 651 | ## 652 | ## `any` is short-circuiting; in other words, it will stop processing 653 | ## as soon as `pred` returns `true`. 654 | ## 655 | ## .. Note:: An empty iterator returns `false`. 656 | ## 657 | runnableExamples: 658 | let nums = @[1, 2, 3, 4, 5] 659 | assert nums.items.any(proc(x: int): bool = x > 3) == true 660 | anyIt(iter, pred(it)) 661 | 662 | template allIt*[T](iter: iterable[T]; expr: untyped): bool = 663 | ## Checks if for every iteration the expression evaluates to `true`. 664 | ## 665 | ## `allIt` is short-circuiting; in other words, it will stop processing 666 | ## as soon as it encounters `false`. 667 | ## 668 | ## .. Note:: An empty iterator returns `true`. 669 | ## 670 | runnableExamples: 671 | let nums = [1, 2, 3, 4, 5] 672 | assert nums.items.allIt(it < 10) == true 673 | assert "".items.allIt(it == '!') == true 674 | var result = true 675 | for it {.inject.} in iter: 676 | if not (expr): 677 | result = false 678 | break 679 | result 680 | 681 | template all*[T](iter: iterable[T]; pred: proc(x: T): bool): bool = 682 | ## Checks if for every iteration the specified predicate function returns 683 | ## true. 684 | ## 685 | ## `all` is short-circuiting; in other words, it will stop processing 686 | ## as soon as `pred` returns `false`. 687 | ## 688 | ## .. Note:: An empty iterator returns `true`. 689 | ## 690 | runnableExamples: 691 | let nums = [1, 2, 3, 4, 5] 692 | assert nums.items.all(proc(x: int): bool = x < 10) == true 693 | assert "".items.all(proc(x: char): bool = x == '!') == true 694 | allIt(iter, pred(it)) 695 | 696 | template findIt*[T](iter: iterable[T]; expr: untyped): Option[T] = 697 | ## Searches for the first element in the iterator for which the specified 698 | ## expression evaluates to `true`. `none` is returned if such an element is 699 | ## not found (every expression evaluates to `false`). If such an element is 700 | ## found, it is wrapped in an `Option` and returned as `some(element)`. 701 | ## Otherwise, `none` is returned. 702 | ## 703 | ## If you need the index of the element, see 704 | ## `positionIt<#positionIt.t,iterable[T],untyped>`_. 705 | ## 706 | ## .. Note:: `findIt` is short-circuiting; in other words, it will stop 707 | ## iterating as soon as expression `expr` returns true. 708 | ## 709 | runnableExamples: 710 | import std/options 711 | let nums = [1, 2, 3, 4, 5] 712 | assert nums.items.findIt(it == 3) == some(3) 713 | var result = none(typeOf(T)) 714 | for it {.inject.} in iter: 715 | if expr: 716 | result = some(it) 717 | break 718 | result 719 | 720 | 721 | template positionIt*[T](iter: iterable[T]; expr: untyped): Option[int] = 722 | ## Searches for the index of the first element in the iterator for which the 723 | ## specified expression evaluates to `true`. `none` is returned if such an 724 | ## element is not found (every expression evaluates to `false`). If such an 725 | ## element is found, its iteration index is wrapped in an `Option` and 726 | ## returned as `some(index)`. Otherwise, `none` is returned. 727 | ## 728 | ## If you need to find the element itself, see 729 | ## `findIt<#findIt.t,iterable[T],untyped>`_. 730 | ## 731 | ## .. Note:: `positionIt` is short-circuiting; in other words, it will stop 732 | ## iterating as soon as expression `expr` returns true. 733 | ## 734 | runnableExamples: 735 | import std/options 736 | let nums = [1, 2, 3, 4, 5] 737 | assert nums.items.positionIt(it == 3) == some(2) 738 | var 739 | position = none(int) 740 | counter = 0 741 | for it {.inject.} in iter: 742 | if expr: 743 | position = some(counter) 744 | break 745 | inc(counter) 746 | position 747 | 748 | template position*[T](iter: iterable[T]; pred: proc(x: T): bool): Option[int] = 749 | ## Searches for the index of the first element in the iterator for which the 750 | ## specified predicate function returns `true`. `none` is returned if such an 751 | ## element is not found (every predicate returns `false`). If such an element 752 | ## is found, its iteration index is wrapped in an `Option` and returned as 753 | ## `some(index)`. Otherwise, `none` is returned. 754 | ## 755 | ## If you need to find the element itself, see 756 | ## `find<#find.t,iterable[T],proc(T)>`_. 757 | ## 758 | ## .. Note:: `position` is short-circuiting; in other words, it will stop 759 | ## iterating as soon as the predicate returns `true`. 760 | ## 761 | runnableExamples: 762 | import std/options 763 | let nums = [1, 2, 3, 4, 5] 764 | assert nums.items.position(proc(x: int): bool = x == 3) == some(2) 765 | positionIt(iter, pred(it)) 766 | 767 | template find*[T](iter: iterable[T]; pred: proc(x: T): bool): Option[T] = 768 | ## Searches for the first element in the iterator that satisfies the specified 769 | ## predicate function. If such an element is found, it is wrapped in an 770 | ## `Option` and returned as `some(element)`. If no element satisfies the 771 | ## predicate, `none` is returned. 772 | ## 773 | ## If you need the index of the element, see 774 | ## `position<#position.t,iterable[T],proc(T)>`_. 775 | ## 776 | ## .. Note:: `find` is short-circuiting; in other words, it will stop 777 | ## iterating as soon as `pred` returns true. 778 | ## 779 | runnableExamples: 780 | import std/options 781 | let nums = @[1, 2, 3, 4, 5] 782 | assert nums.items.find(proc(x: int): bool = x > 3) == some(4) 783 | findIt(iter, pred(it)) 784 | 785 | 786 | template nth*[T](iter: iterable[T]; n: Natural): Option[T] = 787 | ## Returns the nth element of the iterator. 788 | ## If `n` is greater than or equal to the number of iterator elements, returns 789 | ## `none`. 790 | ## 791 | ## Like most indexing operations, the count starts from zero, so 792 | ## `nth(0)` returns the first value, `nth(1)` the second, and so on. 793 | ## 794 | ## .. Note:: This adaptor consumes the iterator and discards all of 795 | ## the preceding elements. 796 | ## 797 | runnableExamples: 798 | import std/options 799 | 800 | let nums = [1, 2, 3, 4, 5] 801 | let thirdElement = nums.items.nth(2) 802 | assert thirdElement == some(3) 803 | let sixthElement = nums.items.nth(5) 804 | assert sixthElement.isNone() 805 | var 806 | result = none(typeOf(T)) 807 | counter = 0 808 | for it in iter: 809 | if counter == n: 810 | result = some(it) 811 | break 812 | inc counter 813 | result 814 | 815 | 816 | 817 | when defined(nimdoc): 818 | template splitOn*[Y, T](iter: iterable[Y], theSplit: T): untyped = 819 | ## Iterates over the iterator building up a collection. 820 | ## If the `Y` parameter is a `string` or `char` it builds up a `string`. 821 | ## Otherwise it builds up a `seq[Y]`. `theSplit` is where a split is made. 822 | runnableExamples: 823 | var a = "Hello" 824 | assert a.items.splitOn('l').splitOn('e').collect == @["H", "o"] 825 | 826 | else: 827 | template splitOn*[Y, T](iter: iterable[Y], theSplit: T): untyped = 828 | genIter(iter): 829 | let filter = theSplit 830 | var line = 831 | when Y is char or Y is string: 832 | "" 833 | else: 834 | newSeq[Y]() 835 | do: 836 | when it is string: 837 | for child in it: 838 | if child != filter: 839 | line.add child 840 | elif line.len > 0: 841 | yield line 842 | line.setLen(0) 843 | else: 844 | if it != filter: 845 | line.add it 846 | elif line.len > 0: 847 | yield line 848 | line.setLen(0) 849 | do: 850 | if line.len > 0: 851 | yield line 852 | 853 | when defined(nimdoc): 854 | template splitOnContainer*[Y, T](iter: iterable[Y], theSplit: T): untyped = 855 | ## Iterates over the iterator building up a collection. 856 | ## If the `Y` parameter is a `string` or `char` it builds up a `string`. 857 | ## Otherwise it builds up a `seq[Y]`. `theSplit` is where a split is made. 858 | ## Unlike `splitOn` this does a `iter` element `notin theSplit`. 859 | runnableExamples: 860 | import std/strutils 861 | var myMatrix = "1 2 3\n 8 9 \n 15 " 862 | echo myMatrix.items.splitOnContainer(NewLines).splitOnContainer(Whitespace).collect 863 | assert myMatrix.items.splitOnContainer(NewLines).splitOnContainer(Whitespace).collect == @["1", "2", "3", "8", "9", "15"] 864 | 865 | 866 | else: 867 | template splitOnContainer*[Y, T](iter: iterable[Y], theSplit: T): untyped = 868 | genIter(iter): 869 | let filter = theSplit 870 | var line = 871 | when Y is char or Y is string: 872 | "" 873 | else: 874 | newSeq[Y]() 875 | do: 876 | when it is string: 877 | for child in it: 878 | if child notin filter: 879 | line.add child 880 | elif line.len > 0: 881 | yield line 882 | line.setLen(0) 883 | else: 884 | if it notin filter: 885 | line.add it 886 | elif line.len > 0: 887 | yield line 888 | line.setLen(0) 889 | do: 890 | if line.len > 0: 891 | yield line 892 | 893 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") 2 | -------------------------------------------------------------------------------- /tests/tchain.nim: -------------------------------------------------------------------------------- 1 | import std/[unittest, strutils] 2 | import slicerator 3 | 4 | 5 | suite "chain": 6 | test"strtest": 7 | const data = "1000\n2000\n3000\n4000\n5000\n6000" 8 | for x in chain data.splitLines.map(parseint(x) * 2).filter(x < 3000): 9 | check x == 2000 10 | for x in chain splitLines(data).filter(x[0] notin {'3', '5'}).map(parseint(x)): 11 | check x in [1000, 2000, 4000, 6000] 12 | 13 | test"intarray": 14 | var a = [10, 20, 30, 40, 50, 60] 15 | for i, x in chain a.items.filter(i > a.len div 2).map(x * 10): 16 | check a[i] * 10 == x 17 | 18 | for i, x in chain a.pairs.unpack(y, z).filter(y > 3): 19 | check i > 3 20 | check i == y 21 | check z in [40, 50, 60] 22 | -------------------------------------------------------------------------------- /tests/test1.nim: -------------------------------------------------------------------------------- 1 | import slicerator 2 | import std/[strutils, unittest] 3 | 4 | 5 | suite "General tests": 6 | test "Test 1": 7 | let s = "Hello world" 8 | var res: string 9 | for x in s[0..2]: 10 | res.add x 11 | 12 | check res == "Hel" 13 | res.setLen(0) 14 | for x in s[3..^1]: 15 | res.add x 16 | check res == "lo world" 17 | for x in res{3..5}: 18 | x = 'a' 19 | check res == "lo aaald" 20 | for x in res{3..^1}: 21 | x = 'b' 22 | check res == "lo bbbbb" 23 | 24 | var secBuff = "" 25 | for ch in res.revitems: 26 | secBuff.add ch 27 | check secBuff == "bbbbb ol" 28 | 29 | for ch in res.revMitems: 30 | if ch == 'b': 31 | ch = 'a' 32 | else: 33 | break 34 | check res == "lo aaaaa" 35 | 36 | for i in res.rFindAll('l'): 37 | check i == 0 38 | 39 | for i in res.rMFindAll('l'): 40 | i = 'a' 41 | 42 | check res == "ao aaaaa" 43 | 44 | res.forMItems(ind, it): 45 | if it == 'o': 46 | check ind == 1 47 | it = 'b' 48 | 49 | check res == "ab aaaaa" 50 | 51 | var 52 | someOtherString = "helloworld" 53 | res = "" 54 | someOtherString.forMItems(ind, it): 55 | res.add $ind 56 | res.add $it 57 | check res == "0h1e2l3l4o5w6o7r8l9d" 58 | 59 | var a = "Hello" 60 | res = "" 61 | for i, x in a.pairs(1..^2): 62 | res.add $i 63 | res.add $x 64 | check res == "1e2l3l" 65 | 66 | res = "" 67 | for i, x in a.pairs(1..^1): 68 | res.add $i 69 | res.add $x 70 | check res == "1e2l3l4o" 71 | 72 | 73 | test "asClosure": 74 | var a = asClosure("hello"[1..2]) 75 | check a() == 'e' 76 | check a() == 'l' 77 | discard a() 78 | check a.finished 79 | 80 | var b = asClosure("hello".pairs) 81 | check b() == (0, 'h') 82 | check b() == (1, 'e') 83 | check b() == (2, 'l') 84 | check b() == (3, 'l') 85 | 86 | var c = asClosure(1..3) 87 | check c() == 1 88 | check c() == 2 89 | check c() == 3 90 | 91 | var d = asClosure(5..20) 92 | for x in d(): 93 | check x in 5..20 94 | check d.finished 95 | 96 | var e = asClosure(countup(1, 4)) 97 | check e() == 1 98 | check e() == 2 99 | check e() == 3 100 | check e() == 4 101 | 102 | var testVal = 0 103 | iterator numfile(filename: string): int = 104 | defer: inc testVal 105 | for l in fileName: 106 | yield l.ord 107 | 108 | proc sum(it: iterator): int = 109 | var t = 0 110 | for n in it(): 111 | t += n 112 | return t 113 | 114 | var it = asClosure(numfile"foo") 115 | check sum(it) == 324 116 | it = asClosure(numfile"World") 117 | check sum(it) == 520 118 | check sum(asClosure(numFile"Hehe")) == 378 119 | check testVal == 3 120 | 121 | 122 | 123 | test "Range iters": 124 | skipIter(1..2, x, 1): 125 | check x == 2 126 | 127 | const theValue = "Hello\ncruel world" 128 | skipIter(theValue.splitLines, it, 1): 129 | check it == "cruel world" 130 | iterRange(1..4, it, 1..2): 131 | check it in 2..3 132 | 133 | test "Resettable closures": 134 | var a = asClosure("hello"[1..2]) 135 | for x in a.iterThenReset: 136 | discard 137 | 138 | var b = 0 139 | for x in a(): 140 | inc b 141 | check b == 2 142 | 143 | for x in a(): 144 | inc b 145 | check b == 2 146 | a.reset 147 | 148 | for x in a(): 149 | inc b 150 | check b == 4 151 | 152 | a = asClosure("GoodDay"[1..5]) 153 | for x in a.iterThenReset: 154 | inc b 155 | check b == 9 156 | 157 | for x in a(): 158 | inc b 159 | check b == 14 160 | 161 | test "zip": 162 | let 163 | a = [10, 20, 30] 164 | b = ['a', 'b', 'c'] 165 | c = "Hello" 166 | check zip(a, b) == @[(10, 'a'), (20, 'b'), (30, 'c')] 167 | check zip(a, c) == @[(10, 'H'), (20, 'e'), (30, 'l')] 168 | check zip(c, b, a) == @[('H', 'a', 10), ('e', 'b', 20), ('l', 'c', 30)] 169 | 170 | 171 | test "map": 172 | block: 173 | let data = [10, 20, 30] 174 | for i, x in map(data, x * 3): 175 | check data[i] * 3 == x 176 | 177 | block: 178 | let data = "helloworld" 179 | for i, y in map(data, char('z'.ord - y.ord)): 180 | check char('z'.ord - data[i].ord) == y 181 | 182 | test "filter": 183 | let data = [1, 1, 0] 184 | var ran = 0 185 | for i, x in filter(data, x == 0): 186 | check x == 0 187 | ran = i 188 | check ran == 2 189 | 190 | 191 | test "zipIter": 192 | let 193 | a = [10, 20, 30] 194 | b = "\10\20\30\40" 195 | var count = 0 196 | for (x, y) in zipIter(a.items, b.items): 197 | check x.ord == y.ord 198 | inc count 199 | check count == 3 200 | 201 | #[ 202 | test "peek": 203 | var a = asClosure("hello".items) 204 | check a.peek() == 'h' 205 | check a.peek() == 'h' 206 | discard a() 207 | check a.peek() == 'e' 208 | check a.peek() == 'e' 209 | check a.peek() == 'e' 210 | discard a() 211 | check a.peek() == 'l' 212 | discard a() 213 | check a.peek() == 'l' 214 | check a.peek() == 'l' 215 | 216 | var b = asClosure(["Hello", "World", "Test"].items) 217 | check b.peek == "Hello" 218 | check b.peek == "Hello" 219 | check b.peek == "Hello" 220 | check b() == "Hello" 221 | 222 | check b.peek == "World" 223 | check b.peek == "World" 224 | check b.peek == "World" 225 | check b() == "World" 226 | 227 | type Test = object 228 | a, b: int 229 | c, d: string 230 | e: int 231 | let 232 | t1 = Test(a: 100, b: 300, c: "hello") 233 | t2 = Test(a: 3, b: 25, c: "Hmm") 234 | t3 = Test(a: 40, b: 15, c: "Heh") 235 | t4 = Test(a: 440, b: 30000, c: "H13232132132132132") 236 | var c = asClosure([t1, t2, t3, t4].items) 237 | 238 | check c.peek == t1 239 | check c.peek == t1 240 | check t1 == c() 241 | check c.peek == t2 242 | check c.peek == t2 243 | check t2 == c() 244 | 245 | check c.peek == t3 246 | check c.peek == t3 247 | check t3 == c() 248 | 249 | check c.peek == t4 250 | check c.peek == t4 251 | check t4 == c() 252 | ]# 253 | -------------------------------------------------------------------------------- /tests/titermacros.nim: -------------------------------------------------------------------------------- 1 | import slicerator/[itermacros, closures] 2 | import std/unittest 3 | 4 | suite "Iterator macros": 5 | const theVal = [10, 20, 30, 40] 6 | test "Basic collect": 7 | # This is used extensively for other tests, so checking it first 8 | check theVal.items.collect() == @theVal 9 | 10 | test "Basic tests": 11 | for (i, x) in theVal.items.mapIt(it * 2).group(2).enumerate(): 12 | case i 13 | of 0: 14 | check x == (20, 40) 15 | of 1: 16 | check x == (60, 80) 17 | else: doAssert false 18 | 19 | for x in theVal.items.mapIt($it).filterIt('1' in it): 20 | check x == "10" 21 | 22 | for x in theVal.items.skip(2).group(2): 23 | check x == (30, 40) 24 | 25 | test "Closure tests": 26 | let myClos1 = asClosure theVal.items.skip(2).group(2) 27 | check myClos1() == (30, 40) 28 | let myClos2 = asClosure theVal.items.group(2).mapIt($it) 29 | check myClos2() == "(10, 20)" 30 | check theVal.items.group(2).mapIt($it).collect == ["(10, 20)", "(30, 40)"] 31 | 32 | 33 | import std/[options, tables, sets] 34 | from std/strutils import isUpperAscii, isLowerAscii, toUpperAscii, split 35 | 36 | const 37 | ints = [-2, -1, 1, 3, -4, 5] 38 | chars = ['a', '.', 'b', 'C', 'z', 'd'] 39 | strs = ["foo", "BAR", "Niklaus", "deadbeef"] 40 | text = "Epicurus sought tranquility through simplicity and pleasure." 41 | 42 | suite "Iterator adaptors": 43 | test "map": 44 | # abs is {.inline.} 45 | check ints.items.map(proc(x:int):int = abs(x)).collect() == @[2, 1, 1, 3, 4, 5] 46 | check chars.items.map(toUpperAscii).collect() == @['A', '.', 'B', 'C', 'Z', 'D'] 47 | check strs.items.map(toUpperAscii).collect() == @["FOO", "BAR", "NIKLAUS", "DEADBEEF"] 48 | 49 | check ints.items.mapIt(abs(it)).collect() == @[2, 1, 1, 3, 4, 5] 50 | check chars.items.mapIt(char(it.ord + 1)).collect() == @['b', '/', 'c', 'D', '{', 'e'] 51 | check strs.items.mapIt((var s = it; s.setLen(1); s)).collect() == @["f", "B", "N", "d"] 52 | 53 | test "filter": 54 | check ints.items.filter(proc(x:int):bool = x > 0).collect() == @[1, 3, 5] 55 | check chars.items.filter(proc(x:char):bool = x in {'a'..'z'}).collect() == @['a', 'b', 'z', 'd'] 56 | check strs.items.filter(proc(x:string):bool = x.len == 3).collect() == @["foo", "BAR"] 57 | 58 | check ints.items.filterIt(it mod 2 == 0).collect() == @[-2, -4] 59 | check chars.items.filterIt(it notin {'a'..'d'}).collect() == @['.', 'C', 'z'] 60 | check strs.items.filterIt(it.len > 7).collect() == @["deadbeef"] 61 | 62 | test "group": 63 | check ints.items.group(2).collect() == @[(-2, -1), (1, 3), (-4, 5)] 64 | # partial tails are dropped 65 | check ints.items.group(4).collect() == @[(-2, -1, 1, 3)] 66 | check chars.items.group(6).collect() == @[('a', '.', 'b', 'C', 'z', 'd')] 67 | 68 | test "skip": 69 | check ints.items.skip(3).collect() == @[3, -4, 5] 70 | check chars.items.skip(6).collect() == newSeq[char](0) 71 | check strs.items.skip(0).collect() == @strs 72 | 73 | test "skipWhile": 74 | check ints.items.skipWhile(proc(x:int):bool = x < 0).collect() == @[1, 3, -4, 5] 75 | check ints.items.skipWhileIt(it < 0).collect() == @[1, 3, -4, 5] 76 | 77 | test"take": 78 | check ints.items.take(3).collect() == @[-2, -1, 1] 79 | # more than items in the iterator 80 | check chars.items.take(6).collect() == @chars 81 | # take zero items 82 | check strs.items.take(0).collect() == newSeq[string](0) 83 | 84 | test "takeWhile": 85 | check ints.items.takeWhile(proc(x:int):bool = x < 0).collect() == @[-2, -1] 86 | check ints.items.takeWhileIt(it < 0).collect() == @[-2, -1] 87 | 88 | test "stepBy": 89 | check ints.items.stepBy(2).collect() == @[-2, 1, -4] 90 | check text.items.stepBy(5).foldIt("", (acc.add(it); acc)) == "Ero qtr ly s" 91 | # first element is always returned 92 | check chars.items.stepBy(9000).collect() == @['a'] 93 | 94 | test "enumerate": 95 | check ints.items.enumerate.collect() == @[(0, -2), (1, -1), (2, 1), (3, 3), (4, -4), (5, 5)] 96 | 97 | test "flatten": 98 | iterator splat(s: string): string = (for w in s.split(): yield w) # hack 99 | let wordEndBytes = text.splat.mapIt(it[^2..^1]).flatten().mapIt(ord(it).byte).collect(set[byte]) 100 | check wordEndBytes == {46.byte, 100, 101, 103, 104, 110, 115, 116, 117, 121} 101 | 102 | suite "Iterator consumers": 103 | test "fold": 104 | func appended(acc: sink seq[string]; it:int): seq[string] = 105 | result = acc 106 | result.add($it) 107 | proc grow(acc: var seq[string]; it:int) = 108 | acc.add($it) 109 | 110 | check ints.items.fold(@["acc"], appended) == @["acc", "-2", "-1", "1", "3", "-4", "5"] 111 | check ints.items.fold(@["acc"], grow) == @["acc", "-2", "-1", "1", "3", "-4", "5"] 112 | check chars.items.foldIt({'@'}, (acc.incl(it); acc)) == {'.', '@', 'C', 'a', 'b', 'd', 'z'} 113 | let t = chars.items.enumerate.foldIt(initTable[char, int](), (acc[it[1]]=it[0]; acc)) 114 | check t['d'] == 5 115 | 116 | test "collect to seq": 117 | check ints.items.collect() == @ints 118 | check chars.items.collect() == @chars 119 | check strs.items.collect() == @strs 120 | 121 | test "collect to specific containers": 122 | check text.items.collect(set[char]) == {' ', '.', 'E', 'a', 'c', 'd', 'e', 'g', 'h', 'i', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'y'} 123 | check ints.items.collect(seq[int]) == @[-2, -1, 1, 3, -4, 5] 124 | check strs.items.collect(HashSet[string]) == toHashSet(strs) 125 | check chars.items.collect(string) == "a.bCzd" 126 | 127 | test "min-max": 128 | check ints.items.min() == -4 129 | check chars.items.min() == '.' 130 | check strs.items.min() == "BAR" 131 | 132 | check ints.items.max() == 5 133 | check chars.items.max() == 'z' 134 | check strs.items.max() == "foo" 135 | 136 | test "count": 137 | check ints.items.count() == 6 138 | check chars.items.count() == 6 139 | check strs.items.count() == 4 140 | 141 | test "sum": 142 | check ints.items.sum() == 2 143 | 144 | test "product": 145 | check ints.items.product() == -120 146 | 147 | test "any-all": 148 | check ints.items.anyIt(it > 1) == true 149 | check chars.items.anyIt(it.isUpperAscii) 150 | check chars.items.any(isLowerAscii) 151 | check chars.items.allIt(it in {'.', 'C', 'a'..'z'}) 152 | check chars.items.all(isLowerAscii) == false 153 | # empty iterator returns true 154 | check "".items.allIt(it == '!') 155 | 156 | test "find": 157 | check ints.items.find(proc(x:int):bool = x > 1) == some(3) 158 | check ints.items.findIt(it < -2) == some(-4) 159 | check strs.items.find(proc(x:string):bool = x.items.all(isUpperAscii)) == some("BAR") 160 | check strs.items.findIt(it == "Dijkstra").isNone() 161 | check chars.items.find(proc(x:char):bool = x.ord > 'y'.ord) == some('z') 162 | 163 | test "position": 164 | check ints.items.position(proc(x:int):bool = x > -1) == some(2) 165 | check ints.items.positionIt(it == 1) == some(2) 166 | check strs.items.position(proc(x:string):bool = x.items.all(isUpperAscii)) == some(1) 167 | check strs.items.positionIt(it == "Dijkstra").isNone() 168 | check chars.items.position(proc(x:char):bool = x.ord > 'y'.ord) == some(4) 169 | 170 | test "nth": 171 | check ints.items.nth(0) == some(-2) 172 | check chars.items.nth(6) == none(char) 173 | check strs.items.nth(1) == some("BAR") 174 | check text.items.enumerate.filterIt(it[1] in {'x'..'z'}).nth(0) == some((26, 'y')) 175 | 176 | static: 177 | discard (0..9).items.mapIt(it).foldIt(0, acc+it) 178 | discard (0..9).items.mapIt(it).sum() 179 | --------------------------------------------------------------------------------