syndicate-nim/src/syndicate.nim

229 lines
7.4 KiB
Nim

# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, macros, options]
import preserves, preserves/records
import syndicate/[assertions, dataspaces, events, skeletons]
export preserves.fromPreserve
export assertions.Observe
export dataspaces.Facet
export dataspaces.FieldId
export dataspaces.Fields
export dataspaces.`==`
export dataspaces.addEndpoint
export dataspaces.addStartScript
export dataspaces.addStopScript
export dataspaces.beginExternalTask
export dataspaces.defineObservableProperty
export dataspaces.endExternalTask
export dataspaces.generateId
export dataspaces.hash
export dataspaces.recordDamage
export dataspaces.recordObservation
export dataspaces.scheduleScript
export dataspaces.stop
export events.EventKind
export skeletons.Analysis
export asyncdispatch.`callback=`
export options.get
proc getCurrentFacet*(): Facet = raiseAssert("must be called from within the DSL")
## 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
getCurrentFacet().addDataflow do (facet: Facet):
if cond:
facet.stop do (facet: Facet):
proc getCurrentFacet(): Facet {.inject, used.} = facet
body
template send*(msg: Preserve): untyped =
mixin getCurrentFacet
send(getCurrentFacet(), msg)
proc wrapDoHandler(pattern, handler: NimNode): NimNode =
## 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]
cbFacetSym = genSym(nskParam, "facet")
scriptFacetSym = genSym(nskParam, "facet")
recSym = genSym(nskParam, "bindings")
varSection = newNimNode(nnkVarSection, handler)
conditional: NimNode
argCount: 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 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)
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
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`))
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)
handlerSym = handler[0]
result = quote do:
mixin getCurrentFacet
getCurrentFacet().addEndpoint do (facet: Facet) -> EndpointSpec:
proc getCurrentFacet(): Facet {.inject, used.} = facet
`handler`
let a = `pattern`
result.assertion = observe(a)
result.analysis = some(analyzeAssertion(a))
result.callback = wrap(facet, EventKind(`event`), `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 asserting*(a: Preserve): untyped =
mixin getCurrentFacet
getCurrentFacet().addEndpoint do (_: Facet) -> EndpointSpec:
result.assertion = 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 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()
template spawn*(name: string; spawnBody: untyped): untyped =
mixin getCurrentFacet
spawn(getCurrentFacet(), name) do (spawnFacet: Facet):
proc getCurrentFacet(): Facet {.inject, used.} = spawnFacet
spawnBody
template withFacet*(f: Facet; body: untyped): untyped =
## Execute a Syndicate ``body`` using the ``Facet`` at ``f``.
runnableExamples:
import preserves, preserves/records
type Foo {.record: "foo".} = ref object
facet: Facet
i: int
proc incAndAssert(foo: Foo) =
inc(foo.i)
withFacet foo.facet:
react: assert: foo
proc getCurrentFacet(): Facet {.inject, used.} = f
body
template syndicate*(ident, dataspaceBody: untyped): untyped =
proc `ident`*(facet: Facet) =
proc getCurrentFacet(): Facet {.inject, used.} = facet
dataspaceBody
when isMainModule:
asyncCheck bootModule("", `ident`)
type BootProc* = proc (facet: Facet) {.gcsafe.}
template boot*(module: BootProc) =
mixin getCurrentFacet
module(getCurrentFacet())
macro `?`*(x: untyped): untyped =
## Sugar for generating Syndicate patterns.
## `?_` is a pattern that matches but discards arbitrary
## values and `?` combined with any other identifier is
## a match and capture.
if eqIdent(x, "_"):
quote: toPreserve(Discard())
else:
quote: toPreserve(Capture())