asyncmacro.nim 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Dominik Picheta
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## AsyncMacro
  10. ## *************
  11. ## `asyncdispatch` module depends on the `asyncmacro` module to work properly.
  12. import macros, strutils, asyncfutures
  13. proc skipUntilStmtList(node: NimNode): NimNode {.compileTime.} =
  14. # Skips a nest of StmtList's.
  15. result = node
  16. if node[0].kind == nnkStmtList:
  17. result = skipUntilStmtList(node[0])
  18. proc skipStmtList(node: NimNode): NimNode {.compileTime.} =
  19. result = node
  20. if node[0].kind == nnkStmtList:
  21. result = node[0]
  22. template createCb(retFutureSym, iteratorNameSym,
  23. strName, identName, futureVarCompletions: untyped) =
  24. bind finished
  25. var nameIterVar = iteratorNameSym
  26. #{.push stackTrace: off.}
  27. proc identName {.closure.} =
  28. try:
  29. if not nameIterVar.finished:
  30. var next = nameIterVar()
  31. # Continue while the yielded future is already finished.
  32. while (not next.isNil) and next.finished:
  33. next = nameIterVar()
  34. if nameIterVar.finished:
  35. break
  36. if next == nil:
  37. if not retFutureSym.finished:
  38. let msg = "Async procedure ($1) yielded `nil`, are you await'ing a " &
  39. "`nil` Future?"
  40. raise newException(AssertionError, msg % strName)
  41. else:
  42. {.gcsafe.}:
  43. {.push hint[ConvFromXtoItselfNotNeeded]: off.}
  44. next.callback = (proc() {.closure, gcsafe.})(identName)
  45. {.pop.}
  46. except:
  47. futureVarCompletions
  48. if retFutureSym.finished:
  49. # Take a look at tasyncexceptions for the bug which this fixes.
  50. # That test explains it better than I can here.
  51. raise
  52. else:
  53. retFutureSym.fail(getCurrentException())
  54. identName()
  55. #{.pop.}
  56. template useVar(result: var NimNode, futureVarNode: NimNode, valueReceiver,
  57. rootReceiver: untyped, fromNode: NimNode) =
  58. ## Params:
  59. ## futureVarNode: The NimNode which is a symbol identifying the Future[T]
  60. ## variable to yield.
  61. ## fromNode: Used for better debug information (to give context).
  62. ## valueReceiver: The node which defines an expression that retrieves the
  63. ## future's value.
  64. ##
  65. ## rootReceiver: ??? TODO
  66. # -> yield future<x>
  67. result.add newNimNode(nnkYieldStmt, fromNode).add(futureVarNode)
  68. # -> future<x>.read
  69. valueReceiver = newDotExpr(futureVarNode, newIdentNode("read"))
  70. result.add rootReceiver
  71. template createVar(result: var NimNode, futSymName: string,
  72. asyncProc: NimNode,
  73. valueReceiver, rootReceiver: untyped,
  74. fromNode: NimNode) =
  75. result = newNimNode(nnkStmtList, fromNode)
  76. var futSym = genSym(nskVar, "future")
  77. result.add newVarStmt(futSym, asyncProc) # -> var future<x> = y
  78. useVar(result, futSym, valueReceiver, rootReceiver, fromNode)
  79. proc createFutureVarCompletions(futureVarIdents: seq[NimNode],
  80. fromNode: NimNode): NimNode {.compileTime.} =
  81. result = newNimNode(nnkStmtList, fromNode)
  82. # Add calls to complete each FutureVar parameter.
  83. for ident in futureVarIdents:
  84. # Only complete them if they have not been completed already by the user.
  85. # TODO: Once https://github.com/nim-lang/Nim/issues/5617 is fixed.
  86. # TODO: Add line info to the complete() call!
  87. # In the meantime, this was really useful for debugging :)
  88. #result.add(newCall(newIdentNode("echo"), newStrLitNode(fromNode.lineinfo)))
  89. result.add newIfStmt(
  90. (
  91. newCall(newIdentNode("not"),
  92. newDotExpr(ident, newIdentNode("finished"))),
  93. newCall(newIdentNode("complete"), ident)
  94. )
  95. )
  96. proc processBody(node, retFutureSym: NimNode,
  97. subTypeIsVoid: bool,
  98. futureVarIdents: seq[NimNode]): NimNode {.compileTime.} =
  99. #echo(node.treeRepr)
  100. result = node
  101. case node.kind
  102. of nnkReturnStmt:
  103. result = newNimNode(nnkStmtList, node)
  104. # As I've painfully found out, the order here really DOES matter.
  105. result.add createFutureVarCompletions(futureVarIdents, node)
  106. if node[0].kind == nnkEmpty:
  107. if not subTypeIsVoid:
  108. result.add newCall(newIdentNode("complete"), retFutureSym,
  109. newIdentNode("result"))
  110. else:
  111. result.add newCall(newIdentNode("complete"), retFutureSym)
  112. else:
  113. let x = node[0].processBody(retFutureSym, subTypeIsVoid,
  114. futureVarIdents)
  115. if x.kind == nnkYieldStmt: result.add x
  116. else:
  117. result.add newCall(newIdentNode("complete"), retFutureSym, x)
  118. result.add newNimNode(nnkReturnStmt, node).add(newNilLit())
  119. return # Don't process the children of this return stmt
  120. of nnkCommand, nnkCall:
  121. if node[0].kind == nnkIdent and node[0].eqIdent("await"):
  122. case node[1].kind
  123. of nnkIdent, nnkInfix, nnkDotExpr, nnkCall, nnkCommand:
  124. # await x
  125. # await x or y
  126. # await foo(p, x)
  127. # await foo p, x
  128. var futureValue: NimNode
  129. result.createVar("future" & $node[1][0].toStrLit, node[1], futureValue,
  130. futureValue, node)
  131. else:
  132. error("Invalid node kind in 'await', got: " & $node[1].kind)
  133. elif node.len > 1 and node[1].kind == nnkCommand and
  134. node[1][0].kind == nnkIdent and node[1][0].eqIdent("await"):
  135. # foo await x
  136. var newCommand = node
  137. result.createVar("future" & $node[0].toStrLit, node[1][1], newCommand[1],
  138. newCommand, node)
  139. of nnkVarSection, nnkLetSection:
  140. case node[0][2].kind
  141. of nnkCommand:
  142. if node[0][2][0].kind == nnkIdent and node[0][2][0].eqIdent("await"):
  143. # var x = await y
  144. var newVarSection = node # TODO: Should this use copyNimNode?
  145. result.createVar("future" & node[0][0].strVal, node[0][2][1],
  146. newVarSection[0][2], newVarSection, node)
  147. else: discard
  148. of nnkAsgn:
  149. case node[1].kind
  150. of nnkCommand:
  151. if node[1][0].eqIdent("await"):
  152. # x = await y
  153. var newAsgn = node
  154. result.createVar("future" & $node[0].toStrLit, node[1][1], newAsgn[1], newAsgn, node)
  155. else: discard
  156. of nnkDiscardStmt:
  157. # discard await x
  158. if node[0].kind == nnkCommand and node[0][0].kind == nnkIdent and
  159. node[0][0].eqIdent("await"):
  160. var newDiscard = node
  161. result.createVar("futureDiscard_" & $toStrLit(node[0][1]), node[0][1],
  162. newDiscard[0], newDiscard, node)
  163. of RoutineNodes-{nnkTemplateDef}:
  164. # skip all the nested procedure definitions
  165. return
  166. else: discard
  167. for i in 0 ..< result.len:
  168. result[i] = processBody(result[i], retFutureSym, subTypeIsVoid,
  169. futureVarIdents)
  170. proc getName(node: NimNode): string {.compileTime.} =
  171. case node.kind
  172. of nnkPostfix:
  173. return node[1].strVal
  174. of nnkIdent, nnkSym:
  175. return node.strVal
  176. of nnkEmpty:
  177. return "anonymous"
  178. else:
  179. error("Unknown name.")
  180. proc getFutureVarIdents(params: NimNode): seq[NimNode] {.compileTime.} =
  181. result = @[]
  182. for i in 1 ..< len(params):
  183. expectKind(params[i], nnkIdentDefs)
  184. if params[i][1].kind == nnkBracketExpr and
  185. params[i][1][0].eqIdent("futurevar"):
  186. result.add(params[i][0])
  187. proc isInvalidReturnType(typeName: string): bool =
  188. return typeName notin ["Future"] #, "FutureStream"]
  189. proc verifyReturnType(typeName: string) {.compileTime.} =
  190. if typeName.isInvalidReturnType:
  191. error("Expected return type of 'Future' got '$1'" %
  192. typeName)
  193. proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} =
  194. ## This macro transforms a single procedure into a closure iterator.
  195. ## The ``async`` macro supports a stmtList holding multiple async procedures.
  196. if prc.kind notin {nnkProcDef, nnkLambda, nnkMethodDef, nnkDo}:
  197. error("Cannot transform this node kind into an async proc." &
  198. " proc/method definition or lambda node expected.")
  199. let prcName = prc.name.getName
  200. let returnType = prc.params[0]
  201. var baseType: NimNode
  202. # Verify that the return type is a Future[T]
  203. if returnType.kind == nnkBracketExpr:
  204. let fut = repr(returnType[0])
  205. verifyReturnType(fut)
  206. baseType = returnType[1]
  207. elif returnType.kind in nnkCallKinds and returnType[0].eqIdent("[]"):
  208. let fut = repr(returnType[1])
  209. verifyReturnType(fut)
  210. baseType = returnType[2]
  211. elif returnType.kind == nnkEmpty:
  212. baseType = returnType
  213. else:
  214. verifyReturnType(repr(returnType))
  215. let subtypeIsVoid = returnType.kind == nnkEmpty or
  216. (baseType.kind == nnkIdent and returnType[1].eqIdent("void"))
  217. let futureVarIdents = getFutureVarIdents(prc.params)
  218. var outerProcBody = newNimNode(nnkStmtList, prc.body)
  219. # -> var retFuture = newFuture[T]()
  220. var retFutureSym = genSym(nskVar, "retFuture")
  221. var subRetType =
  222. if returnType.kind == nnkEmpty: newIdentNode("void")
  223. else: baseType
  224. outerProcBody.add(
  225. newVarStmt(retFutureSym,
  226. newCall(
  227. newNimNode(nnkBracketExpr, prc.body).add(
  228. newIdentNode("newFuture"),
  229. subRetType),
  230. newLit(prcName)))) # Get type from return type of this proc
  231. # -> iterator nameIter(): FutureBase {.closure.} =
  232. # -> {.push warning[resultshadowed]: off.}
  233. # -> var result: T
  234. # -> {.pop.}
  235. # -> <proc_body>
  236. # -> complete(retFuture, result)
  237. var iteratorNameSym = genSym(nskIterator, $prcName & "Iter")
  238. var procBody = prc.body.processBody(retFutureSym, subtypeIsVoid,
  239. futureVarIdents)
  240. # don't do anything with forward bodies (empty)
  241. if procBody.kind != nnkEmpty:
  242. procBody.add(createFutureVarCompletions(futureVarIdents, nil))
  243. if not subtypeIsVoid:
  244. procBody.insert(0, newNimNode(nnkPragma).add(newIdentNode("push"),
  245. newNimNode(nnkExprColonExpr).add(newNimNode(nnkBracketExpr).add(
  246. newIdentNode("warning"), newIdentNode("resultshadowed")),
  247. newIdentNode("off")))) # -> {.push warning[resultshadowed]: off.}
  248. procBody.insert(1, newNimNode(nnkVarSection, prc.body).add(
  249. newIdentDefs(newIdentNode("result"), baseType))) # -> var result: T
  250. procBody.insert(2, newNimNode(nnkPragma).add(
  251. newIdentNode("pop"))) # -> {.pop.})
  252. procBody.add(
  253. newCall(newIdentNode("complete"),
  254. retFutureSym, newIdentNode("result"))) # -> complete(retFuture, result)
  255. else:
  256. # -> complete(retFuture)
  257. procBody.add(newCall(newIdentNode("complete"), retFutureSym))
  258. var closureIterator = newProc(iteratorNameSym, [newIdentNode("FutureBase")],
  259. procBody, nnkIteratorDef)
  260. closureIterator.pragma = newNimNode(nnkPragma, lineInfoFrom=prc.body)
  261. closureIterator.addPragma(newIdentNode("closure"))
  262. # If proc has an explicit gcsafe pragma, we add it to iterator as well.
  263. if prc.pragma.findChild(it.kind in {nnkSym, nnkIdent} and $it == "gcsafe") != nil:
  264. closureIterator.addPragma(newIdentNode("gcsafe"))
  265. outerProcBody.add(closureIterator)
  266. # -> createCb(retFuture)
  267. # NOTE: The "_continue" suffix is checked for in asyncfutures.nim to produce
  268. # friendlier stack traces:
  269. var cbName = genSym(nskProc, prcName & "_continue")
  270. var procCb = getAst createCb(retFutureSym, iteratorNameSym,
  271. newStrLitNode(prcName),
  272. cbName,
  273. createFutureVarCompletions(futureVarIdents, nil))
  274. outerProcBody.add procCb
  275. # -> return retFuture
  276. outerProcBody.add newNimNode(nnkReturnStmt, prc.body[^1]).add(retFutureSym)
  277. result = prc
  278. if subtypeIsVoid:
  279. # Add discardable pragma.
  280. if returnType.kind == nnkEmpty:
  281. # Add Future[void]
  282. result.params[0] = parseExpr("Future[void]")
  283. if procBody.kind != nnkEmpty:
  284. result.body = outerProcBody
  285. #echo(treeRepr(result))
  286. #if prcName == "recvLineInto":
  287. # echo(toStrLit(result))
  288. macro async*(prc: untyped): untyped =
  289. ## Macro which processes async procedures into the appropriate
  290. ## iterators and yield statements.
  291. if prc.kind == nnkStmtList:
  292. result = newStmtList()
  293. for oneProc in prc:
  294. result.add asyncSingleProc(oneProc)
  295. else:
  296. result = asyncSingleProc(prc)
  297. when defined(nimDumpAsync):
  298. echo repr result
  299. # Multisync
  300. proc emptyNoop[T](x: T): T =
  301. # The ``await``s are replaced by a call to this for simplicity.
  302. when T isnot void:
  303. return x
  304. proc stripAwait(node: NimNode): NimNode =
  305. ## Strips out all ``await`` commands from a procedure body, replaces them
  306. ## with ``emptyNoop`` for simplicity.
  307. result = node
  308. let emptyNoopSym = bindSym("emptyNoop")
  309. case node.kind
  310. of nnkCommand, nnkCall:
  311. if node[0].kind == nnkIdent and node[0].eqIdent("await"):
  312. node[0] = emptyNoopSym
  313. elif node.len > 1 and node[1].kind == nnkCommand and
  314. node[1][0].kind == nnkIdent and node[1][0].eqIdent("await"):
  315. # foo await x
  316. node[1][0] = emptyNoopSym
  317. of nnkVarSection, nnkLetSection:
  318. case node[0][2].kind
  319. of nnkCommand:
  320. if node[0][2][0].kind == nnkIdent and node[0][2][0].eqIdent("await"):
  321. # var x = await y
  322. node[0][2][0] = emptyNoopSym
  323. else: discard
  324. of nnkAsgn:
  325. case node[1].kind
  326. of nnkCommand:
  327. if node[1][0].eqIdent("await"):
  328. # x = await y
  329. node[1][0] = emptyNoopSym
  330. else: discard
  331. of nnkDiscardStmt:
  332. # discard await x
  333. if node[0].kind == nnkCommand and node[0][0].kind == nnkIdent and
  334. node[0][0].eqIdent("await"):
  335. node[0][0] = emptyNoopSym
  336. else: discard
  337. for i in 0 ..< result.len:
  338. result[i] = stripAwait(result[i])
  339. proc splitParamType(paramType: NimNode, async: bool): NimNode =
  340. result = paramType
  341. if paramType.kind == nnkInfix and paramType[0].strVal in ["|", "or"]:
  342. let firstAsync = "async" in paramType[1].strVal.normalize
  343. let secondAsync = "async" in paramType[2].strVal.normalize
  344. if firstAsync:
  345. result = paramType[if async: 1 else: 2]
  346. elif secondAsync:
  347. result = paramType[if async: 2 else: 1]
  348. proc stripReturnType(returnType: NimNode): NimNode =
  349. # Strip out the 'Future' from 'Future[T]'.
  350. result = returnType
  351. if returnType.kind == nnkBracketExpr:
  352. let fut = repr(returnType[0])
  353. verifyReturnType(fut)
  354. result = returnType[1]
  355. proc splitProc(prc: NimNode): (NimNode, NimNode) =
  356. ## Takes a procedure definition which takes a generic union of arguments,
  357. ## for example: proc (socket: Socket | AsyncSocket).
  358. ## It transforms them so that ``proc (socket: Socket)`` and
  359. ## ``proc (socket: AsyncSocket)`` are returned.
  360. result[0] = prc.copyNimTree()
  361. # Retrieve the `T` inside `Future[T]`.
  362. let returnType = stripReturnType(result[0][3][0])
  363. result[0][3][0] = splitParamType(returnType, async=false)
  364. for i in 1 ..< result[0][3].len:
  365. # Sync proc (0) -> FormalParams (3) -> IdentDefs, the parameter (i) ->
  366. # parameter type (1).
  367. result[0][3][i][1] = splitParamType(result[0][3][i][1], async=false)
  368. result[0][6] = stripAwait(result[0][6])
  369. result[1] = prc.copyNimTree()
  370. if result[1][3][0].kind == nnkBracketExpr:
  371. result[1][3][0][1] = splitParamType(result[1][3][0][1], async=true)
  372. for i in 1 ..< result[1][3].len:
  373. # Async proc (1) -> FormalParams (3) -> IdentDefs, the parameter (i) ->
  374. # parameter type (1).
  375. result[1][3][i][1] = splitParamType(result[1][3][i][1], async=true)
  376. macro multisync*(prc: untyped): untyped =
  377. ## Macro which processes async procedures into both asynchronous and
  378. ## synchronous procedures.
  379. ##
  380. ## The generated async procedures use the ``async`` macro, whereas the
  381. ## generated synchronous procedures simply strip off the ``await`` calls.
  382. let (sync, asyncPrc) = splitProc(prc)
  383. result = newStmtList()
  384. result.add(asyncSingleProc(asyncPrc))
  385. result.add(sync)