├── .gitignore ├── changeLog.md ├── readme.md ├── src ├── unpack.nim └── unpack │ └── deprecating.nim ├── tests ├── config.nims ├── deprecatingApi.nim ├── nestedUnpack.nim ├── restOperator.nim └── theTest.nim └── unpack.nimble /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ -------------------------------------------------------------------------------- /changeLog.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## v0.5.0 4 | 5 | - Hides deprecated api, `unpack`, `vunpack`, and `lunpack`, in `unpack/deprecating`. If you still wants to use them for some reason, please `import unpack/deprecating` instead. 6 | 7 | ## v0.4.0 8 | 9 | - Supports nested unpacking. 10 | 11 | ## v0.3.2 12 | 13 | - Implements hack to enable rest unpacking after `var`. i.e. `[var _ as *a, b] <- someSeq` 14 | 15 | ## v0.3.1 16 | 17 | - Renames rename syntax. From the originally confusing `{name: anotherName} <- tim` and `unpackObject(name = anotherName)` to clear and descriptive `as` for both. (i.e. `{name as anotherName} <- tim` and `unpackObject(name as anotherName)`.) 18 | 19 | ## v0.3.0 20 | 21 | - Adds `*` rest operator support for unpacking sequence-like stuff. 22 | 23 | ## v0.2.0 24 | 25 | - Deprecating `unpack`, `lunpack`, and `vunpack` in favor of `unpackObject`, `unpackSeq`, `aUnpackObject`, and `aUnpackSeq`. The new interface is more similar to the `<-` syntax, and the programmers will have more control over how the data source will be unpacked. 26 | 27 | - Adds example and tests to demonstrate how to flexibly unpack named tuples (like a boss). 28 | 29 | - Sequence unpacking will skip the `_` entirely now. 30 | 31 | ## v0.1.0 32 | 33 | - Inspired by @Yardanico's [unpackarray.nim](https://gist.github.com/Yardanico/b6fee43f6da8a3bbf0fe048063357115) 34 | 35 | - Initially only has `unpack`, `lunpack`, and `vunpack` macros. These allows unpacking after chaining but has some limitation. Hated by most (N=2). 36 | 37 | - Later added `<--` and `<-` syntax as suggested by @alehander42. 38 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Unpack 2 | 3 | Array/Sequence/Object destructuring/unpacking macros attempt to quench the thirst of (Python, ES6, et al.)-spoiled people. 4 | 5 | ## Installation 6 | 7 | ```cli 8 | nimble install unpack 9 | ``` 10 | 11 | ## Example Usage 12 | 13 | ```nim 14 | import unpack 15 | 16 | let someSeq = @[1, 1, 2, 3, 5] 17 | someSeq.unpackSeq(a, b, c) # creates a, b, c with 'let' 18 | # is expanded into: 19 | # let 20 | # a = someSeq[0] 21 | # b = someSeq[1] 22 | # c = someSeq[2] 23 | 24 | # or equivalently: 25 | [a2, b2, c2] <- someSeq 26 | 27 | someSeq.unpackSeq(a3, _, _, b3) # use `_` to skip index 28 | # is expanded into: 29 | # let 30 | # a3 = someSeq[0] 31 | # b3 = someSeq[3] 32 | 33 | echo a, b, c # 112 34 | someSeq.unpackSeq(var d, e) # creates d,e with 'var' 35 | 36 | # or equivalently: 37 | [var d2, e2] <- someSeq 38 | 39 | someSeq.aUnpackSeq(d2, e2) # assigns someSeq[0] to d2, someSeq[1] to e2 40 | 41 | # or equivalently: 42 | [d2, e2] <-- someSeq 43 | # yes, <-- for assignment; <- for definitions. 44 | # This is not a typo. 45 | 46 | type 47 | Person = object 48 | name, job: string 49 | 50 | let tim = Person(name: "Tim", job: "Fluffer") 51 | 52 | # create name, job with let and assign respective member values to them 53 | tim.unpackObject(name, job) 54 | 55 | # or equivalently: 56 | # {name, job} <- tim 57 | 58 | # is expanded into: 59 | # let 60 | # name = tim.name 61 | # job = tim.job 62 | 63 | # you can also unpack into custom names using 'as' 64 | {job as someJob, name as otherName} <- tim 65 | 66 | # or equivalently: 67 | tim.unpackObject(job as someOtherJob, name as someOtherName) 68 | 69 | # is expanded into: 70 | # let 71 | # someOtherName = tim.name 72 | # someOtherJob = tim.job 73 | 74 | var 75 | secreteState, arg = 0 76 | proc someProcWithSideEffects(person: Person, input: int): Person = 77 | secreteState += 1 78 | {var job, name as newName} <- person 79 | newName &= $input 80 | result = Person(name: newName, job: job) 81 | 82 | # using this at the end of proc chain will not invoke proc chain multiple times 83 | tim.someProcWithSideEffects(arg).unpackObject(name as tim0, job as job0) 84 | 85 | # or equivalently: 86 | # {name as tim0, job as job0} <- tim.someProcWithSideEffects(arg) 87 | 88 | # is expanded into: 89 | # let someUniqueSym1_212_498 = tim.someProcWithSideEffects(arg) 90 | # let 91 | # tim0 = someUniqueSym1_212_498.name 92 | # job0 = someUniqueSym1_212_498.job 93 | 94 | # if you haven't noticed, 95 | # this means we can unpack named tuples like objects 96 | type 97 | SomeTuple = tuple[x, y, z, i, j, k: int; l, m: string] 98 | 99 | let someTuple = (1, 3, 7, 0, 3, 6, "so", "lengthy").SomeTuple 100 | 101 | # with vanilla nim, to get arbitrary fields 102 | let (_, diz, _, iz, _, _, _, it) = someTuple 103 | # it gets lengthy 104 | 105 | # with this package 106 | {y as diz2, i as iz2, m as it2} <- someTuple 107 | # Mind. Blown. 108 | 109 | # also, if you only care about the first three items 110 | [nice, n, sweet] <- someTuple 111 | 112 | # with vanilla nim 113 | let (youNeedTo, writeSoMany, underscoresMan, _, _, _, _, _) = someTuple 114 | 115 | # also supports nested unpacking 116 | let nestedTuple = ((123, 321), (-3, (1, 2, 3, 4))) 117 | 118 | # This nesting is unnecessarily deep, but it's supported. 119 | [ [run, outOf], [names, [_, _, toUse, now]]] <- nestedTuple 120 | 121 | # to be continued... 122 | 123 | ``` 124 | 125 | See [tests/theTest.nim](tests/theTest.nim) for more usages. 126 | 127 | ## Provisional Features 128 | 129 | Following features are implemented but the syntax or their actual effects are still in question. 130 | 131 | ### Rest operator for unpacking sequences 132 | 133 | Like in Python (`*a,b = range(5)`) and modern JavaScript `let [a,...b] = someArray`, you can put the rest of the sequence into a new sequence. I haven't decided on the actual prefix to use yet, but I am settling on `*` as used in Python for now. If you have better ideas, please start an issue to discuss other options. 134 | 135 | ```nim 136 | 137 | import unpack 138 | 139 | let mamaHen = @[3, 4, 5, 6, 7] 140 | 141 | [a, b, *sneakyFox] <- mamaHen 142 | 143 | # is expanded into: 144 | # let 145 | # a = mamaHen[0] 146 | # b = mamaHen[1] 147 | # sneakyFox = mamaHen[2..^1] 148 | 149 | assert(sneakyFox == @[5, 6, 7]) 150 | 151 | [*sloppySavior, e] <- sneakyFox 152 | 153 | assert(sloppySavior == @[5, 6]) 154 | 155 | # Perhaps the variable naming may be a bit mis-leading, 156 | # since mamaHen[x..y] creates a new sequence and copy the slice into it, 157 | # so rather than stealing, the sneakyFox actually cloned(?) whatever mamaHen had with her 158 | 159 | # You can use *_ to skip the beginning 160 | [*_, pickyFox] <- mamaHen 161 | 162 | assert(pickyFox == 7) 163 | 164 | # It's okay to take the middle chunk too. 165 | [f, g, *randomFox, _, h] <- mamaHen 166 | 167 | assert([f, g, h] == [3, 4, 7]) 168 | assert(randomFox == @[5]) 169 | 170 | # Due to restriction from nim's grammar, `*` following `var` 171 | # is not allowed. Adding `_ as` before it is the current hack I chose to bypass this. 172 | [var _ as *boldFox, i, j] <- mamaHen 173 | 174 | assert([i, j] == [6, 7]) 175 | assert(boldFox == @[3, 4, 5]) 176 | 177 | # They are indeed created with var. 178 | i = 12 179 | boldFox[2] = 123 180 | 181 | assert(i == 12) 182 | assert(boldFox == @[3, 123, 5]) 183 | 184 | ``` 185 | 186 | Under the hood, `unpack` just attaches `[countFromStart..^countFromEnd]` to whatever you throw at it, so anything that has slice operator implemented should work. Which also brings us to our first caveat. 187 | 188 | #### Caveat 189 | 190 | ##### Doesn't Work on tuples 191 | 192 | Unless you implement the `..` operator (and its friends) yourself though. 193 | 194 | ##### Only one rest operator per unpacked sequence 195 | 196 | `[*a, *b, c] <- someSeq` is not allowed. It might be possible, but I think it will be really messy (plus I am lazy). Same restriction applies to both Python and JavaScript, so I think it's okay to skip this part for now. 197 | 198 | However, using rest operator in different parts of the nested sequence is fine, since they have different index counters, so this will work: 199 | 200 | ```nim 201 | [[*a, b], [c, d, *e], *f, g, h] <- someNestedSeq 202 | # is expanded into: 203 | # let 204 | # b = someNestedSeq[0][^1] 205 | # a = someNestedSeq[0][0..^2] 206 | # c = someNestedSeq[1][0] 207 | # d = someNestedSeq[1][1] 208 | # e = someNestedSeq[1][2..^1] 209 | # h = someNestedSeq[^1] 210 | # g = someNestedSeq[^2] 211 | # f = someNestedSeq[2..^3] 212 | ``` 213 | 214 | ##### Can't guard against incorrect index access at compile time 215 | 216 | Since we have no way to know the sequence length at compile time, (well, at least I don't know a way). We can't know if you are trying to do something goofy like: 217 | 218 | ```nim 219 | [a, b, *c, d, e] <- @[1,2,3] 220 | ``` 221 | 222 | ## Notes 223 | 224 | ### About the syntax 225 | 226 | #### Using `let` in [] and {} is not allowed 227 | 228 | Yes, I also wanted to have the natural `[let x, y] <- someSeq` syntax for defining new symbol with let, `[x, y] <- someSeq` for assignment, but the compiler deems it illegal. I ended up settle with more verbose assignment syntax since I anticipate it being used less often. 229 | 230 | ## TODO 231 | 232 | - Docs 233 | - Maybe we can also support tables? 234 | - More informative error message for out of bound sequence access during unpacking. 235 | 236 | ## Maybe TODO 237 | 238 | - rest operator for objects/tables. 239 | 240 | ## Suggestions and PR's are welcome 241 | 242 | Especially if you know how to make this macro easier to use. Also, if you know any other existing package that does this kind of stuff better, please let me know, thank you. 243 | -------------------------------------------------------------------------------- /src/unpack.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | 3 | const 4 | restOp = "*" 5 | skipOp = "_" 6 | renameOp = "as" 7 | 8 | proc getRealDest(dest: NimNode; restCount: var int): NimNode = 9 | result = dest 10 | 11 | if dest.kind == nnkInfix: 12 | if dest[0].strVal == renameOp: 13 | result = dest[2] 14 | else: 15 | error("Only `" & renameOp & "` is allowed as the infix", dest[0]) 16 | 17 | case result.kind: 18 | of nnkVarTy: 19 | result = dest[0] 20 | of nnkPrefix: 21 | if result[0].strVal != restOp: 22 | error("Beep boop. I don't understand prefix `" & dest[ 23 | 0].strVal & "`. Only prefix I know is `" & restOp & "` operator", 24 | dest) 25 | else: 26 | if restCount > 0: 27 | error("Only one rest operator allowed per unpack") 28 | restCount += 1 29 | result = result[1] 30 | else: discard 31 | 32 | proc processSeqUnpack(section, src, dests, stNode: NimNode): NimNode 33 | 34 | 35 | proc processObjectUnpack(section, src, dests, stNode: NimNode): NimNode = 36 | result = section 37 | for dest in dests.children: 38 | case dest.kind: 39 | of nnkInfix: 40 | if dest[0].strVal == renameOp: 41 | var realDest = dest[1] 42 | case realDest.kind: 43 | of nnkVarTy: 44 | realDest = realDest[0] 45 | else: discard 46 | var newNode = stNode.copyNimTree 47 | var newSource = nnkDotExpr.newTree(src, realDest) 48 | let newDest = dest[2] 49 | case newDest.kind: 50 | of nnkCurly: 51 | result = processObjectUnpack(result, newSource, newDest, stNode) 52 | of nnkBracket: 53 | result = processSeqUnpack(result, newSource, newDest, stNode) 54 | else: 55 | newNode[0] = newDest 56 | newNode[^1] = newSource 57 | result.add(newNode) 58 | else: 59 | error("Only `" & renameOp & "` is allowed as the infix", 60 | dest[0]) 61 | of nnkExprColonExpr, nnkExprEqExpr: 62 | var realDest = dest[0] 63 | case realDest.kind: 64 | of nnkVarTy: 65 | realDest = realDest[0] 66 | else: discard 67 | var newNode = stNode.copyNimTree 68 | newNode[0] = dest[1] 69 | newNode[^1] = nnkDotExpr.newTree(src, realDest) 70 | warning("This syntax is being deprecated, please use `{" & 71 | realDest.strVal & " as " & dest[ 72 | 1].strVal & "}` instead", dest) 73 | result.add(newNode) 74 | else: 75 | var realDest = dest 76 | case dest.kind: 77 | of nnkVarTy: 78 | realDest = dest[0] 79 | else: discard 80 | var newNode = stNode.copyNimTree 81 | newNode[0] = realDest 82 | newNode[^1] = nnkDotExpr.newTree(src, realDest) 83 | result.add(newNode) 84 | 85 | proc processSeqUnpack(section, src, dests, stNode: NimNode): NimNode = 86 | var 87 | startInd = 0 88 | endCount = 0 89 | restCount = 0 90 | restDest = newLit(0) 91 | result = section 92 | # First pass from the front 93 | for dest in dests.children: 94 | let realDest = getRealDest(dest, restCount) 95 | var newNode = stNode.copyNimTree 96 | let newSource = nnkBracketExpr.newTree(src, newLit(startInd)) 97 | let newDest = realDest 98 | case realDest.kind: 99 | of nnkCurly: 100 | result = processObjectUnpack(result, newSource, newDest, stNode) 101 | of nnkBracket: 102 | result = processSeqUnpack(result, newSource, newDest, stNode) 103 | else: 104 | if realDest.strVal != skipOp: 105 | if restCount == 0: 106 | 107 | newNode[0] = realDest 108 | newNode[^1] = newSource 109 | result.add(newNode) 110 | if restCount == 0: startInd += 1 111 | else: 112 | if restDest.kind == nnkIntLit: 113 | restDest = realDest 114 | else: endCount += 1 115 | # Second pass from the back 116 | if endCount > 0: 117 | for endInd in 1..endCount: 118 | let realDest = getRealDest(dests[^endInd], restCount) 119 | var newNode = stNode.copyNimTree 120 | let newSource = nnkBracketExpr.newTree(src, nnkPrefix.newTree( 121 | newIdentNode("^"), 122 | newLit(endInd) 123 | )) 124 | let newDest = realDest 125 | case realDest.kind: 126 | of nnkCurly: 127 | result = processObjectUnpack(result, newSource, newDest, stNode) 128 | of nnkBracket: 129 | result = processSeqUnpack(result, newSource, newDest, stNode) 130 | else: 131 | if realDest.strVal != skipOp: 132 | newNode[0] = realDest 133 | newNode[^1] = newSource 134 | result.add(newNode) 135 | # Adds rest statement 136 | if restCount == 1: 137 | var newNode = stNode.copyNimTree 138 | if restDest.strVal != skipOp: 139 | newNode[0] = restDest 140 | newNode[^1] = nnkBracketExpr.newTree(src, nnkInfix.newTree( 141 | newIdentNode("..^"), 142 | newLit(startInd), 143 | newLit(endCount+1) 144 | )) 145 | result.add(newNode) 146 | 147 | proc prepareHead(srcNode: NimNode; sec, 148 | statement: NimNodeKind): (NimNode, NimNode, NimNode, NimNode) = 149 | var tobeResult = newStmtList() 150 | var section = sec.newTree() 151 | var stNode = statement.newTree(newEmptyNode(), newEmptyNode()) 152 | if statement != nnkAsgn: 153 | stNode.add(newEmptyNode()) 154 | var src = srcNode 155 | # Creates a temporary symbol to store proc result. 156 | if src.kind != nnkSym: 157 | src = genSym(nskLet, "src") 158 | tobeResult.add(newLetStmt(src, srcNode)) 159 | (tobeResult, section, src, stNode) 160 | 161 | proc unpackSequenceInternal(srcNode, dests: NimNode; sec, 162 | statement: NimNodeKind): NimNode = 163 | var (tobeResult, section, src, stNode) = prepareHead(srcNode, sec, 164 | statement) 165 | result = tobeResult 166 | section = processSeqUnpack(section, src, dests, stNode) 167 | result.add(section) 168 | 169 | proc unpackObjectInternal(srcNode, dests: NimNode; sec, 170 | statement: NimNodeKind): NimNode = 171 | var (tobeResult, section, src, stNode) = prepareHead(srcNode, sec, 172 | statement) 173 | result = tobeResult 174 | section = processObjectUnpack(section, src, dests, stNode) 175 | result.add(section) 176 | 177 | macro `<-`*(dests: untyped; src: typed): typed = 178 | ## Creates new symbol to unpack src into 179 | ## [a, b, c] <- src 180 | ## put var before first item to create mutable variable 181 | ## [var a, b, c] <- src 182 | ## unpacking objects 183 | ## {var meberA, meberB, memberC} <- src 184 | ## unpacking objects to symbols with custom names 185 | ## {var meberA as customNameA, meberB, memberC as customNameC} <- src 186 | 187 | var hasVar = false 188 | var firstDest = dests[0] 189 | while firstDest.len > 0: 190 | case firstDest.kind 191 | of nnkVarTy: 192 | hasVar = true 193 | break 194 | of nnkInfix: firstDest = firstDest[1] 195 | else: firstDest = firstDest[0] 196 | let sec = if hasVar: nnkVarSection else: nnkLetSection 197 | let statement = nnkIdentDefs 198 | case dests.kind: 199 | of nnkBracket: 200 | result = unpackSequenceInternal(src, dests, sec, statement) 201 | of nnkCurly, nnkTableConstr: 202 | result = unpackObjectInternal(src, dests, sec, statement) 203 | else: 204 | error("Oh Noo! Unknown kind: " & $dests.kind) 205 | 206 | macro `<--`*(dests: untyped; src: typed): typed = 207 | ## unpack src into existing symbols 208 | ## [a, b, c] <-- src 209 | ## for objects 210 | ## {meberA, meberB, memberC} <-- src 211 | ## rename 212 | ## {meberA as customNameA, meberB, memberC} <-- src 213 | let sec = nnkStmtList 214 | let statement = nnkAsgn 215 | case dests.kind: 216 | of nnkBracket: 217 | result = unpackSequenceInternal(src, dests, sec, statement) 218 | of nnkCurly, nnkTableConstr: 219 | result = unpackObjectInternal(src, dests, sec, statement) 220 | else: 221 | error("Oh Noo! Unknown kind: " & $dests.kind) 222 | 223 | 224 | macro unpackObject*(src: typed; dests: varargs[untyped]): typed = 225 | ## unpacking objects/named tuples into immutable symbols 226 | ## (i.e. create new symbol with `let`) 227 | ## src.unpackObject(meberA, meberB, memberC) 228 | ## unpacking objects into new variables 229 | ## src.unpackObject(var meberA, meberB, memberC) 230 | ## unpacking objects to symbols with custom names 231 | ## src.unpackObject(var memberA as customNameA, meberB, memberC as customNameC) 232 | var hasVar = false 233 | var firstDest = dests[0] 234 | while firstDest.len > 0: 235 | case firstDest.kind 236 | of nnkVarTy: 237 | hasVar = true 238 | break 239 | of nnkInfix: firstDest = firstDest[1] 240 | else: firstDest = firstDest[0] 241 | let sec = if hasVar: nnkVarSection else: nnkLetSection 242 | let statement = nnkIdentDefs 243 | result = unpackObjectInternal(src, dests, sec, statement) 244 | 245 | macro aUnpackObject*(src: typed; dests: varargs[untyped]): typed = 246 | ## assigning unpacked objects/named tuples members into existing symbols 247 | ## var memberA, memberB, memberC: string 248 | ## src.aUnpackObject(meberA, meberB, memberC) 249 | ## unpacking objects to symbols with custom names 250 | ## var customNameA,customNameC:string 251 | ## src.aUnpackObject(memberA as customNameA, meberB, memberC as customNameC) 252 | result = unpackObjectInternal(src, dests, nnkStmtList, nnkAsgn) 253 | 254 | macro unpackSeq*(src: typed; dests: varargs[untyped]): typed = 255 | ## unpacking array/seq/tuple into immutable symbols (i.e. create with `let`) 256 | ## src.unpackSeq(a, b, c) 257 | ## unpacking array/seq/tuple into variables 258 | ## src.unpackSeq(var a, b, c) 259 | var hasVar = false 260 | var firstDest = dests[0] 261 | while firstDest.len > 0: 262 | if firstDest.kind == nnkVarTy: 263 | hasVar = true 264 | break 265 | firstDest = firstDest[0] 266 | let sec = if hasVar: nnkVarSection else: nnkLetSection 267 | let statement = nnkIdentDefs 268 | result = unpackSequenceInternal(src, dests, sec, statement) 269 | 270 | macro aUnpackSeq*(src: typed; dests: varargs[untyped]): typed = 271 | ## assigning values unpacked from seq/array/tuple into existing symbols 272 | ## var a, b, c: int 273 | ## src.aUnpackSeq(a, b, c) 274 | result = unpackSequenceInternal(src, dests, nnkStmtList, nnkAsgn) 275 | 276 | -------------------------------------------------------------------------------- /src/unpack/deprecating.nim: -------------------------------------------------------------------------------- 1 | import macros 2 | proc unpackInternal(srcNode, dests: NimNode; sec, 3 | statement: NimNodeKind): NimNode = 4 | result = newStmtList() 5 | var section = sec.newTree() 6 | var stNode = statement.newTree(newEmptyNode(), newEmptyNode()) 7 | if statement != nnkAsgn: 8 | stNode.add(newEmptyNode()) 9 | var src = srcNode 10 | # Creates a temporary symbol to store proc result. 11 | if src.kind != nnkSym: 12 | src = genSym(nskLet, "src") 13 | result.add(newLetStmt(src, srcNode)) 14 | let typ = srcNode.getType 15 | case typ.typeKind: 16 | of ntySequence, ntyArray, ntyOpenArray, ntyTuple: 17 | var i = 1 18 | for dest in dests.children: 19 | if dest.strVal != "_": 20 | var newNode = stNode.copyNimTree 21 | newNode[0] = dest 22 | newNode[^1] = nnkBracketExpr.newTree(src, newLit(i-1)) 23 | section.add(newNode) 24 | inc i 25 | of ntyObject, ntyRef, ntyPtr: 26 | for dest in dests.children: 27 | case dest.kind: 28 | of nnkExprEqExpr: 29 | var newNode = stNode.copyNimTree 30 | newNode[0] = dest[0] 31 | newNode[^1] = nnkDotExpr.newTree(src, dest[1]) 32 | section.add(newNode) 33 | else: 34 | var newNode = stNode.copyNimTree 35 | newNode[0] = dest 36 | newNode[^1] = nnkDotExpr.newTree(src, dest) 37 | section.add(newNode) 38 | else: error("Oh NOOoo! Type `" & $src.typeKind & "` can not be unpacked!", 39 | src) 40 | result.add(section) 41 | 42 | macro vunpack*(src: typed; dests: varargs[untyped]): typed {. 43 | deprecated: "use unpackObject/unpackSeq instead".} = 44 | unpackInternal(src, dests, nnkVarSection, nnkIdentDefs) 45 | macro lunpack*(src: typed; dests: varargs[untyped]): typed {. 46 | deprecated: "use unpackObject/unpackSeq instead".} = 47 | unpackInternal(src, dests, nnkLetSection, nnkIdentDefs) 48 | macro unpack*(src: typed; dests: varargs[untyped]): typed {. 49 | deprecated: "use aUnpackObject/aunpackSeq instead".} = 50 | unpackInternal(src, dests, nnkStmtList, nnkAsgn) 51 | -------------------------------------------------------------------------------- /tests/config.nims: -------------------------------------------------------------------------------- 1 | switch("path", "$projectDir/../src") -------------------------------------------------------------------------------- /tests/deprecatingApi.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import unpack/deprecating 4 | import macros 5 | 6 | suite "Sequence unpacking with (l|v)?unpack macro": 7 | setup: 8 | let testSeq = @[2, 4, 6] 9 | let testArray = [2, 4, 6] 10 | let testTuple = (2, 4, 6) 11 | 12 | test "should unpack sequence": 13 | testSeq.lunpack(a, b, c) 14 | check [a, c, b] == [2, 6, 4] 15 | # is expanded into: 16 | # let 17 | # a = testSeq[0] 18 | # b = testSeq[1] 19 | # c = testSeq[2] 20 | 21 | test "should ignore '_' in sequence": 22 | testSeq.lunpack(a, _, c) 23 | check [a, c] == [2, 6] 24 | # is expanded into: 25 | # let 26 | # a = testSeq[0] 27 | # c = testSeq[2] 28 | 29 | test "should unpack array": 30 | testArray.lunpack(a, b, c) 31 | check [a, c, b] == [2, 6, 4] 32 | test "should unpack tuple": 33 | testTuple.lunpack(a, b, c) 34 | check [a, c, b] == [2, 6, 4] 35 | ## testTuple.lunpack(d, e, f, g) <- will cause IndexError at runtime 36 | test "should unpack from index 0 to arbitrary number": 37 | testTuple.lunpack(a, b) 38 | check [a, b] == [2, 4] 39 | test "vunpack should create variables with var": 40 | testTuple.vunpack(a, b) 41 | check [a, b] == [2, 4] 42 | a = 13 43 | check a == 13 44 | test "lunpack defines symbols with let": 45 | testTuple.lunpack(a, b) 46 | check [a, b] == [2, 4] 47 | test "unpack should assign data to existing variables": 48 | var a, b = 1 49 | testTuple.unpack(a, _, b) 50 | check [a, b] == [2, 6] 51 | 52 | 53 | suite "Object meber unpacking with (l|v)?unpack macro": 54 | type 55 | Person = object 56 | name, job: string 57 | PersonRef = ref Person 58 | setup: 59 | let timName = "Tim" 60 | let fluffer = "Fluffer" 61 | let tim = Person(name: timName, job: fluffer) 62 | let timRef = new(Person) 63 | timRef.name = timName 64 | timRef.job = fluffer 65 | var secreteCounter = 0 66 | proc colleague(p: Person; name: string): Person = 67 | secreteCounter += 1 68 | result = p 69 | result.name = name 70 | test "should unpack ordinary objects": 71 | tim.lunpack(name, job) 72 | check name == timName 73 | check job == fluffer 74 | 75 | test "should unpack object refs": 76 | timRef.lunpack(name, job) 77 | check name == timName 78 | check job == fluffer 79 | 80 | test "should unpack object pointers": 81 | let timPtr = unsafeAddr(tim) 82 | timPtr.lunpack(job, name) 83 | check name == timName 84 | check job == fluffer 85 | 86 | test "should not call proc multiple times when invoked after a chain of calls": 87 | let johnName = "John" 88 | tim.colleague(johnName).lunpack(name, job) 89 | 90 | # is expanded into: 91 | # let someUniqueSym1_212_498 = tim.colleague(johnName) 92 | # let 93 | # name = someUniqueSym1_212_498.name 94 | # job = someUniqueSym1_212_498.job 95 | 96 | check name == johnName 97 | check job == fluffer 98 | check secreteCounter == 1 99 | 100 | test "should be able to rename object member with '=' sign": 101 | tim.lunpack(otherName = name) 102 | 103 | check otherName == timName 104 | 105 | tim.lunpack(job, yetAnotherName = name) # and is order-agnostic. 106 | 107 | check yetAnotherName == timName 108 | check job == fluffer 109 | -------------------------------------------------------------------------------- /tests/nestedUnpack.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import unpack 4 | import macros 5 | 6 | type 7 | Person = object 8 | name, job: string 9 | hobby: seq[string] 10 | let testSeq = @[@[2, 4], @[6, 4], @[3, 4, 5]] 11 | let testTuple = ((2, 4), 6, ((3, 4), 5)) 12 | let timName = "Tim" 13 | let johnName = "John" 14 | let fluffer = "Fluffer" 15 | let bobber = "Bobber" 16 | let waterBottle = "water bottle" 17 | let timHobby = @[waterBottle, "stapler"] 18 | let people = [Person(name: timName, job: fluffer, hobby: timHobby), 19 | Person(name: johnName, job: bobber)] 20 | 21 | suite "Unpacking nested sequence": 22 | test "should unpack nested sequence": 23 | testSeq.unpackSeq([a, b], [c]) 24 | check [a, c, b] == [2, 6, 4] 25 | # is expanded into: 26 | # let 27 | # a = testSeq[0][0] 28 | # b = testSeq[0][1] 29 | # c = testSeq[1][0] 30 | test "should unpack nested tuples": 31 | testTuple.unpackSeq([a, b], c, [_, d]) 32 | check [a, c, b, d] == [2, 6, 4, 5] 33 | 34 | test "should unpack nested sequence of objects with sequences": 35 | people.unpackSeq({name, job, hobby as [a, _]}, john) 36 | # expands into: 37 | # let 38 | # name = people[0].name 39 | # job = people[0].job 40 | # a = people[0].hobby[0] 41 | # john = people[1] 42 | check name == timName 43 | check job == fluffer 44 | check a == waterBottle 45 | check john.name == johnName 46 | 47 | test "<- should also unpack nested sequence of objects with sequences": 48 | [var {name, job, hobby as [a, _]}, john] <- people 49 | # expands into: 50 | # var 51 | # name = people[0].name 52 | # job = people[0].job 53 | # a = people[0].hobby[0] 54 | # john = people[1] 55 | check name == timName 56 | check job == fluffer 57 | check a == waterBottle 58 | check john.name == johnName 59 | job = bobber 60 | check job == bobber 61 | 62 | 63 | type 64 | Cult = object 65 | members: array[2, Person] 66 | name: string 67 | let 68 | tinyCult = "tinyCult" 69 | theCult = Cult(members: people, name: tinyCult) 70 | 71 | suite "Unpacking nested objects": 72 | 73 | test "should also unpack nested objects": 74 | {members as [_, john], name} <- theCult 75 | check john.name == johnName 76 | check name == tinyCult 77 | test "should work with variables definition": 78 | {var members as [_, john], name} <- theCult 79 | check john.name == johnName 80 | check name == tinyCult 81 | 82 | john.name = timName 83 | check john.name == timName 84 | 85 | test "should work with reassignment as well": 86 | 87 | {var members as [_, john], name} <- theCult 88 | check john.name == johnName 89 | check name == tinyCult 90 | 91 | {members as [{name}, _]} <-- theCult 92 | check name == timName 93 | -------------------------------------------------------------------------------- /tests/restOperator.nim: -------------------------------------------------------------------------------- 1 | import unpack 2 | 3 | let mamaHen = @[3, 4, 5, 6, 7] 4 | 5 | [a, b, *sneakyFox] <- mamaHen 6 | 7 | # is expanded into: 8 | # let 9 | # a = mamaHen[0] 10 | # b = mamaHen[1] 11 | # sneakyFox = mamaHen[2..^1] 12 | 13 | assert(sneakyFox == @[5, 6, 7]) 14 | 15 | [*sloppySavior, e] <- sneakyFox 16 | 17 | assert(sloppySavior == @[5, 6]) 18 | 19 | # Perhaps the variable naming may be a bit mis-leading, 20 | # since mamaHen[x..y] creates a new sequence and copy the slice into it, 21 | # so rather than stealing, the sneakyFox actually cloned(?) whatever mamaHen had with her 22 | 23 | # You can use *_ to skip the beginning 24 | [*_, pickyFox] <- mamaHen 25 | 26 | assert(pickyFox == 7) 27 | 28 | # It's okay to take the middle chunk too. 29 | [f, g, *randomFox, _, h] <- mamaHen 30 | 31 | assert([f, g, h] == [3, 4, 7]) 32 | assert(randomFox == @[5]) 33 | 34 | # Due to restriction from nim's grammar, `*` following `var` 35 | # is not allowed. Adding `_ as` before it is the current hack I chose to bypass this. 36 | [var _ as *boldFox, i, j] <- mamaHen 37 | 38 | assert([i, j] == [6, 7]) 39 | assert(boldFox == @[3, 4, 5]) 40 | 41 | # They are indeed created with var. 42 | i = 12 43 | boldFox[2] = 123 44 | 45 | assert(i == 12) 46 | assert(boldFox == @[3, 4, 123]) 47 | 48 | # Works with nested unpack as well. 49 | 50 | let tisMad = @[@[@[1, 2, 3], @[4, 5]], @[@[6], @[7, 8, 9, 10]]] 51 | 52 | [var [[z, *y], [_, x]], [[w], [_, *v, u]]] <- tisMad 53 | 54 | assert([z, x, w, u] == [1, 5, 6, 10]) 55 | assert(y == @[2, 3]) 56 | assert(v == @[8, 9]) 57 | -------------------------------------------------------------------------------- /tests/theTest.nim: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import unpack 4 | 5 | suite "Sequence-like unpacking with unpackSeq/aUnpackSeq macro": 6 | setup: 7 | let testSeq = @[2, 4, 6] 8 | let testArray = [2, 4, 6] 9 | let testTuple = (2, 4, 6) 10 | let testString = "246" 11 | 12 | test "should unpack sequence": 13 | testSeq.unpackSeq(a, b, c) 14 | check [a, c, b] == [2, 6, 4] 15 | # is expanded into: 16 | # let 17 | # a = testSeq[0] 18 | # b = testSeq[1] 19 | # c = testSeq[2] 20 | 21 | test "should ignore '_' in sequence": 22 | testSeq.unpackSeq(a, _, c) 23 | check [a, c] == [2, 6] 24 | # is expanded into: 25 | # let 26 | # a = testSeq[0] 27 | # c = testSeq[2] 28 | 29 | test "should unpack array": 30 | testArray.unpackSeq(a, b, c) 31 | check [a, c, b] == [2, 6, 4] 32 | 33 | test "should unpack tuple": 34 | testTuple.unpackSeq(a, b, c) 35 | check [a, c, b] == [2, 6, 4] 36 | ## testTuple.unpackObject(d, e, f, g) <- will cause IndexError at runtime 37 | 38 | test "should unpack string": 39 | testString.unpackSeq(a, b, c) 40 | check [a, c, b] == ['2', '6', '4'] 41 | 42 | test "should unpack from index 0 to arbitrary number": 43 | testTuple.unpackSeq(a, b) 44 | check [a, b] == [2, 4] 45 | 46 | test "unpackSeq with var before first item should create variables with var": 47 | testTuple.unpackSeq(var a, b) 48 | check [a, b] == [2, 4] 49 | a = 13 50 | check a == 13 51 | 52 | test "unpackSeq without var before first item defines symbols with let": 53 | testTuple.unpackSeq(a, b) 54 | check [a, b] == [2, 4] 55 | 56 | test "aUnpackSeq should assign data to existing variables": 57 | var a, b = 1 58 | testTuple.aUnpackSeq(a, _, b) 59 | check [a, b] == [2, 6] 60 | 61 | suite "Sequence unpacking with arrow operators": 62 | setup: 63 | let testSeq = @[2, 4, 6] 64 | let testArray = [2, 4, 6] 65 | let testTuple = (2, 4, 6) 66 | 67 | test "should unpack sequence": 68 | [a, b, c] <- testSeq 69 | check [a, c, b] == [2, 6, 4] 70 | # is expanded into: 71 | # let 72 | # a = testSeq[0] 73 | # b = testSeq[1] 74 | # c = testSeq[2] 75 | test "should unpack array": 76 | [a, b, c] <- testArray 77 | check [a, c, b] == [2, 6, 4] 78 | test "should unpack tuple": 79 | [a, b, c] <- testTuple 80 | check [a, c, b] == [2, 6, 4] 81 | ## [d, e, f, g] <- testTuple <- will cause IndexError at runtime 82 | test "should unpack from index 0 to arbitrary number": 83 | [a, b] <- testTuple 84 | check [a, b] == [2, 4] 85 | test "adding var before first item should create mutable variables": 86 | [var a, b] <- testTuple 87 | check [a, b] == [2, 4] 88 | a = 13 89 | check a == 13 90 | test "<- without var defines symbols with let": 91 | [a, b] <- testTuple 92 | check [a, b] == [2, 4] 93 | test "<-- should assign data to existing variables": 94 | var a, b = 1 95 | [a, b] <-- testTuple 96 | check [a, b] == [2, 4] 97 | 98 | suite "Object meber unpacking unpackObject/aUnpackObject": 99 | type 100 | Person = object 101 | name, job: string 102 | PersonRef = ref Person 103 | setup: 104 | let timName = "Tim" 105 | let fluffer = "Fluffer" 106 | let johnName = "John" 107 | let tim = Person(name: timName, job: fluffer) 108 | let timRef = new(Person) 109 | timRef.name = timName 110 | timRef.job = fluffer 111 | var secreteCounter = 0 112 | proc colleague(p: Person; name: string): Person = 113 | secreteCounter += 1 114 | result = p 115 | result.name = name 116 | test "should unpack ordinary objects": 117 | tim.unpackObject(name, job) 118 | check name == timName 119 | check job == fluffer 120 | 121 | test "should unpack object refs": 122 | timRef.unpackObject(name, job) 123 | check name == timName 124 | check job == fluffer 125 | 126 | test "should unpack object pointers": 127 | let timPtr = unsafeAddr(tim) 128 | timPtr.unpackObject(job, name) 129 | check name == timName 130 | check job == fluffer 131 | 132 | test "should not call proc multiple times when invoked after a chain of calls": 133 | tim.colleague(johnName).unpackObject(name, job) 134 | 135 | # is expanded into: 136 | # let someUniqueSym1_212_498 = tim.colleague(johnName) 137 | # let 138 | # name = someUniqueSym1_212_498.name 139 | # job = someUniqueSym1_212_498.job 140 | 141 | check name == johnName 142 | check job == fluffer 143 | check secreteCounter == 1 144 | 145 | test "should be able to rename object member with '=' sign": 146 | tim.unpackObject(name as otherName) 147 | 148 | check otherName == timName 149 | 150 | tim.unpackObject(job, name as yetAnotherName) # and is order-agnostic. 151 | 152 | check yetAnotherName == timName 153 | check job == fluffer 154 | 155 | test "adding var before first item should create new variables": 156 | tim.unpackObject(var name as otherName, job) 157 | 158 | check otherName == timName 159 | check job == fluffer 160 | otherName = johnName 161 | 162 | check otherName == johnName 163 | 164 | test "aUnpackObject should assign to existing variables": 165 | var otherName, yetAnotherName, job = "" 166 | tim.aUnpackObject(name as otherName) 167 | 168 | check otherName == timName 169 | 170 | tim.aUnpackObject(job, name as yetAnotherName) # and is order-agnostic. 171 | 172 | check yetAnotherName == timName 173 | check job == fluffer 174 | 175 | suite "Object meber unpacking with arrow operators": 176 | type 177 | Person = object 178 | name, job: string 179 | PersonRef = ref Person 180 | setup: 181 | let timName = "Tim" 182 | let fluffer = "Fluffer" 183 | let johnName = "John" 184 | let poofer = "Poofer" 185 | let tim = Person(name: timName, job: fluffer) 186 | let timRef = new(Person) 187 | timRef.name = timName 188 | timRef.job = fluffer 189 | var secreteCounter = 0 190 | proc colleague(p: Person; name: string): Person = 191 | secreteCounter += 1 192 | result = p 193 | result.name = name 194 | test "should unpack ordinary objects": 195 | {name, job} <- tim 196 | check name == timName 197 | check job == fluffer 198 | 199 | test "should unpack object refs": 200 | {name, job} <- timRef 201 | check name == timName 202 | check job == fluffer 203 | 204 | test "should unpack object pointers": 205 | let timPtr = unsafeAddr(tim) 206 | {job, name} <- timPtr 207 | check name == timName 208 | check job == fluffer 209 | 210 | test "should not call proc multiple times when invoked after a chain of calls": 211 | {name, job} <- tim.colleague(johnName) 212 | 213 | # is expanded into: 214 | # let someUniqueSym1_212_498 = tim.colleague(johnName) 215 | # let 216 | # name = someUniqueSym1_212_498.name 217 | # job = someUniqueSym1_212_498.job 218 | 219 | check name == johnName 220 | check job == fluffer 221 | check secreteCounter == 1 222 | 223 | test "should be able to rename object member with ':' sign": 224 | {name as otherName} <- tim 225 | 226 | check otherName == timName 227 | 228 | {job, name as yetAnotherName} <- tim # and is order-agnostic. 229 | 230 | check yetAnotherName == timName 231 | check job == fluffer 232 | test "adding var before first item should create all symbol as mutable variables": 233 | {var name as otherName, job} <- tim 234 | 235 | check otherName == timName 236 | check job == fluffer 237 | 238 | job = poofer 239 | otherName = johnName 240 | 241 | check job == poofer 242 | check otherName == johnName 243 | test "<-- should assign data to existing variables": 244 | {var name, job} <- tim 245 | 246 | check name == timName 247 | check job == fluffer 248 | 249 | {name, job} <-- tim.colleague(johnName) 250 | 251 | check name == johnName 252 | 253 | suite "Named tuple unpacking with arrow operators": 254 | setup: 255 | type 256 | Animal = tuple[numLegs: int; name, genus: string] 257 | let carolyn = "Carolyn" 258 | let felis = "Felis" 259 | let equus = "Equus" 260 | let bojack = "BoJack" 261 | let cat = (numLegs: 2, name: carolyn, genus: felis).Animal 262 | let horse = (numLegs: 2, name: bojack, genus: equus).Animal 263 | test "should be unpackable with [] like unpacking sequences": 264 | [var l, n, g] <- cat 265 | check (l, n, g) == (2, carolyn, felis) 266 | [l, n] <-- horse 267 | check (l, n, g) == (2, bojack, felis) 268 | [_, _, g] <-- horse 269 | check (l, n, g) == (2, bojack, equus) 270 | 271 | test "should be unpackable with {} like unpacking objects": 272 | {var numLegs as l, name as n, genus as g} <- cat 273 | check (l, n, g) == (2, carolyn, felis) 274 | {name as n, numLegs as l} <-- horse 275 | check (l, n, g) == (2, bojack, felis) 276 | {genus as g} <-- horse 277 | check (l, n, g) == (2, bojack, equus) 278 | -------------------------------------------------------------------------------- /unpack.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.4.0" 4 | author = "T.agd" 5 | description = "Array/Sequence/Object destructuring/unpacking macro" 6 | license = "MIT" 7 | srcDir = "src" 8 | 9 | 10 | # Dependencies 11 | 12 | requires "nim >= 0.19.0" 13 | --------------------------------------------------------------------------------