syndicate-nim/src/syndicate.nim

165 lines
5.3 KiB
Nim
Raw Normal View History

2021-06-29 15:18:09 +00:00
# SPDX-License-Identifier: ISC
import std/[asyncdispatch, macros, options]
import preserves
import syndicate/[assertions, dataspaces, events, skeletons]
2021-06-29 15:18:09 +00:00
export assertions.`?_`
export assertions.`?*`
2021-06-29 15:18:09 +00:00
export assertions.Observe
export dataspaces.Facet
export dataspaces.FieldId
export dataspaces.Fields
export dataspaces.addEndpoint
export dataspaces.addStartScript
export dataspaces.addStopScript
2021-06-29 15:18:09 +00:00
export dataspaces.defineObservableProperty
export dataspaces.generateId
export dataspaces.hash
export dataspaces.recordDamage
export dataspaces.recordObservation
export dataspaces.scheduleScript
export events.EventKind
export skeletons.Analysis
export asyncdispatch.`callback=`
proc `==`*(x, y: FieldId): bool {.borrow.}
proc getCurrentFacet*(): Facet {.error.}
2021-06-29 15:18:09 +00:00
## 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):
2021-06-30 08:47:54 +00:00
proc getCurrentFacet(): Facet {.inject, used.} = facet
2021-06-29 15:18:09 +00:00
body
template sendMessage*(msg: untyped): untyped =
2021-06-29 15:18:09 +00:00
mixin getCurrentFacet
send(getCurrentFacet(), toPreserve(msg))
2021-07-07 09:47:17 +00:00
proc wrapHandler(handler: NimNode): NimNode =
## Generate a procedure that unpacks a `pattern` match to fit the
## parameters of `handler`, and calls the body of `handler`.
# TODO: compile time analysis of pattern to count number of captures
handler.expectKind nnkDo
2021-06-29 15:18:09 +00:00
let
formalArgs = handler[3]
2021-06-29 15:18:09 +00:00
cbFacetSym = genSym(nskParam, "facet")
scriptFacetSym = genSym(nskParam, "facet")
2021-07-07 09:47:17 +00:00
recSym = genSym(nskParam, "bindings")
2021-06-29 15:18:09 +00:00
var
letSection = newNimNode(nnkLetSection, handler)
2021-06-29 15:18:09 +00:00
captureCount: 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(captureCount)
let script = newProc(
# the script scheduled by the callback when the event is matched
name = genSym(nskProc, "script"),
params = [
newEmptyNode(),
newIdentDefs(scriptFacetSym, ident"Facet"),
],
body = newStmtList(
newCall("assert",
infix(newCall("len", recSym), "==", newLit(captureCount))),
2021-06-30 08:47:54 +00:00
newProc(
name = ident"getCurrentFacet",
params = [ ident"Facet" ],
body = scriptFacetSym,
pragmas = newNimNode(nnkPragma).add(ident"inject").add(ident"used")),
2021-06-29 15:18:09 +00:00
letSection,
handler[6]
2021-06-29 15:18:09 +00:00
)
)
newProc(
# the event handler that is called when an assertion matches
name = genSym(nskProc, "event_handler"),
params = [
newEmptyNode(),
newIdentDefs(cbFacetSym, ident"Facet"),
newIdentDefs(recSym, newNimNode(nnkBracketExpr).add(ident"seq",
ident"Preserve")),
],
2021-07-07 09:47:17 +00:00
body =
2021-06-29 15:18:09 +00:00
newStmtList(
script,
newCall("scheduleScript", cbFacetSym, script[0])
2021-07-07 09:47:17 +00:00
))
2021-06-29 15:18:09 +00:00
# TODO: this proc just checks the event type and then schedules a script,
# should the event check be done in skeletons instead?
2021-07-07 09:47:17 +00:00
proc onEvent(event: EventKind; pattern, doHandler: NimNode): NimNode =
2021-06-29 15:18:09 +00:00
let
2021-07-07 09:47:17 +00:00
handler = wrapHandler(doHandler)
2021-06-29 15:18:09 +00:00
handlerSym = handler[0]
result = quote do:
`handler`
mixin getCurrentFacet
2021-07-07 09:47:17 +00:00
onEvent(getCurrentFacet(), `pattern`, EventKind(`event`), `handlerSym`)
2021-06-29 15:18:09 +00:00
macro onAsserted*(pattern: Preserve; handler: untyped) =
onEvent(addedEvent, pattern, handler)
2021-06-29 15:18:09 +00:00
macro onRetracted*(pattern: Preserve; handler: untyped) =
onEvent(removedEvent, pattern, handler)
2021-06-29 15:18:09 +00:00
macro onMessage*(pattern: Preserve; doHandler: untyped) =
onEvent(messageEvent, pattern, doHandler)
2021-06-29 15:18:09 +00:00
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 =
2021-06-29 15:18:09 +00:00
mixin getCurrentFacet
let facet = getCurrentFacet()
2021-06-30 08:47:54 +00:00
discard facet.addEndpoint do (_: Facet) -> EndpointSpec:
2021-06-29 15:18:09 +00:00
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 spawn*(name: string; spawnBody: untyped): untyped =
mixin getCurrentFacet
spawn(getCurrentFacet(), name) do (spawnFacet: Facet):
2021-06-30 08:47:54 +00:00
proc getCurrentFacet(): Facet {.inject, used.} = spawnFacet
2021-06-29 15:18:09 +00:00
spawnBody
template syndicate*(name: string; dataspaceBody: untyped): untyped =
proc bootProc(rootFacet: Facet) =
2021-06-30 08:47:54 +00:00
proc getCurrentFacet(): Facet {.inject, used.} = rootFacet
2021-06-29 15:18:09 +00:00
dataspaceBody
asyncCheck bootModule(name, bootProc)