FutureVar[T] parameters are now completed automatically.

This commit is contained in:
Dominik Picheta 2016-09-25 16:05:22 +02:00
commit 927fce4c7f
4 changed files with 114 additions and 17 deletions

View file

@ -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.

View file

@ -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

View 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()

View file

@ -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
------------------ ------------------