syndicate-nim/src/syndicate.nim

222 lines
7.1 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 preserves.`%`
export preserves.fromPreserve
export records.init
export assertions.`?_`
export assertions.`?*`
2021-06-29 15:18:09 +00:00
export assertions.Observe
export dataspaces.Facet
export dataspaces.FieldId
export dataspaces.Fields
2021-07-08 09:50:13 +00:00
export dataspaces.`==`
2021-06-29 15:18:09 +00:00
export dataspaces.addEndpoint
export dataspaces.addStartScript
export dataspaces.addStopScript
export dataspaces.beginExternalTask
2021-06-29 15:18:09 +00:00
export dataspaces.defineObservableProperty
export dataspaces.endExternalTask
2021-06-29 15:18:09 +00:00
export dataspaces.generateId
export dataspaces.hash
export dataspaces.recordDamage
export dataspaces.recordObservation
export dataspaces.scheduleScript
2021-07-08 09:50:13 +00:00
export dataspaces.stop
2021-06-29 15:18:09 +00:00
export events.EventKind
export skeletons.Analysis
export asyncdispatch.`callback=`
export options.get
2021-06-29 15:18:09 +00:00
2021-07-08 09:50:13 +00:00
proc getCurrentFacet*(): Facet = raiseAssert("must be called from within the DSL")
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
2021-07-09 10:38:10 +00:00
getCurrentFacet().addDataflow do (facet: Facet):
2021-06-29 15:18:09 +00:00
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
2021-07-12 10:10:11 +00:00
template send*(msg: Preserve): untyped =
2021-06-29 15:18:09 +00:00
mixin getCurrentFacet
2021-07-12 10:10:11 +00:00
send(getCurrentFacet(), msg)
2021-07-08 09:50:13 +00:00
proc wrapDoHandler(pattern, handler: NimNode): NimNode =
2021-07-07 09:47:17 +00:00
## Generate a procedure that unpacks a `pattern` match to fit the
## parameters of `handler`, and calls the body of `handler`.
handler.expectKind nnkDo
var
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")
varSection = newNimNode(nnkVarSection, handler)
conditional: NimNode
2021-07-08 09:50:13 +00:00
argCount: int
2021-06-29 15:18:09 +00:00
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 varDef = newNimNode(nnkIdentDefs, arg)
arg.copyChildrenTo varDef
varSection.add(varDef)
var conversion = newCall("fromPreserve", varDef[0],
newNimNode(nnkBracketExpr).add(recSym, newLit(pred i)))
if conditional.isNil:
conditional = conversion
else:
conditional = infix(conditional, "and", conversion)
2021-07-08 09:50:13 +00:00
inc(argCount)
var scriptBody = newStmtList()
if argCount > 0:
scriptBody.add(
varSection,
newNimNode(nnkIfStmt).add(
newNimNode(nnkElifBranch).add(
conditional, handler[6])))
else:
scriptBody.add(handler[6])
var
2021-07-08 09:50:13 +00:00
scriptSym = genSym(nskProc, "script")
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 " & $(toPreserve `recSym`))
2021-07-08 09:50:13 +00:00
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)
2021-06-29 15:18:09 +00:00
handlerSym = handler[0]
result = quote do:
mixin getCurrentFacet
2021-07-09 10:38:10 +00:00
getCurrentFacet().addEndpoint do (facet: Facet) -> EndpointSpec:
2021-07-08 09:50:13 +00:00
proc getCurrentFacet(): Facet {.inject, used.} = facet
`handler`
let a = `pattern`
result.assertion = Observe.init(a)
2021-07-08 09:50:13 +00:00
result.analysis = some(analyzeAssertion(a))
2021-07-09 10:38:10 +00:00
result.callback = wrap(facet, 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 asserting*(a: Preserve): untyped =
2021-06-29 15:18:09 +00:00
mixin getCurrentFacet
2021-07-09 10:38:10 +00:00
getCurrentFacet().addEndpoint do (_: Facet) -> EndpointSpec:
result.assertion = a
2021-06-29 15:18:09 +00:00
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
2021-07-08 09:50:13 +00:00
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()
2021-06-29 15:18:09 +00:00
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 withFacet*(f: Facet; body: untyped): untyped =
## Execute a Syndicate ``body`` using the ``Facet`` at ``f``.
runnableExamples:
import preserves, preserves/records
type Foo = ref object
facet: Facet
i: int
proc incAndAssert(foo: Foo) =
inc(foo.i)
withFacet foo.facet:
react: assert: initRecord("Foo", %foo.i)
proc getCurrentFacet(): Facet {.inject, used.} = f
body
2021-07-08 09:50:13 +00:00
template syndicate*(ident, dataspaceBody: untyped): untyped =
proc `ident`*(facet: Facet) =
proc getCurrentFacet(): Facet {.inject, used.} = facet
2021-06-29 15:18:09 +00:00
dataspaceBody
2021-07-08 09:50:13 +00:00
when isMainModule:
asyncCheck bootModule("", `ident`)
type BootProc* = proc (facet: Facet) {.gcsafe.}
template boot*(module: BootProc) =
2021-07-08 09:50:13 +00:00
mixin getCurrentFacet
module(getCurrentFacet())