2021-07-28 16:37:40 -05:00

324 lines
10 KiB
Nim

import # std libs
std/[macros, unicode]
import # task_runner libs
./runner
macro task*(kind: static TaskKind, stoppable: static bool, body: untyped): untyped =
result = newStmtList()
const
star = "*"
syncPost = "Sync"
taskArgPost = "TaskArg"
taskPost = "Task"
var
exported = false
starId = ident(star)
taskArgTypeId = ident(taskArgPost)
taskArgTypeDerivedId: NimNode
taskName: string
taskNameId: NimNode
taskNameImplId: NimNode
taskNameSyncId: NimNode
taskReturnTypeId: NimNode
if kind(body[0]) == nnkPostfix and body[0][0] == ident(star):
exported = true
taskName = strVal(body[0][1])
taskNameId = ident(taskName)
else:
taskNameId = body[0]
taskName = strVal(taskNameId)
taskArgTypeDerivedId = ident(taskName.capitalize & taskArgPost)
taskNameImplId = ident(taskName & taskPost)
taskNameSyncId = ident(taskName & syncPost)
taskReturnTypeId = body[3][0]
let
chanReturnToSenderId = ident("chanReturnToSender")
serializedPointerTypeId = ident("ByteAddress")
taskStoppedId = ident("taskStopped")
# The repetitiveness of some code below could/should be cleaned up with
# additional metaprogramming (and probably more informed use of shortcuts and
# helpers provided by Nim's macros module); there can be a task options
# object for which e.g. the `stoppable` field is a boolean flag, but then
# also a helper object/table with the same fields/keys but the values are
# tuples of the type names and field names to be added to the type derived
# from TaskArg; the fields of the supplied/default options object can be
# iterated over and the proper nnkIdentDefs, etc. can be built according to
# the options values and info in the helper object/table; the same (or very
# similar) technique could be used to allow e.g. specification of a
# TaskRunner instance and/or worker name and/or `ptr Atomic[bool]` (for
# stopping the task) in the options object, which would then affect whether
# parameters for those things are included in the type signatures of the
# constructed procs or instead baked into their bodies.
var
taskArgTypeDef = newNimNode(nnkTypeDef)
objDef = newNimNode(nnkObjectTy)
recList = newNimNode(nnkRecList)
taskArgTypeDef.add(taskArgTypeDerivedId)
taskArgTypeDef.add(newEmptyNode())
objDef.add(newEmptyNode())
objDef.add(newNimNode(nnkOfInherit).add(taskArgTypeId))
if kind == rts:
var
objParam = newNimNode(nnkIdentDefs)
post = newNimNode(nnkPostfix)
post.add(starId)
post.add(chanReturnToSenderId)
objParam.add(post)
objParam.add(serializedPointerTypeId)
objParam.add(newEmptyNode())
recList.add(objParam)
if stoppable == true:
var
objParam = newNimNode(nnkIdentDefs)
post = newNimNode(nnkPostfix)
post.add(starId)
post.add(taskStoppedId)
objParam.add(post)
objParam.add(serializedPointerTypeId)
objParam.add(newEmptyNode())
recList.add(objParam)
for nn in body[3]:
if kind(nn) == nnkIdentDefs:
var
objParam = newNimNode(nnkIdentDefs)
post = newNimNode(nnkPostfix)
post.add(starId)
post.add(nn[0])
objParam.add(post)
objParam.add(nn[1])
objParam.add(nn[2])
recList.add(objParam)
objDef.add(recList)
taskArgTypeDef.add(newNimNode(nnkRefTy).add(objDef))
result.add(newNimNode(nnkTypeSection).add(taskArgTypeDef))
let
asyncPragmaId = ident("async")
atomicTypeId = ident("Atomic")
boolTypeId = ident("bool")
futureTypeId = ident("Future")
taskArgId = ident("taskArg")
taskArgEncId = ident("taskArgEncoded")
chanSendToHostId = ident("chanSendToHost")
chanSendToWorkerId = ident("chanSendToWorker")
stoppedId = ident("stopped")
taskRunnerId = ident("taskRunner")
taskRunnerTypeId = ident("TaskRunner")
workerChannelTypeId = ident("WorkerChannel")
workerId = ident("worker")
workerNameId = ident("workerName")
workerNameTypeId = ident("string")
workerRunningId = ident("workerRunning")
var
atomPtr = newNimNode(nnkPtrTy)
atomBracket = newNimNode(nnkBracketExpr)
atomBracket.add(atomicTypeId)
atomBracket.add(boolTypeId)
atomPtr.add(atomBracket)
let
taskStoppedTypeId = atomPtr
workerRunningTypeId = atomPtr
var impl = newStmtList()
impl.add quote do:
let
`taskArgId` = decode[`taskArgTypeDerivedId`](`taskArgEncId`)
`chanSendToHostId` = cast[`workerChannelTypeId`](`taskArgId`.`chanSendToHostId`)
if kind == rts:
impl.add quote do:
let `chanReturnToSenderId` = cast[`workerChannelTypeId`](`taskArgId`.`chanReturnToSenderId`)
if stoppable == true:
impl.add quote do:
var `taskStoppedId` = cast[`taskStoppedTypeId`](`taskArgId`.`taskStoppedId`)
impl.add quote do:
var `workerRunningId` = cast[`workerRunningTypeId`](`taskArgId`.`workerRunningId`)
for nn in body[3]:
if kind(nn) == nnkIdentDefs:
let id = nn[0]
impl.add quote do:
let `id` = `taskArgId`.`id`
impl.add(body[6])
result.add quote do:
const `taskNameImplId`: Task = proc(`taskArgEncId`: string) {.async, gcsafe, nimcall, raises: [Defect].} =
`impl`
var
taskBody = newStmtList()
taskSyncBody = newStmtList()
taskProcDef = newNimNode(nnkProcDef)
taskProcSyncDef = newNimNode(nnkProcDef)
taskProcParams = newNimNode(nnkFormalParams)
stoppedIdentDefs = newNimNode(nnkIdentDefs)
taskRunnerIdentDefs = newNimNode(nnkIdentDefs)
workerNameIdentDefs = newNimNode(nnkIdentDefs)
taskProcDef.add(taskNameId)
taskProcDef.add(newEmptyNode())
taskProcDef.add(body[2])
taskProcParams.add(newEmptyNode())
taskRunnerIdentDefs.add(taskRunnerId)
taskRunnerIdentDefs.add(taskRunnerTypeId)
taskRunnerIdentDefs.add(newEmptyNode())
taskProcParams.add(taskRunnerIdentDefs)
workerNameIdentDefs.add(workerNameId)
workerNameIdentDefs.add(workerNameTypeId)
workerNameIdentDefs.add(newEmptyNode())
taskProcParams.add(workerNameIdentDefs)
if stoppable == true:
stoppedIdentDefs.add(stoppedId)
stoppedIdentDefs.add(taskStoppedTypeId)
stoppedIdentDefs.add(newEmptyNode())
taskProcParams.add(stoppedIdentDefs)
for nn in body[3]:
if kind(nn) == nnkIdentDefs:
taskProcParams.add(nn)
taskProcDef.add(taskProcParams)
taskProcDef.add(newNimNode(nnkPragma).add(asyncPragmaId))
taskProcDef.add(newEmptyNode())
copyChildrenTo(taskProcDef, taskProcSyncDef)
taskProcSyncDef[0] = taskNameSyncId
taskProcSyncDef[4] = newEmptyNode()
if kind == rts:
if kind(taskReturnTypeId) != nnkEmpty:
var futureBracket = newNimNode(nnkBracketExpr)
futureBracket.add(futureTypeId)
futureBracket.add(taskReturnTypeId)
taskProcDef[3][0] = futureBracket
taskProcSyncDef[3][0] = taskReturnTypeId
taskBody.add quote do:
let
`workerId` = taskRunner.workers[workerName].worker
`chanSendToHostId` = `workerId`.chanRecvFromWorker
`chanSendToWorkerId` = `workerId`.chanSendToWorker
if kind == rts:
taskBody.add quote do:
let `chanReturnToSenderId` = newWorkerChannel()
taskBody.add quote do:
let `taskArgId` = `taskArgTypeDerivedId`(
`chanSendToHostId`: cast[`serializedPointerTypeId`](`chanSendToHostId`),
task: cast[`serializedPointerTypeId`](`taskNameImplId`),
taskName: `taskName`,
`workerRunningId`: cast[`serializedPointerTypeId`](addr taskRunner.running)
)
var taskArgConstructor = taskBody[if kind == rts: 2 else: 1][0][2]
if kind == rts:
var objField = newNimNode(nnkExprColonExpr)
objField.add(chanReturnToSenderId)
objField.add quote do: cast[`serializedPointerTypeId`](`chanReturnToSenderId`)
taskArgConstructor.add(objField)
if stoppable == true:
var objField = newNimNode(nnkExprColonExpr)
objField.add(taskStoppedId)
objField.add quote do: cast[`serializedPointerTypeId`](`stoppedId`)
taskArgConstructor.add(objField)
for nn in body[3]:
var objField = newNimNode(nnkExprColonExpr)
if kind(nn) == nnkIdentDefs:
objField.add(nn[0])
objField.add(nn[0])
taskArgConstructor.add(objField)
if kind == rts:
taskBody.add quote do:
`chanReturnToSenderId`.open()
copyChildrenTo(taskBody, taskSyncBody)
taskBody.add quote do:
asyncSpawn `chanSendToWorkerId`.send(`taskArgId`.encode.safe)
taskSyncBody.add quote do:
`chanSendToWorkerId`.sendSync(`taskArgId`.encode.safe)
if kind == rts:
if kind(taskReturnTypeId) != nnkEmpty:
taskBody.add quote do:
let res = decode[`taskReturnTypeId`]($(await `chanReturnToSenderId`.recv()))
`chanReturnToSenderId`.close()
return res
taskSyncBody.add quote do:
let res = decode[`taskReturnTypeId`]($`chanReturnToSenderId`.recvSync())
`chanReturnToSenderId`.close()
return res
else:
taskBody.add quote do:
discard $(await `chanReturnToSenderId`.recv())
`chanReturnToSenderId`.close()
taskSyncBody.add quote do:
discard $`chanReturnToSenderId`.recvSync()
`chanReturnToSenderId`.close()
taskProcDef.add(taskBody)
taskProcSyncDef.add(taskSyncBody)
result.add(taskProcDef)
result.add(taskProcSyncDef)
if exported:
result.add quote do:
export `taskArgTypeDerivedId`, `taskNameId`, `taskNameSyncId`, `taskNameImplId`
# debug ----------------------------------------------------------------------
# echo toStrLit(result)
# The approach below doesn't work because unexpected things can happen with the
# AST of `body`, at least that's that what I observed; can look into a
# different approach:
# https://github.com/beef331/kashae/blob/master/src/kashae.nim#L204-L220
# (that approach was recommended in #main channel of the official Nim discord
# server when I asked about the unexpected AST things)
# macro task*(kind: TaskKind, body: untyped): untyped =
# result = newStmtList()
# result.add(quote do: task(`kind`, false, `body`))
#
# macro task*(stoppable: bool, body: untyped): untyped =
# result = newStmtList()
# result.add(quote do: task(no_rts, `stoppable`, `body`))
#
# macro task*(body: untyped): untyped =
# result = newStmtList()
# result.add(quote do: task(no_rts, false, `body`))