192 lines
6.3 KiB
Nim
192 lines
6.3 KiB
Nim
# SPDX-License-Identifier: ISC
|
|
|
|
import std/[asyncdispatch, macros, options]
|
|
import preserves
|
|
import syndicate/[assertions, dataspaces, events, skeletons]
|
|
|
|
export assertions.`?_`
|
|
export assertions.`?*`
|
|
export assertions.Observe
|
|
export dataspaces.Facet
|
|
export dataspaces.FieldId
|
|
export dataspaces.Fields
|
|
export dataspaces.`==`
|
|
export dataspaces.addEndpoint
|
|
export dataspaces.addStartScript
|
|
export dataspaces.addStopScript
|
|
export dataspaces.beginExternalTask
|
|
export dataspaces.defineObservableProperty
|
|
export dataspaces.endExternalTask
|
|
export dataspaces.generateId
|
|
export dataspaces.hash
|
|
export dataspaces.recordDamage
|
|
export dataspaces.recordObservation
|
|
export dataspaces.scheduleScript
|
|
export dataspaces.stop
|
|
export events.EventKind
|
|
export skeletons.Analysis
|
|
|
|
export asyncdispatch.`callback=`
|
|
|
|
proc getCurrentFacet*(): Facet = raiseAssert("must be called from within the DSL")
|
|
## Return the current `Facet` for this context.
|
|
|
|
template stopIf*(cond, body: untyped): untyped =
|
|
## Stop the current facet if `cond` is true and
|
|
## invoke `body` after the facet has stopped.
|
|
mixin getCurrentFacet
|
|
discard getCurrentFacet().addDataflow do (facet: Facet):
|
|
if cond:
|
|
facet.stop do (facet: Facet):
|
|
proc getCurrentFacet(): Facet {.inject, used.} = facet
|
|
body
|
|
|
|
template sendMessage*(msg: untyped): untyped =
|
|
mixin getCurrentFacet
|
|
send(getCurrentFacet(), toPreserve(msg))
|
|
|
|
proc wrapDoHandler(pattern, handler: NimNode): NimNode =
|
|
## Generate a procedure that unpacks a `pattern` match to fit the
|
|
## parameters of `handler`, and calls the body of `handler`.
|
|
handler.expectKind nnkDo
|
|
let
|
|
formalArgs = handler[3]
|
|
cbFacetSym = genSym(nskParam, "facet")
|
|
scriptFacetSym = genSym(nskParam, "facet")
|
|
recSym = genSym(nskParam, "bindings")
|
|
var
|
|
letSection = newNimNode(nnkLetSection, handler)
|
|
argCount: int
|
|
for i, arg in formalArgs:
|
|
if i > 0:
|
|
arg.expectKind nnkIdentDefs
|
|
if arg[0] == ident"_" or arg[0] == ident"*":
|
|
if arg[1].kind != nnkEmpty:
|
|
error("placeholders may not be typed", arg)
|
|
else:
|
|
if arg[1].kind == nnkEmpty:
|
|
error("type required for capture", arg)
|
|
var letDef = newNimNode(nnkIdentDefs, arg)
|
|
arg.copyChildrenTo letDef
|
|
letDef[2] = newCall("preserveTo",
|
|
newNimNode(nnkBracketExpr).add(recSym, newLit(pred i)),
|
|
letDef[1])
|
|
letSection.add(letDef)
|
|
inc(argCount)
|
|
let
|
|
scriptSym = genSym(nskProc, "script")
|
|
scriptBody = newStmtList(letSection, handler[6])
|
|
handlerSym = genSym(nskProc, "handler")
|
|
litArgCount = newLit argCount
|
|
quote do:
|
|
proc `handlerSym`(`cbFacetSym`: Facet; `recSym`: seq[Preserve]) =
|
|
assert(`litArgCount` == captureCount(`pattern`), "pattern does not match handler")
|
|
# this should be a compile-time check
|
|
assert(
|
|
`litArgCount` == len(`recSym`),
|
|
"cannot unpack " & $`litArgCount` & " bindings from " & $(%`recSym`))
|
|
proc `scriptSym`(`scriptFacetSym`: Facet) =
|
|
proc getCurrentFacet(): Facet {.inject, used.} = `scriptFacetSym`
|
|
`scriptBody`
|
|
scheduleScript(`cbFacetSym`, `scriptSym`)
|
|
|
|
proc wrapHandler(pattern, handler: NimNode): NimNode =
|
|
case handler.kind
|
|
of nnkDo:
|
|
result = wrapDoHandler(pattern, handler)
|
|
of nnkStmtList:
|
|
let sym = genSym(nskProc, "handler")
|
|
result = quote do:
|
|
proc `sym`(facet: Facet; _: seq[Preserve]) =
|
|
proc getCurrentFacet(): Facet {.inject, used.} = facet
|
|
`handler`
|
|
else:
|
|
error("unhandled event handler", handler)
|
|
|
|
proc onEvent(event: EventKind, pattern, handler: NimNode): NimNode =
|
|
let
|
|
handler = wrapHandler(pattern, handler)
|
|
handlerSym = handler[0]
|
|
result = quote do:
|
|
mixin getCurrentFacet
|
|
discard getCurrentFacet().addEndpoint do (facet: Facet) -> EndpointSpec:
|
|
proc getCurrentFacet(): Facet {.inject, used.} = facet
|
|
`handler`
|
|
let a = `pattern`
|
|
result.assertion = some(Observe % a)
|
|
result.analysis = some(analyzeAssertion(a))
|
|
result.analysis.get.callback = wrap(facet, EventKind(`event`), `handlerSym`)
|
|
|
|
macro onAsserted*(pattern: Preserve; handler: untyped) =
|
|
onEvent(addedEvent, pattern, handler)
|
|
|
|
macro onRetracted*(pattern: Preserve; handler: untyped) =
|
|
onEvent(removedEvent, pattern, handler)
|
|
|
|
macro onMessage*(pattern: Preserve; doHandler: untyped) =
|
|
onEvent(messageEvent, pattern, doHandler)
|
|
|
|
template onStart*(body: untyped): untyped =
|
|
mixin getCurrentFacet
|
|
getCurrentFacet().addStartScript do (facet: Facet):
|
|
proc getCurrentFacet(): Facet {.inject, used.} = facet
|
|
body
|
|
|
|
template onStop*(body: untyped): untyped =
|
|
mixin getCurrentFacet
|
|
getCurrentFacet().addStopScript do (facet: Facet):
|
|
proc getCurrentFacet(): Facet {.inject, used.} = facet
|
|
body
|
|
|
|
template assert*(a: Preserve): untyped =
|
|
mixin getCurrentFacet
|
|
let facet = getCurrentFacet()
|
|
discard facet.addEndpoint do (_: Facet) -> EndpointSpec:
|
|
result.assertion = some(a)
|
|
|
|
template field*(F: untyped; T: typedesc; initial: T): untyped =
|
|
## Declare a field. The identifier `F` shall be a value with
|
|
## `get` and `set` procs.
|
|
mixin getCurrentFacet
|
|
declareField(getCurrentFacet(), F, T, initial)
|
|
# use the template defined in dataspaces
|
|
|
|
template react*(body: untyped): untyped =
|
|
mixin getCurrentFacet
|
|
addChildFacet(getCurrentFacet()) do (facet: Facet):
|
|
proc getCurrentFacet(): Facet {.inject, used.} = facet
|
|
body
|
|
|
|
template stop*(body: untyped): untyped =
|
|
mixin getCurrentFacet
|
|
stop(getCurrentFacet()) do (facet: Facet):
|
|
proc getCurrentFacet(): Facet {.inject, used.} = facet
|
|
body
|
|
|
|
template stop*(): untyped =
|
|
mixin getCurrentFacet
|
|
stop(getCurrentFacet())
|
|
|
|
template during*(pattern: Preserve; handler: untyped) =
|
|
onAsserted(pattern):
|
|
react:
|
|
onAsserted(pattern, handler)
|
|
onRetracted(pattern): stop()
|
|
|
|
template spawn*(name: string; spawnBody: untyped): untyped =
|
|
mixin getCurrentFacet
|
|
spawn(getCurrentFacet(), name) do (spawnFacet: Facet):
|
|
proc getCurrentFacet(): Facet {.inject, used.} = spawnFacet
|
|
spawnBody
|
|
|
|
template syndicate*(ident, dataspaceBody: untyped): untyped =
|
|
proc `ident`*(facet: Facet) =
|
|
proc getCurrentFacet(): Facet {.inject, used.} = facet
|
|
dataspaceBody
|
|
when isMainModule:
|
|
asyncCheck bootModule("", `ident`)
|
|
|
|
template boot*(module: proc (facet: Facet) {.gcsafe.}) =
|
|
mixin getCurrentFacet
|
|
module(getCurrentFacet())
|