175 lines
5.8 KiB
Nim
175 lines
5.8 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.addEndpoint
|
|
export dataspaces.addStartScript
|
|
export dataspaces.addStopScript
|
|
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 newLit(p: pointer): NimNode = ident"nil"
|
|
## Hack to make `newLit` work on `Presevere`.
|
|
|
|
proc getCurrentFacet*(): Facet {.error.}
|
|
## 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 callbackForEvent(event: EventKind; pattern, handler: NimNode): NimNode =
|
|
## Generate a procedure that checks an event kind, unpacks `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
|
|
let
|
|
formalArgs = handler[3]
|
|
cbFacetSym = genSym(nskParam, "facet")
|
|
scriptFacetSym = genSym(nskParam, "facet")
|
|
eventSym = genSym(nskParam, "event")
|
|
recSym = genSym(nskParam, "record")
|
|
var
|
|
letSection = newNimNode(nnkLetSection, handler)
|
|
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))),
|
|
newProc(
|
|
name = ident"getCurrentFacet",
|
|
params = [ ident"Facet" ],
|
|
body = scriptFacetSym,
|
|
pragmas = newNimNode(nnkPragma).add(ident"inject").add(ident"used")),
|
|
letSection,
|
|
handler[6]
|
|
)
|
|
)
|
|
newProc(
|
|
# the event handler that is called when an assertion matches
|
|
name = genSym(nskProc, "event_handler"),
|
|
params = [
|
|
newEmptyNode(),
|
|
newIdentDefs(cbFacetSym, ident"Facet"),
|
|
newIdentDefs(eventSym, ident"EventKind"),
|
|
newIdentDefs(recSym, newNimNode(nnkBracketExpr).add(ident"seq",
|
|
ident"Preserve")),
|
|
],
|
|
body = newStmtList(
|
|
newIfStmt((cond: infix(eventSym, "==", newLit(event)), body:
|
|
newStmtList(
|
|
script,
|
|
newCall("scheduleScript", cbFacetSym, script[0])
|
|
)))))
|
|
# TODO: this proc just checks the event type and then schedules a script,
|
|
# should the event check be done in skeletons instead?
|
|
|
|
proc onEvent(event: EventKind; pattern, handler: NimNode): NimNode =
|
|
let
|
|
handler = callbackForEvent(event, pattern, handler)
|
|
handlerSym = handler[0]
|
|
result = quote do:
|
|
`handler`
|
|
mixin getCurrentFacet
|
|
discard getCurrentFacet().addEndpoint do (facet: Facet) -> EndpointSpec:
|
|
let a = `pattern`
|
|
result.assertion = some(toPreserve(observe(a)))
|
|
result.analysis = some(analyzeAssertion(a))
|
|
result.analysis.get.callback = wrap(facet, `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 spawn*(name: string; spawnBody: untyped): untyped =
|
|
mixin getCurrentFacet
|
|
spawn(getCurrentFacet(), name) do (spawnFacet: Facet):
|
|
proc getCurrentFacet(): Facet {.inject, used.} = spawnFacet
|
|
spawnBody
|
|
|
|
template syndicate*(name: string; dataspaceBody: untyped): untyped =
|
|
proc bootProc(rootFacet: Facet) =
|
|
proc getCurrentFacet(): Facet {.inject, used.} = rootFacet
|
|
dataspaceBody
|
|
asyncCheck bootModule(name, bootProc)
|