FutureVar[T] parameters are now completed automatically.
This commit is contained in:
parent
242af696dd
commit
927fce4c7f
4 changed files with 114 additions and 17 deletions
|
|
@ -156,7 +156,6 @@ export Port, SocketFlag
|
||||||
## * Can't await in a ``except`` body
|
## * Can't await in a ``except`` body
|
||||||
## * Forward declarations for async procs are broken,
|
## * Forward declarations for async procs are broken,
|
||||||
## link includes workaround: https://github.com/nim-lang/Nim/issues/3182.
|
## link includes workaround: https://github.com/nim-lang/Nim/issues/3182.
|
||||||
## * FutureVar[T] needs to be completed manually.
|
|
||||||
|
|
||||||
# TODO: Check if yielded future is nil and throw a more meaningful exception
|
# TODO: Check if yielded future is nil and throw a more meaningful exception
|
||||||
|
|
||||||
|
|
@ -263,6 +262,18 @@ proc complete*[T](future: FutureVar[T]) =
|
||||||
if fut.cb != nil:
|
if fut.cb != nil:
|
||||||
fut.cb()
|
fut.cb()
|
||||||
|
|
||||||
|
proc complete*[T](future: FutureVar[T], val: T) =
|
||||||
|
## Completes a ``FutureVar`` with value ``val``.
|
||||||
|
##
|
||||||
|
## Any previously stored value will be overwritten.
|
||||||
|
template fut: expr = Future[T](future)
|
||||||
|
checkFinished(fut)
|
||||||
|
assert(fut.error == nil)
|
||||||
|
fut.finished = true
|
||||||
|
fut.value = val
|
||||||
|
if fut.cb != nil:
|
||||||
|
fut.cb()
|
||||||
|
|
||||||
proc fail*[T](future: Future[T], error: ref Exception) =
|
proc fail*[T](future: Future[T], error: ref Exception) =
|
||||||
## Completes ``future`` with ``error``.
|
## Completes ``future`` with ``error``.
|
||||||
#assert(not future.finished, "Future already finished, cannot finish twice.")
|
#assert(not future.finished, "Future already finished, cannot finish twice.")
|
||||||
|
|
@ -311,17 +322,18 @@ proc injectStacktrace[T](future: Future[T]) =
|
||||||
msg.add("\n Empty or nil stack trace.")
|
msg.add("\n Empty or nil stack trace.")
|
||||||
future.error.msg.add(msg)
|
future.error.msg.add(msg)
|
||||||
|
|
||||||
proc read*[T](future: Future[T]): T =
|
proc read*[T](future: Future[T] | FutureVar[T]): T =
|
||||||
## Retrieves the value of ``future``. Future must be finished otherwise
|
## Retrieves the value of ``future``. Future must be finished otherwise
|
||||||
## this function will fail with a ``ValueError`` exception.
|
## this function will fail with a ``ValueError`` exception.
|
||||||
##
|
##
|
||||||
## If the result of the future is an error then that error will be raised.
|
## If the result of the future is an error then that error will be raised.
|
||||||
if future.finished:
|
let fut = Future[T](future)
|
||||||
if future.error != nil:
|
if fut.finished:
|
||||||
injectStacktrace(future)
|
if fut.error != nil:
|
||||||
raise future.error
|
injectStacktrace(fut)
|
||||||
|
raise fut.error
|
||||||
when T isnot void:
|
when T isnot void:
|
||||||
return future.value
|
return fut.value
|
||||||
else:
|
else:
|
||||||
# TODO: Make a custom exception type for this?
|
# TODO: Make a custom exception type for this?
|
||||||
raise newException(ValueError, "Future still in progress.")
|
raise newException(ValueError, "Future still in progress.")
|
||||||
|
|
@ -342,11 +354,11 @@ proc mget*[T](future: FutureVar[T]): var T =
|
||||||
## Future has not been finished.
|
## Future has not been finished.
|
||||||
result = Future[T](future).value
|
result = Future[T](future).value
|
||||||
|
|
||||||
proc finished*[T](future: Future[T]): bool =
|
proc finished*[T](future: Future[T] | FutureVar[T]): bool =
|
||||||
## Determines whether ``future`` has completed.
|
## Determines whether ``future`` has completed.
|
||||||
##
|
##
|
||||||
## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
|
## ``True`` may indicate an error or a value. Use ``failed`` to distinguish.
|
||||||
future.finished
|
(Future[T](future)).finished
|
||||||
|
|
||||||
proc failed*(future: FutureBase): bool =
|
proc failed*(future: FutureBase): bool =
|
||||||
## Determines whether ``future`` completed with an error.
|
## Determines whether ``future`` completed with an error.
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
|
||||||
result = node[0]
|
result = node[0]
|
||||||
|
|
||||||
template createCb(retFutureSym, iteratorNameSym,
|
template createCb(retFutureSym, iteratorNameSym,
|
||||||
name: untyped) =
|
name, futureVarCompletions: untyped) =
|
||||||
var nameIterVar = iteratorNameSym
|
var nameIterVar = iteratorNameSym
|
||||||
#{.push stackTrace: off.}
|
#{.push stackTrace: off.}
|
||||||
proc cb {.closure,gcsafe.} =
|
proc cb {.closure,gcsafe.} =
|
||||||
|
|
@ -44,6 +44,8 @@ template createCb(retFutureSym, iteratorNameSym,
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
retFutureSym.fail(getCurrentException())
|
retFutureSym.fail(getCurrentException())
|
||||||
|
|
||||||
|
futureVarCompletions
|
||||||
cb()
|
cb()
|
||||||
#{.pop.}
|
#{.pop.}
|
||||||
proc generateExceptionCheck(futSym,
|
proc generateExceptionCheck(futSym,
|
||||||
|
|
@ -119,8 +121,22 @@ template createVar(result: var NimNode, futSymName: string,
|
||||||
result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y
|
result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y
|
||||||
useVar(result, futSym, valueReceiver, rootReceiver, fromNode)
|
useVar(result, futSym, valueReceiver, rootReceiver, fromNode)
|
||||||
|
|
||||||
|
proc createFutureVarCompletions(futureVarIdents: seq[NimNode]): NimNode
|
||||||
|
{.compileTime.} =
|
||||||
|
result = newStmtList()
|
||||||
|
# Add calls to complete each FutureVar parameter.
|
||||||
|
for ident in futureVarIdents:
|
||||||
|
# Only complete them if they have not been completed already by the user.
|
||||||
|
result.add newIfStmt(
|
||||||
|
(
|
||||||
|
newCall(newIdentNode("not"),
|
||||||
|
newDotExpr(ident, newIdentNode("finished"))),
|
||||||
|
newCall(newIdentNode("complete"), ident)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
proc processBody(node, retFutureSym: NimNode,
|
proc processBody(node, retFutureSym: NimNode,
|
||||||
subTypeIsVoid: bool,
|
subTypeIsVoid: bool, futureVarIdents: seq[NimNode],
|
||||||
tryStmt: NimNode): NimNode {.compileTime.} =
|
tryStmt: NimNode): NimNode {.compileTime.} =
|
||||||
#echo(node.treeRepr)
|
#echo(node.treeRepr)
|
||||||
result = node
|
result = node
|
||||||
|
|
@ -134,11 +150,14 @@ proc processBody(node, retFutureSym: NimNode,
|
||||||
else:
|
else:
|
||||||
result.add newCall(newIdentNode("complete"), retFutureSym)
|
result.add newCall(newIdentNode("complete"), retFutureSym)
|
||||||
else:
|
else:
|
||||||
let x = node[0].processBody(retFutureSym, subTypeIsVoid, tryStmt)
|
let x = node[0].processBody(retFutureSym, subTypeIsVoid,
|
||||||
|
futureVarIdents, tryStmt)
|
||||||
if x.kind == nnkYieldStmt: result.add x
|
if x.kind == nnkYieldStmt: result.add x
|
||||||
else:
|
else:
|
||||||
result.add newCall(newIdentNode("complete"), retFutureSym, x)
|
result.add newCall(newIdentNode("complete"), retFutureSym, x)
|
||||||
|
|
||||||
|
result.add createFutureVarCompletions(futureVarIdents)
|
||||||
|
|
||||||
result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
|
result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
|
||||||
return # Don't process the children of this return stmt
|
return # Don't process the children of this return stmt
|
||||||
of nnkCommand, nnkCall:
|
of nnkCommand, nnkCall:
|
||||||
|
|
@ -196,7 +215,8 @@ proc processBody(node, retFutureSym: NimNode,
|
||||||
# Transform ``except`` body.
|
# Transform ``except`` body.
|
||||||
# TODO: Could we perform some ``await`` transformation here to get it
|
# TODO: Could we perform some ``await`` transformation here to get it
|
||||||
# working in ``except``?
|
# working in ``except``?
|
||||||
tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid, nil)
|
tryBody[1] = processBody(n[1], retFutureSym, subTypeIsVoid,
|
||||||
|
futureVarIdents, nil)
|
||||||
|
|
||||||
proc processForTry(n: NimNode, i: var int,
|
proc processForTry(n: NimNode, i: var int,
|
||||||
res: NimNode): bool {.compileTime.} =
|
res: NimNode): bool {.compileTime.} =
|
||||||
|
|
@ -207,7 +227,7 @@ proc processBody(node, retFutureSym: NimNode,
|
||||||
var skipped = n.skipStmtList()
|
var skipped = n.skipStmtList()
|
||||||
while i < skipped.len:
|
while i < skipped.len:
|
||||||
var processed = processBody(skipped[i], retFutureSym,
|
var processed = processBody(skipped[i], retFutureSym,
|
||||||
subTypeIsVoid, n)
|
subTypeIsVoid, futureVarIdents, n)
|
||||||
|
|
||||||
# Check if we transformed the node into an exception check.
|
# Check if we transformed the node into an exception check.
|
||||||
# This suggests skipped[i] contains ``await``.
|
# This suggests skipped[i] contains ``await``.
|
||||||
|
|
@ -239,7 +259,8 @@ proc processBody(node, retFutureSym: NimNode,
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
for i in 0 .. <result.len:
|
for i in 0 .. <result.len:
|
||||||
result[i] = processBody(result[i], retFutureSym, subTypeIsVoid, nil)
|
result[i] = processBody(result[i], retFutureSym, subTypeIsVoid,
|
||||||
|
futureVarIdents, nil)
|
||||||
|
|
||||||
proc getName(node: NimNode): string {.compileTime.} =
|
proc getName(node: NimNode): string {.compileTime.} =
|
||||||
case node.kind
|
case node.kind
|
||||||
|
|
@ -252,6 +273,14 @@ proc getName(node: NimNode): string {.compileTime.} =
|
||||||
else:
|
else:
|
||||||
error("Unknown name.")
|
error("Unknown name.")
|
||||||
|
|
||||||
|
proc getFutureVarIdents(params: NimNode): seq[NimNode] {.compileTime.} =
|
||||||
|
result = @[]
|
||||||
|
for i in 1 .. <len(params):
|
||||||
|
expectKind(params[i], nnkIdentDefs)
|
||||||
|
if params[i][1].kind == nnkBracketExpr and
|
||||||
|
($params[i][1][0].ident).normalize == "futurevar":
|
||||||
|
result.add(params[i][0])
|
||||||
|
|
||||||
proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
## This macro transforms a single procedure into a closure iterator.
|
## This macro transforms a single procedure into a closure iterator.
|
||||||
## The ``async`` macro supports a stmtList holding multiple async procedures.
|
## The ``async`` macro supports a stmtList holding multiple async procedures.
|
||||||
|
|
@ -282,6 +311,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
let subtypeIsVoid = returnType.kind == nnkEmpty or
|
let subtypeIsVoid = returnType.kind == nnkEmpty or
|
||||||
(baseType.kind == nnkIdent and returnType[1].ident == !"void")
|
(baseType.kind == nnkIdent and returnType[1].ident == !"void")
|
||||||
|
|
||||||
|
let futureVarIdents = getFutureVarIdents(prc[3])
|
||||||
|
|
||||||
var outerProcBody = newNimNode(nnkStmtList, prc[6])
|
var outerProcBody = newNimNode(nnkStmtList, prc[6])
|
||||||
|
|
||||||
# -> var retFuture = newFuture[T]()
|
# -> var retFuture = newFuture[T]()
|
||||||
|
|
@ -304,7 +335,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
# -> <proc_body>
|
# -> <proc_body>
|
||||||
# -> complete(retFuture, result)
|
# -> complete(retFuture, result)
|
||||||
var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter")
|
var iteratorNameSym = genSym(nskIterator, $prc[0].getName & "Iter")
|
||||||
var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid, nil)
|
var procBody = prc[6].processBody(retFutureSym, subtypeIsVoid,
|
||||||
|
futureVarIdents, nil)
|
||||||
# don't do anything with forward bodies (empty)
|
# don't do anything with forward bodies (empty)
|
||||||
if procBody.kind != nnkEmpty:
|
if procBody.kind != nnkEmpty:
|
||||||
if not subtypeIsVoid:
|
if not subtypeIsVoid:
|
||||||
|
|
@ -326,6 +358,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
# -> complete(retFuture)
|
# -> complete(retFuture)
|
||||||
procBody.add(newCall(newIdentNode("complete"), retFutureSym))
|
procBody.add(newCall(newIdentNode("complete"), retFutureSym))
|
||||||
|
|
||||||
|
procBody.add(createFutureVarCompletions(futureVarIdents))
|
||||||
|
|
||||||
var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
|
var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
|
||||||
procBody, nnkIteratorDef)
|
procBody, nnkIteratorDef)
|
||||||
closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure"))
|
closureIterator[4] = newNimNode(nnkPragma, prc[6]).add(newIdentNode("closure"))
|
||||||
|
|
@ -334,7 +368,8 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
|
||||||
# -> createCb(retFuture)
|
# -> createCb(retFuture)
|
||||||
#var cbName = newIdentNode("cb")
|
#var cbName = newIdentNode("cb")
|
||||||
var procCb = getAst createCb(retFutureSym, iteratorNameSym,
|
var procCb = getAst createCb(retFutureSym, iteratorNameSym,
|
||||||
newStrLitNode(prc[0].getName))
|
newStrLitNode(prc[0].getName),
|
||||||
|
createFutureVarCompletions(futureVarIdents))
|
||||||
outerProcBody.add procCb
|
outerProcBody.add procCb
|
||||||
|
|
||||||
# -> return retFuture
|
# -> return retFuture
|
||||||
|
|
|
||||||
47
tests/async/tfuturevar.nim
Normal file
47
tests/async/tfuturevar.nim
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import asyncdispatch
|
||||||
|
|
||||||
|
proc completeOnReturn(fut: FutureVar[string], x: bool) {.async.} =
|
||||||
|
if x:
|
||||||
|
fut.mget() = ""
|
||||||
|
fut.mget.add("foobar")
|
||||||
|
return
|
||||||
|
|
||||||
|
proc completeOnImplicitReturn(fut: FutureVar[string], x: bool) {.async.} =
|
||||||
|
if x:
|
||||||
|
fut.mget() = ""
|
||||||
|
fut.mget.add("foobar")
|
||||||
|
|
||||||
|
proc failureTest(fut: FutureVar[string], x: bool) {.async.} =
|
||||||
|
if x:
|
||||||
|
raise newException(Exception, "Test")
|
||||||
|
|
||||||
|
proc manualComplete(fut: FutureVar[string], x: bool) {.async.} =
|
||||||
|
if x:
|
||||||
|
fut.mget() = "Hello World"
|
||||||
|
fut.complete()
|
||||||
|
return
|
||||||
|
|
||||||
|
proc main() {.async.} =
|
||||||
|
var fut: FutureVar[string]
|
||||||
|
|
||||||
|
fut = newFutureVar[string]()
|
||||||
|
await completeOnReturn(fut, true)
|
||||||
|
doAssert(fut.read() == "foobar")
|
||||||
|
|
||||||
|
fut = newFutureVar[string]()
|
||||||
|
await completeOnImplicitReturn(fut, true)
|
||||||
|
doAssert(fut.read() == "foobar")
|
||||||
|
|
||||||
|
fut = newFutureVar[string]()
|
||||||
|
let retFut = failureTest(fut, true)
|
||||||
|
yield retFut
|
||||||
|
doAssert(fut.read().isNil)
|
||||||
|
doAssert(fut.finished)
|
||||||
|
|
||||||
|
fut = newFutureVar[string]()
|
||||||
|
await manualComplete(fut, true)
|
||||||
|
doAssert(fut.read() == "Hello World")
|
||||||
|
|
||||||
|
|
||||||
|
waitFor main()
|
||||||
|
|
||||||
|
|
@ -104,6 +104,9 @@ Library Additions
|
||||||
- Added a new macro called ``multisync`` allowing you to write procedures for
|
- Added a new macro called ``multisync`` allowing you to write procedures for
|
||||||
synchronous and asynchronous sockets with no duplication.
|
synchronous and asynchronous sockets with no duplication.
|
||||||
|
|
||||||
|
- The ``async`` macro will now complete ``FutureVar[T]`` parameters
|
||||||
|
automatically unless they have been completed already.
|
||||||
|
|
||||||
Compiler Additions
|
Compiler Additions
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue