syndicate-nim/src/syndicate.nim

184 lines
6.5 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, preserves/records
import syndicate/[assertions, dataspaces, events, skeletons]
2021-06-29 15:18:09 +00:00
export assertions.Capture
export assertions.Discard
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 newLit(p: pointer): NimNode = ident"nil"
## Hack to make `newLit` work on `Presevere`.
proc getCurrentFacet*(): Facet =
## Return the current `Facet` for this context.
raiseAssert "getCurrentFacet called outside of a Syndicate 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 send*(class: RecordClass; fields: varargs[Preserve,
toPreserve]): untyped =
mixin getCurrentFacet
send(getCurrentFacet(), init(class, fields))
proc assertionForRecord(class: RecordClass; doHandler: NimNode): NimNode =
## Generate an assertion that captures or discards the items of record `class`
## according to the parameters of `doHandler`. `_` parameters are discarded.
let formalArgs = doHandler[3]
if formalArgs.len.pred != class.arity:
error($formalArgs.repr & " does not match record class " & $class, doHandler)
result = newCall("init", newLit(class))
for i, arg in formalArgs:
if i > 0:
arg.expectKind nnkIdentDefs
if arg[0] == ident"_":
result.add newCall("init", ident"Discard")
else:
result.add newCall("init", ident"Capture", newCall("init",
ident"Discard"))
2021-06-30 08:47:54 +00:00
proc callbackForEvent(event: EventKind; class: RecordClass; doHandler: NimNode): NimNode =
2021-06-29 15:18:09 +00:00
## Generate a procedure that checks an event kind, unpacks the fields of `class` to match the
## parameters of `doHandler`, and calls the body of `doHandler`.
let formalArgs = doHandler[3]
if formalArgs.len.pred != class.arity:
error($formalArgs.repr & " does not match record class " & $class, doHandler)
doHandler.expectKind nnkDo
let
cbFacetSym = genSym(nskParam, "facet")
scriptFacetSym = genSym(nskParam, "facet")
eventSym = genSym(nskParam, "event")
recSym = genSym(nskParam, "record")
var
letSection = newNimNode(nnkLetSection, doHandler)
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,
doHandler[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; class: RecordClass; doHandler: NimNode): NimNode =
let
assertion = assertionForRecord(class, doHandler)
2021-06-30 08:47:54 +00:00
handler = callbackForEvent(event, class, doHandler)
2021-06-29 15:18:09 +00:00
handlerSym = handler[0]
result = quote do:
`handler`
mixin getCurrentFacet
discard getCurrentFacet().addEndpoint do (facet: Facet) -> EndpointSpec:
let a = `assertion`
result.assertion = some(init(Observe, a))
result.analysis = some(analyzeAssertion(a))
result.analysis.get.callback = wrap(facet, `handlerSym`)
macro onAsserted*(class: static[RecordClass]; doHandler: untyped) =
onEvent(addedEvent, class, doHandler)
macro onRetracted*(class: static[RecordClass]; doHandler: untyped) =
onEvent(removedEvent, class, doHandler)
macro onMessage*(class: static[RecordClass]; doHandler: untyped) =
onEvent(messageEvent, class, doHandler)
template assert*(class: RecordClass; field: untyped): untyped =
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
let a = init(class, getPreserve(field))
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)