mirror of
https://github.com/logos-storage/questionable.git
synced 2026-01-02 13:53:11 +00:00
tuple binding and unpacking support (#29)
* tuple binding and unpacking support * support tuple binding in nim < 1.6 * Use ? instead of Option[] in tests * Test binding of optional tuple, not just tuple * Add tests for tuple unpacking of Result * Remove unused import * Rearrange tuple binding code Introduce separate proc for creation of unpacking statement. Use `quote do` to make code as similar as possible to the `bindLet` and `bindVar` templates. * Add tests for failed tuple bindings --------- Co-authored-by: Mark Spanbroek <mark@spanbroek.net>
This commit is contained in:
parent
1dcef4b302
commit
096ca864b0
@ -30,14 +30,50 @@ template bindVar(name, expression): bool =
|
||||
placeholder(T)
|
||||
option.isSome
|
||||
|
||||
proc newUnpackTupleNode(names: NimNode, value: NimNode): NimNode =
|
||||
# builds tuple unpacking statement, eg: let (a, b) = value
|
||||
let vartuple = nnkVarTuple.newTree()
|
||||
for i in 0..<names.len:
|
||||
vartuple.add names[i]
|
||||
vartuple.add newEmptyNode()
|
||||
vartuple.add value
|
||||
nnkLetSection.newTree(vartuple)
|
||||
|
||||
macro bindTuple(names, expression): bool =
|
||||
let opt = ident("option")
|
||||
let evaluated = ident("evaluated")
|
||||
let T = ident("T")
|
||||
|
||||
let value = quote do:
|
||||
if `opt`.isSome:
|
||||
`opt`.unsafeGet()
|
||||
else:
|
||||
bindFailed(`evaluated`)
|
||||
placeholder(`T`)
|
||||
|
||||
let letsection = newUnpackTupleNode(names, value)
|
||||
|
||||
quote do:
|
||||
let `evaluated` = `expression`
|
||||
let `opt` = `evaluated`.option
|
||||
type `T` = typeof(`opt`.unsafeGet())
|
||||
`letsection`
|
||||
`opt`.isSome
|
||||
|
||||
macro `=?`*(name, expression): bool =
|
||||
## The `=?` operator lets you bind the value inside an Option or Result to a
|
||||
## new variable. It can be used inside of a conditional expression, for
|
||||
## instance in an `if` statement.
|
||||
|
||||
name.expectKind({nnkIdent, nnkVarTy})
|
||||
when (NimMajor, NimMinor) < (1, 6):
|
||||
name.expectKind({nnkIdent, nnkVarTy, nnkTupleConstr, nnkPar})
|
||||
else:
|
||||
name.expectKind({nnkIdent, nnkVarTy, nnkTupleConstr})
|
||||
|
||||
if name.kind == nnkIdent:
|
||||
quote do: bindLet(`name`, `expression`)
|
||||
elif name.kind == nnkTupleConstr or name.kind == nnkPar:
|
||||
quote do: bindTuple(`name`, `expression`)
|
||||
else:
|
||||
let name = name[0]
|
||||
quote do: bindVar(`name`, `expression`)
|
||||
|
||||
@ -165,6 +165,82 @@ suite "optionals":
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples":
|
||||
if (a, b) =? (some ("test", 1)):
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
if (a, b) =? none (string, int):
|
||||
discard a
|
||||
discard b
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples with named fields":
|
||||
if (a, b) =? (some (desc: "test", id: 1)):
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples returned from proc":
|
||||
proc returnsTuple(): ?tuple[name: string, id: int] = some ("test", 1)
|
||||
|
||||
if (a, b) =? returnsTuple():
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples returned from proc with unnamed fields":
|
||||
proc returnsTuple(): ?(string, int,) = some ("test", 1,)
|
||||
|
||||
if (a, b,) =? returnsTuple():
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples with _":
|
||||
if (_, b) =? some ("test", 1):
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples with named fields":
|
||||
if (a, b) =? some (desc: "test", id: 1):
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds variable to tuples with named fields":
|
||||
if t =? some (desc: "test", id: 1):
|
||||
check t.desc == "test"
|
||||
check t.id == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds to tuple types":
|
||||
type MyTuple = tuple
|
||||
desc: string
|
||||
id: int
|
||||
|
||||
let mt: MyTuple = ("test", 1)
|
||||
|
||||
if t =? (some mt):
|
||||
check t.desc == "test"
|
||||
check t.id == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
if (a, b) =? (some mt):
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "without statement can be used for early returns":
|
||||
proc test1 =
|
||||
without a =? 42.some:
|
||||
|
||||
@ -165,6 +165,82 @@ suite "result":
|
||||
|
||||
check called
|
||||
|
||||
test "=? binds and unpacks tuples":
|
||||
if (a, b) =? (success ("test", 1)):
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
if (a, b) =? (string, int).failure(error):
|
||||
discard a
|
||||
discard b
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples with named fields":
|
||||
if (a, b) =? (success (desc: "test", id: 1)):
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples returned from proc":
|
||||
proc returnsTuple(): ?!tuple[name: string, id: int] = success ("test", 1)
|
||||
|
||||
if (a, b) =? returnsTuple():
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples returned from proc with unnamed fields":
|
||||
proc returnsTuple(): ?!(string, int,) = success ("test", 1,)
|
||||
|
||||
if (a, b,) =? returnsTuple():
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples with _":
|
||||
if (_, b) =? success ("test", 1):
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds and unpacks tuples with named fields":
|
||||
if (a, b) =? success (desc: "test", id: 1):
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds variable to tuples with named fields":
|
||||
if t =? success (desc: "test", id: 1):
|
||||
check t.desc == "test"
|
||||
check t.id == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "=? binds to tuple types":
|
||||
type MyTuple = tuple
|
||||
desc: string
|
||||
id: int
|
||||
|
||||
let mt: MyTuple = ("test", 1)
|
||||
|
||||
if t =? (success mt):
|
||||
check t.desc == "test"
|
||||
check t.id == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
if (a, b) =? (success mt):
|
||||
check a == "test"
|
||||
check b == 1
|
||||
else:
|
||||
fail()
|
||||
|
||||
test "without statement works for results":
|
||||
proc test1 =
|
||||
without a =? 42.success:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user