Add react and during to DSL

This commit is contained in:
Emery Hemingway 2021-07-08 11:50:13 +02:00
parent c85c809bef
commit 3dfafd925d
6 changed files with 141 additions and 102 deletions

View File

@ -10,6 +10,7 @@ export assertions.Observe
export dataspaces.Facet
export dataspaces.FieldId
export dataspaces.Fields
export dataspaces.`==`
export dataspaces.addEndpoint
export dataspaces.addStartScript
export dataspaces.addStopScript
@ -19,14 +20,13 @@ export dataspaces.hash
export dataspaces.recordDamage
export dataspaces.recordObservation
export dataspaces.scheduleScript
export dataspaces.stop
export events.EventKind
export skeletons.Analysis
export asyncdispatch.`callback=`
proc `==`*(x, y: FieldId): bool {.borrow.}
proc getCurrentFacet*(): Facet {.error.}
proc getCurrentFacet*(): Facet = raiseAssert("must be called from within the DSL")
## Return the current `Facet` for this context.
template stopIf*(cond, body: untyped): untyped =
@ -43,10 +43,9 @@ template sendMessage*(msg: untyped): untyped =
mixin getCurrentFacet
send(getCurrentFacet(), toPreserve(msg))
proc wrapHandler(handler: NimNode): NimNode =
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`.
# TODO: compile time analysis of pattern to count number of captures
handler.expectKind nnkDo
let
formalArgs = handler[3]
@ -55,7 +54,7 @@ proc wrapHandler(handler: NimNode): NimNode =
recSym = genSym(nskParam, "bindings")
var
letSection = newNimNode(nnkLetSection, handler)
captureCount: int
argCount: int
for i, arg in formalArgs:
if i > 0:
arg.expectKind nnkIdentDefs
@ -71,51 +70,50 @@ proc wrapHandler(handler: NimNode): NimNode =
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(recSym, newNimNode(nnkBracketExpr).add(ident"seq",
ident"Preserve")),
],
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, doHandler: NimNode): NimNode =
inc(argCount)
let
handler = wrapHandler(doHandler)
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)
handlerSym = handler[0]
result = quote do:
`handler`
mixin getCurrentFacet
onEvent(getCurrentFacet(), `pattern`, EventKind(`event`), `handlerSym`)
discard getCurrentFacet().addEndpoint do (facet: Facet) -> EndpointSpec:
proc getCurrentFacet(): Facet {.inject, used.} = facet
`handler`
let a = `pattern`
result.assertion = some(Observe % a)
result.analysis = some(analyzeAssertion(a))
result.analysis.get.callback = wrap(facet, EventKind(`event`), `handlerSym`)
macro onAsserted*(pattern: Preserve; handler: untyped) =
onEvent(addedEvent, pattern, handler)
@ -151,14 +149,41 @@ template field*(F: untyped; T: typedesc; initial: T): untyped =
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 syndicate*(name: string; dataspaceBody: untyped): untyped =
proc bootProc(rootFacet: Facet) =
proc getCurrentFacet(): Facet {.inject, used.} = rootFacet
template syndicate*(ident, dataspaceBody: untyped): untyped =
proc `ident`*(facet: Facet) =
proc getCurrentFacet(): Facet {.inject, used.} = facet
dataspaceBody
asyncCheck bootModule(name, bootProc)
when isMainModule:
asyncCheck bootModule("", `ident`)
template boot*(module: proc (facet: Facet) {.gcsafe.}) =
mixin getCurrentFacet
module(getCurrentFacet())

View File

@ -12,3 +12,10 @@ const
`?_`* = initRecord("discard")
`?*`* = Capture % `?_`
proc captureCount*(pattern: Preserve): int =
if Capture.isClassOf pattern:
result = 1
else:
for e in pattern.items:
result.inc captureCount(e)

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: ISC
import ./assertions, ./bags, ./dataflow, ./events, ./skeletons
import preserves, preserves/records
import ./bags, ./dataflow, ./events, ./skeletons
import preserves
import std/[asyncdispatch, deques, hashes, macros, options, sets, tables]
export dataflow.defineObservableProperty
@ -296,13 +296,12 @@ proc terminate(facet) =
let
actor = facet.actor
parent = facet.parent
if parent.isSome:
get(parent).children.del(facet.id)
else:
if parent.isNone:
reset actor.rootFacet
facet.isLive = false
for child in facet.children.values:
child.terminate()
reset facet.children
actor.scheduleTask do ():
facet.invokeScript do (facet: Facet):
for s in facet.stopScripts:
@ -323,7 +322,7 @@ template withNonScriptContext(facet; body: untyped) =
finally: facet.inScript = inScriptPrev
proc ensureFacetSetup(facet; s: string) =
assert(not facet.inScript, "Cannot " & s & "ouside facet setup")
assert(not facet.inScript, "Cannot " & s & " ouside facet setup")
proc ensureNonFacetSetup(facet; s: string) =
assert(facet.inScript, "Cannot " & s & " during facet setup")
@ -367,6 +366,9 @@ proc addFacet(actor; parentFacet: Option[Facet]; bootScript: Script[void]; check
if ((parentFacet.isSome) and (not parentFacet.get.isLive)) or f.isInert:
f.terminate()
proc addChildFacet*(facet; bootProc: Script[void]) =
facet.actor.addFacet(some facet, bootProc, true)
proc deliverMessage(ds: Dataspace; msg: Value; ac: Option[Actor]) =
ds.index.deliverMessage(msg)
@ -509,18 +511,15 @@ proc runTasks(ds: Dataspace): bool =
ds.performPendingActions()
result = ds.runnable.len > 0 or ds.pendingTurns.len > 0
proc stop*(facet; continuation: Script[void]) =
proc stop*(facet; continuation: Script[void] = nil) =
facet.parent.map do (parent: Facet):
facet.actor.scheduleTask do ():
facet.terminate()
parent.scheduleScript do (parent: Facet):
continuation(parent)
# ^ TODO: is this the correct scope to use??
proc stop*(facet) =
facet.parent.map do (parent: Facet):
facet.actor.scheduleTask do ():
facet.terminate()
parent.invokeScript do (_: Facet):
facet.actor.scheduleTask do ():
facet.terminate()
if not continuation.isNil:
parent.scheduleScript do (parent: Facet):
continuation(parent)
# ^ TODO: is this the correct scope to use??
proc addStopHandler*(g: Ground; h: StopHandler) =
g.stopHandlers.add(h)
@ -568,33 +567,18 @@ template declareField*(facet: Facet; F: untyped; T: typedesc; initial: T): untyp
facet.actor.dataspace.dataflow.recordObservation(f.id)
facet.fields[fieldOff]
template stopIf*(facet: Facet; cond: untyped; continuation: Script[void]): untyped =
## Stop the current facet if `cond` is true and
## invoke `body` after the facet has stopped.
discard facet.addDataflow do (facet: Facet):
if cond: facet.stop(continuation)
type EventHandler* = proc (facet: Facet; bindings: seq[Value]) {.gcsafe.}
proc wrap*(facet; onEvent: EventKind; cb: EventHandler): HandlerCallback =
proc wrap*(facet: Facet; onEvent: EventKind; cb: EventHandler): HandlerCallback =
proc wrapper(event: EventKind; bindings: seq[Value]) =
facet.invokeScript do (facet: Facet):
if event == onEvent:
facet.scheduleScript do (facet: Facet):
cb(facet, bindings)
wrapper
proc onEvent*(facet; pattern: Preserve; event: EventKind; cb: EventHandler) =
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
result.assertion = some(Observe % pattern)
result.analysis = some(analyzeAssertion(pattern))
result.analysis.get.callback = wrap(facet, event, cb)
proc onAsserted*(facet; pattern: Preserve; cb: EventHandler) =
onEvent(facet, pattern, addedEvent, cb)
proc onRetracted*(facet; pattern: Preserve; cb: EventHandler) =
onEvent(facet, pattern, removedEvent, cb)
proc onMessage*(facet; pattern: Preserve; cb: EventHandler) =
onEvent(facet, pattern, messageEvent, cb)
template stopIf*(facet: Facet; cond: untyped; continuation: Script[void]): untyped =
## Stop the current facet if `cond` is true and
## invoke `body` after the facet has stopped.
discard facet.addDataflow do (facet: Facet):
if cond: facet.stop(continuation)

View File

@ -205,7 +205,7 @@ type
root: Node
proc initIndex*(): Index =
result.root = Node(continuation: Continuation())
Index(root: Node(continuation: Continuation()))
using index: Index

View File

@ -7,6 +7,8 @@ import asyncdispatch, tables, options
const N = 100000
const
`?_` = init(Discard)
`?$` = init(Capture, `?_`)
BoxState = RecordClass(label: symbol"BoxState", arity: 1)
SetBox = RecordClass(label: symbol"SetBox", arity: 1)
@ -18,7 +20,7 @@ proc boot(facet: Facet) =
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
# echo "recomputing published BoxState ", facet.fields.value
let a = BoxState % value.get
let a = BoxState.init(value.getPreserve)
result.assertion = some a
discard facet.addDataflow do (facet: Facet):
@ -27,19 +29,40 @@ proc boot(facet: Facet) =
facet.stop do (facet: Facet):
echo "terminated box root facet"
facet.onMessage(SetBox % `?*`) do (facet: Facet; vs: seq[Value]):
value.set(vs[0])
echo "box updated value ", vs[0]
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
const a = SetBox.init(`?$`)
result.analysis = some analyzeAssertion(a)
proc cb(facet: Facet; vs: seq[Value]) =
facet.scheduleScript do (facet: Facet):
value.set(vs[0])
# echo "box updated value ", vs[0]
result.analysis.get.callback = facet.wrap(messageEvent, cb)
const o = Observe.init(SetBox.init(`?$`))
result.assertion = some o
facet.spawn("client") do (facet: Facet):
facet.onAsserted(BoxState % `?*`) do (facet: Facet; vs: seq[Value]):
let v = SetBox % vs[0].int.succ
# echo "client sending ", v
facet.send(v)
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
const a = BoxState.init(`?$`)
result.analysis = some analyzeAssertion(a)
proc cb(facet: Facet; vs: seq[Value]) =
facet.scheduleScript do (facet: Facet):
let v = SetBox.init(vs[0].int.succ.toPreserve)
# echo "client sending ", v
facet.send(v)
result.analysis.get.callback = facet.wrap(addedEvent, cb)
const o = Observe.init(BoxState.init(`?$`))
result.assertion = some o
facet.onRetracted(BoxState % `?_`) do (facet: Facet; vs: seq[Value]):
echo "box gone"
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
const a = BoxState.init(`?_`)
result.analysis = some analyzeAssertion(a)
proc cb(facet: Facet; vs: seq[Value]) =
facet.scheduleScript do (facet: Facet):
echo "box gone"
result.analysis.get.callback = facet.wrap(removedEvent, cb)
const o = Observe.init(BoxState.init(`?_`))
result.assertion = some o
facet.actor.dataspace.ground.addStopHandler do (_: Dataspace):
echo "stopping box-and-client"

View File

@ -8,7 +8,7 @@ const
BoxState = RecordClass(label: symbol"box-state", arity: 1)
SetBox = RecordClass(label: symbol"set-box", arity: 1)
syndicate "test_dsl":
syndicate testDsl:
spawn "box":
field(currentValue, int, 0)