Skip to content

Commit 8732e66

Browse files
v0.4.0 Can now unpack nested structures.
1 parent c983d05 commit 8732e66

File tree

6 files changed

+240
-76
lines changed

6 files changed

+240
-76
lines changed

changeLog.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Change Log
22

3+
## v0.4.0
4+
5+
- Supports nested unpacking.
6+
7+
## v0.3.2
8+
9+
- Implements hack to enable rest unpacking after `var`. i.e. `[var _ as *a, b] <- someSeq`
10+
311
## v0.3.1
412

513
- 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)`.)
@@ -10,7 +18,7 @@
1018

1119
## v0.2.0
1220

13-
- Deprecating `unpack`, `lunpack`, and `vunpack` in favor of `unpackObject`, `unpackSeq`, `aUnpackObjec`, 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.
21+
- 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.
1422

1523
- Adds example and tests to demonstrate how to flexibly unpack named tuples (like a boss).
1624

readme.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ tim.someProcWithSideEffects(arg).unpackObject(name as tim0, job as job0)
9090
# {name as tim0, job as job0} <- tim.someProcWithSideEffects(arg)
9191
9292
# is expanded into:
93-
# let someUniqueSym1212498 = tim.someProcWithSideEffects(arg)
93+
# let someUniqueSym1_212_498 = tim.someProcWithSideEffects(arg)
9494
# let
95-
# tim0 = someUniqueSym1212498.name
96-
# job0 = someUniqueSym1212498.job
95+
# tim0 = someUniqueSym1_212_498.name
96+
# job0 = someUniqueSym1_212_498.job
9797
9898
# if you haven't noticed,
9999
# this means we can unpack named tuples like objects
@@ -116,7 +116,14 @@ let (_, diz, _, iz, _, _, _, it) = someTuple
116116
# with vanilla nim
117117
let (youNeedTo, writeSoMany, underscoresMan, _, _, _, _, _) = someTuple
118118
119+
# also supports nested unpacking
120+
let nestedTuple = ((123, 321), (-3, (1, 2, 3, 4)))
121+
122+
# This nesting is unnecessarily deep, but it's supported.
123+
[ [run, outOf], [names, [_, _, toUse, now]]] <- nestedTuple
124+
119125
# to be continued...
126+
120127
```
121128

122129
See [tests/theTest.nim](tests/theTest.nim) for more usages.
@@ -188,10 +195,26 @@ Under the hood, `unpack` just attaches `[countFromStart..^countFromEnd]` to what
188195

189196
Unless you implement the `..` operator (and its friends) yourself though.
190197

191-
##### Only one rest operator per unpack
198+
##### Only one rest operator per unpacked sequence
192199

193200
`[*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.
194201

202+
However, using rest operator in different parts of the nested sequence is fine, since they have different index counters, so this will work:
203+
204+
```nim
205+
[[*a, b], [c, d, *e], *f, g, h] <- someNestedSeq
206+
# is expanded into:
207+
# let
208+
# b = someNestedSeq[0][^1]
209+
# a = someNestedSeq[0][0..^2]
210+
# c = someNestedSeq[1][0]
211+
# d = someNestedSeq[1][1]
212+
# e = someNestedSeq[1][2..^1]
213+
# h = someNestedSeq[^1]
214+
# g = someNestedSeq[^2]
215+
# f = someNestedSeq[2..^3]
216+
```
217+
195218
##### Can't guard against incorrect index access at compile time
196219

197220
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:

src/unpack.nim

Lines changed: 101 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -31,32 +31,84 @@ proc getRealDest(dest: NimNode; restCount: var int): NimNode =
3131
result = result[1]
3232
else: discard
3333

34-
proc unpackSequenceInternal(srcNode, dests: NimNode; sec,
35-
statement: NimNodeKind): NimNode =
36-
result = newStmtList()
37-
var section = sec.newTree()
38-
var stNode = statement.newTree(newEmptyNode(), newEmptyNode())
39-
if statement != nnkAsgn:
40-
stNode.add(newEmptyNode())
41-
var src = srcNode
42-
# Creates a temporary symbol to store proc result.
43-
if src.kind != nnkSym:
44-
src = genSym(nskLet, "src")
45-
result.add(newLetStmt(src, srcNode))
34+
proc processSeqUnpack(section, src, dests, stNode: NimNode): NimNode
35+
36+
37+
proc processObjectUnpack(section, src, dests, stNode: NimNode): NimNode =
38+
result = section
39+
for dest in dests.children:
40+
case dest.kind:
41+
of nnkInfix:
42+
if dest[0].strVal == renameOp:
43+
var realDest = dest[1]
44+
case realDest.kind:
45+
of nnkVarTy:
46+
realDest = realDest[0]
47+
else: discard
48+
var newNode = stNode.copyNimTree
49+
var newSource = nnkDotExpr.newTree(src, realDest)
50+
let newDest = dest[2]
51+
case newDest.kind:
52+
of nnkCurly:
53+
result = processObjectUnpack(result, newSource, newDest, stNode)
54+
of nnkBracket:
55+
result = processSeqUnpack(result, newSource, newDest, stNode)
56+
else:
57+
newNode[0] = newDest
58+
newNode[^1] = newSource
59+
result.add(newNode)
60+
else:
61+
error("Only `" & renameOp & "` is allowed as the infix",
62+
dest[0])
63+
of nnkExprColonExpr, nnkExprEqExpr:
64+
var realDest = dest[0]
65+
case realDest.kind:
66+
of nnkVarTy:
67+
realDest = realDest[0]
68+
else: discard
69+
var newNode = stNode.copyNimTree
70+
newNode[0] = dest[1]
71+
newNode[^1] = nnkDotExpr.newTree(src, realDest)
72+
warning("This syntax is being deprecated, please use `{" &
73+
realDest.strVal & " as " & dest[
74+
1].strVal & "}` instead", dest)
75+
result.add(newNode)
76+
else:
77+
var realDest = dest
78+
case dest.kind:
79+
of nnkVarTy:
80+
realDest = dest[0]
81+
else: discard
82+
var newNode = stNode.copyNimTree
83+
newNode[0] = realDest
84+
newNode[^1] = nnkDotExpr.newTree(src, realDest)
85+
result.add(newNode)
86+
87+
proc processSeqUnpack(section, src, dests, stNode: NimNode): NimNode =
4688
var
4789
startInd = 0
4890
endCount = 0
4991
restCount = 0
5092
restDest = newLit(0)
93+
result = section
5194
# First pass from the front
5295
for dest in dests.children:
5396
let realDest = getRealDest(dest, restCount)
5497
var newNode = stNode.copyNimTree
55-
if realDest.strVal != skipOp:
56-
if restCount == 0:
57-
newNode[0] = realDest
58-
newNode[^1] = nnkBracketExpr.newTree(src, newLit(startInd))
59-
section.add(newNode)
98+
let newSource = nnkBracketExpr.newTree(src, newLit(startInd))
99+
let newDest = realDest
100+
case realDest.kind:
101+
of nnkCurly:
102+
result = processObjectUnpack(result, newSource, newDest, stNode)
103+
of nnkBracket:
104+
result = processSeqUnpack(result, newSource, newDest, stNode)
105+
else:
106+
if realDest.strVal != skipOp:
107+
if restCount == 0:
108+
109+
newNode[0] = realDest
110+
newNode[^1] = newSource
111+
result.add(newNode)
60112
if restCount == 0: startInd += 1
61113
else:
62114
if restDest.kind == nnkIntLit:
@@ -67,14 +119,21 @@ proc unpackSequenceInternal(srcNode, dests: NimNode; sec,
67119
for endInd in 1..endCount:
68120
let realDest = getRealDest(dests[^endInd], restCount)
69121
var newNode = stNode.copyNimTree
70-
71-
if realDest.strVal != skipOp:
72-
newNode[0] = realDest
73-
newNode[^1] = nnkBracketExpr.newTree(src, nnkPrefix.newTree(
122+
let newSource = nnkBracketExpr.newTree(src, nnkPrefix.newTree(
74123
newIdentNode("^"),
75124
newLit(endInd)
76125
))
77-
section.add(newNode)
126+
let newDest = realDest
127+
case realDest.kind:
128+
of nnkCurly:
129+
result = processObjectUnpack(result, newSource, newDest, stNode)
130+
of nnkBracket:
131+
result = processSeqUnpack(result, newSource, newDest, stNode)
132+
else:
133+
if realDest.strVal != skipOp:
134+
newNode[0] = realDest
135+
newNode[^1] = newSource
136+
result.add(newNode)
78137
# Adds rest statement
79138
if restCount == 1:
80139
var newNode = stNode.copyNimTree
@@ -85,64 +144,36 @@ proc unpackSequenceInternal(srcNode, dests: NimNode; sec,
85144
newLit(startInd),
86145
newLit(endCount+1)
87146
))
88-
section.add(newNode)
89-
90-
91-
result.add(section)
147+
result.add(newNode)
92148

93-
proc unpackObjectInternal(srcNode, dests: NimNode; sec,
94-
statement: NimNodeKind): NimNode =
95-
result = newStmtList()
149+
proc prepareHead(srcNode: NimNode; sec,
150+
statement: NimNodeKind): (NimNode, NimNode, NimNode, NimNode) =
151+
var tobeResult = newStmtList()
96152
var section = sec.newTree()
97153
var stNode = statement.newTree(newEmptyNode(), newEmptyNode())
98154
if statement != nnkAsgn:
99155
stNode.add(newEmptyNode())
100156
var src = srcNode
101157
# Creates a temporary symbol to store proc result.
102158
if src.kind != nnkSym:
103-
## echo src.treeRepr
104159
src = genSym(nskLet, "src")
105-
result.add(newLetStmt(src, srcNode))
106-
for dest in dests.children:
107-
case dest.kind:
108-
of nnkInfix:
109-
if dest[0].strVal == renameOp:
110-
var realDest = dest[1]
111-
case realDest.kind:
112-
of nnkVarTy:
113-
realDest = realDest[0]
114-
else: discard
115-
var newNode = stNode.copyNimTree
116-
newNode[0] = dest[2]
117-
newNode[^1] = nnkDotExpr.newTree(src, realDest)
118-
section.add(newNode)
119-
else:
120-
error("Only `" & renameOp & "` is allowed as the infix", dest[0])
121-
of nnkExprColonExpr, nnkExprEqExpr:
122-
var realDest = dest[0]
123-
case realDest.kind:
124-
of nnkVarTy:
125-
realDest = realDest[0]
126-
else: discard
127-
var newNode = stNode.copyNimTree
128-
newNode[0] = dest[1]
129-
newNode[^1] = nnkDotExpr.newTree(src, realDest)
130-
warning("This syntax is being deprecated, please use `{" &
131-
realDest.strVal & " as " & dest[
132-
1].strVal & "}` instead", dest)
160+
tobeResult.add(newLetStmt(src, srcNode))
161+
(tobeResult, section, src, stNode)
133162

134-
section.add(newNode)
135-
else:
136-
var realDest = dest
137-
case dest.kind:
138-
of nnkVarTy:
139-
realDest = dest[0]
140-
else: discard
141-
var newNode = stNode.copyNimTree
142-
newNode[0] = realDest
143-
newNode[^1] = nnkDotExpr.newTree(src, realDest)
144-
section.add(newNode)
163+
proc unpackSequenceInternal(srcNode, dests: NimNode; sec,
164+
statement: NimNodeKind): NimNode =
165+
var (tobeResult, section, src, stNode) = prepareHead(srcNode, sec,
166+
statement)
167+
result = tobeResult
168+
section = processSeqUnpack(section, src, dests, stNode)
169+
result.add(section)
145170

171+
proc unpackObjectInternal(srcNode, dests: NimNode; sec,
172+
statement: NimNodeKind): NimNode =
173+
var (tobeResult, section, src, stNode) = prepareHead(srcNode, sec,
174+
statement)
175+
result = tobeResult
176+
section = processObjectUnpack(section, src, dests, stNode)
146177
result.add(section)
147178

148179
macro `<-`*(dests: untyped; src: typed): typed =
@@ -243,3 +274,4 @@ macro aUnpackSeq*(src: typed; dests: varargs[untyped]): typed =
243274
## var a, b, c: int
244275
## src.aUnpackSeq(a, b, c)
245276
result = unpackSequenceInternal(src, dests, nnkStmtList, nnkAsgn)
277+

tests/nestedUnpack.nim

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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

0 commit comments

Comments
 (0)