Add react and during to DSL
This commit is contained in:
parent
c85c809bef
commit
3dfafd925d
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -205,7 +205,7 @@ type
|
|||
root: Node
|
||||
|
||||
proc initIndex*(): Index =
|
||||
result.root = Node(continuation: Continuation())
|
||||
Index(root: Node(continuation: Continuation()))
|
||||
|
||||
using index: Index
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue