tuple binding and unpacking support

This commit is contained in:
Eric Mastro 2023-02-09 22:12:46 +11:00
parent 1dcef4b302
commit 7a3d3430a4
No known key found for this signature in database
GPG Key ID: 141E3048D95A4E63
2 changed files with 104 additions and 1 deletions

View File

@ -1,5 +1,6 @@
import std/options
import std/macros
import std/sequtils
import ./private/binderror
proc option[T](option: Option[T]): Option[T] =
@ -30,14 +31,45 @@ template bindVar(name, expression): bool =
placeholder(T)
option.isSome
macro bindTuple(name, expression): bool =
let stmtList = newStmtList()
let opt = genSym(nskLet, "option")
let T = genSym(nskType, "T")
stmtList.add quote do:
let evaluated = `expression`
let `opt` = evaluated.option
type `T` = typeof(`opt`.unsafeGet())
let valueNode = quote do:
if `opt`.isSome:
`opt`.unsafeGet()
else:
bindFailed(evaluated)
placeholder(`T`)
stmtList.add nnkStmtList.newTree(
nnkLetSection.newTree(
nnkVarTuple.newTree(
name.children.toSeq.concat(
@[newEmptyNode(), valueNode]
)
)
)
)
stmtList.add quote do: `opt`.isSome
return stmtList
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})
name.expectKind({nnkIdent, nnkVarTy, nnkTupleConstr})
if name.kind == nnkIdent:
quote do: bindLet(`name`, `expression`)
elif name.kind == nnkTupleConstr:
quote do: bindTuple(`name`, `expression`)
else:
let name = name[0]
quote do: bindVar(`name`, `expression`)

View File

@ -165,6 +165,77 @@ suite "optionals":
else:
fail()
test "=? binds and unpacks tuples":
if (a, b) =? (some ("test", 1)):
check a == "test"
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 and unpacks tuples returned from proc":
proc returnsTuple(): Option[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(): Option[(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) =? ("test", 1):
check b == 1
else:
fail()
test "=? binds and unpacks tuples with named fields":
if (a, b) =? (desc: "test", id: 1):
check a == "test"
check b == 1
else:
fail()
test "=? binds variable to tuples with named fields":
if t =? (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: