syndicate-nim/src/syndicate.nim

208 lines
6.7 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 preserves.`%`
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=`
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
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-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 letDef = newNimNode(nnkIdentDefs, arg)
arg.copyChildrenTo letDef
letDef[2] = newCall("preserveTo",
newNimNode(nnkBracketExpr).add(recSym, newLit(pred i)),
letDef[1])
letSection.add(letDef)
2021-07-08 09:50:13 +00:00
inc(argCount)
2021-06-29 15:18:09 +00:00
let
2021-07-08 09:50:13 +00:00
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)
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`
2021-07-09 10:38:10 +00:00
result.assertion = Observe % 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 assert*(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())