Initial Syndicate DSL
This commit is contained in:
parent
f745e8b53f
commit
77e32a214e
|
@ -1 +1,2 @@
|
||||||
tests/test_box_and_client
|
tests/test_box_and_client
|
||||||
|
tests/test_dsl
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
# Syndicate
|
# Syndicate
|
||||||
|
|
||||||
Nim implementation of [Syndicate](https://syndicate-lang.org/) dataspaces and actors.
|
Nim implementation of [Syndicate](https://syndicate-lang.org/) dataspaces and actors.
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
* Complete Syndicate DSL
|
||||||
|
* Timer driver
|
||||||
|
* Remote dataspaces
|
||||||
|
* Async-dispatch integration
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
# SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
import preserves, preserves/records
|
||||||
|
|
||||||
|
import ./assertions, ./dataspaces, ./events, ./skeletons
|
||||||
|
import std/[asyncdispatch, macros, options]
|
||||||
|
|
||||||
|
export assertions.Capture
|
||||||
|
export assertions.Discard
|
||||||
|
export assertions.Observe
|
||||||
|
export dataspaces.Facet
|
||||||
|
export dataspaces.FieldId
|
||||||
|
export dataspaces.Fields
|
||||||
|
export dataspaces.addEndpoint
|
||||||
|
export dataspaces.defineObservableProperty
|
||||||
|
export dataspaces.generateId
|
||||||
|
export dataspaces.hash
|
||||||
|
export dataspaces.recordDamage
|
||||||
|
export dataspaces.recordObservation
|
||||||
|
export dataspaces.scheduleScript
|
||||||
|
export events.EventKind
|
||||||
|
export skeletons.Analysis
|
||||||
|
|
||||||
|
export asyncdispatch.`callback=`
|
||||||
|
|
||||||
|
proc `==`*(x, y: FieldId): bool {.borrow.}
|
||||||
|
|
||||||
|
proc newLit(p: pointer): NimNode = ident"nil"
|
||||||
|
## Hack to make `newLit` work on `Presevere`.
|
||||||
|
|
||||||
|
proc getCurrentFacet*(): Facet =
|
||||||
|
## Return the current `Facet` for this context.
|
||||||
|
raiseAssert "getCurrentFacet called outside of a Syndicate 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
|
||||||
|
discard getCurrentFacet().addDataflow do (facet: Facet):
|
||||||
|
if cond:
|
||||||
|
facet.stop do (facet: Facet):
|
||||||
|
body
|
||||||
|
|
||||||
|
template send*(class: RecordClass; fields: varargs[Preserve,
|
||||||
|
toPreserve]): untyped =
|
||||||
|
mixin getCurrentFacet
|
||||||
|
send(getCurrentFacet(), init(class, fields))
|
||||||
|
|
||||||
|
proc assertionForRecord(class: RecordClass; doHandler: NimNode): NimNode =
|
||||||
|
## Generate an assertion that captures or discards the items of record `class`
|
||||||
|
## according to the parameters of `doHandler`. `_` parameters are discarded.
|
||||||
|
let formalArgs = doHandler[3]
|
||||||
|
if formalArgs.len.pred != class.arity:
|
||||||
|
error($formalArgs.repr & " does not match record class " & $class, doHandler)
|
||||||
|
result = newCall("init", newLit(class))
|
||||||
|
for i, arg in formalArgs:
|
||||||
|
if i > 0:
|
||||||
|
arg.expectKind nnkIdentDefs
|
||||||
|
if arg[0] == ident"_":
|
||||||
|
result.add newCall("init", ident"Discard")
|
||||||
|
else:
|
||||||
|
result.add newCall("init", ident"Capture", newCall("init",
|
||||||
|
ident"Discard"))
|
||||||
|
|
||||||
|
proc callbackForEvent(event: EventKind; class: RecordClass; doHandler: NimNode;
|
||||||
|
assertion: NimNode): NimNode =
|
||||||
|
## Generate a procedure that checks an event kind, unpacks the fields of `class` to match the
|
||||||
|
## parameters of `doHandler`, and calls the body of `doHandler`.
|
||||||
|
# TODO: the assertion parameter is just for tracing.
|
||||||
|
let formalArgs = doHandler[3]
|
||||||
|
if formalArgs.len.pred != class.arity:
|
||||||
|
error($formalArgs.repr & " does not match record class " & $class, doHandler)
|
||||||
|
doHandler.expectKind nnkDo
|
||||||
|
let
|
||||||
|
cbFacetSym = genSym(nskParam, "facet")
|
||||||
|
scriptFacetSym = genSym(nskParam, "facet")
|
||||||
|
eventSym = genSym(nskParam, "event")
|
||||||
|
recSym = genSym(nskParam, "record")
|
||||||
|
var
|
||||||
|
letSection = newNimNode(nnkLetSection, doHandler)
|
||||||
|
captureCount: 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 letDef = newNimNode(nnkIdentDefs, arg)
|
||||||
|
arg.copyChildrenTo letDef
|
||||||
|
letDef[2] = newCall("preserveTo",
|
||||||
|
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))),
|
||||||
|
letSection,
|
||||||
|
doHandler[6]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
newProc(
|
||||||
|
# the event handler that is called when an assertion matches
|
||||||
|
name = genSym(nskProc, "event_handler"),
|
||||||
|
params = [
|
||||||
|
newEmptyNode(),
|
||||||
|
newIdentDefs(cbFacetSym, ident"Facet"),
|
||||||
|
newIdentDefs(eventSym, ident"EventKind"),
|
||||||
|
newIdentDefs(recSym, newNimNode(nnkBracketExpr).add(ident"seq",
|
||||||
|
ident"Preserve")),
|
||||||
|
],
|
||||||
|
body = newStmtList(
|
||||||
|
newIfStmt((cond: infix(eventSym, "==", newLit(event)), 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; class: RecordClass; doHandler: NimNode): NimNode =
|
||||||
|
let
|
||||||
|
assertion = assertionForRecord(class, doHandler)
|
||||||
|
handler = callbackForEvent(event, class, doHandler, assertion)
|
||||||
|
handlerSym = handler[0]
|
||||||
|
result = quote do:
|
||||||
|
`handler`
|
||||||
|
mixin getCurrentFacet
|
||||||
|
discard getCurrentFacet().addEndpoint do (facet: Facet) -> EndpointSpec:
|
||||||
|
let a = `assertion`
|
||||||
|
result.assertion = some(init(Observe, a))
|
||||||
|
result.analysis = some(analyzeAssertion(a))
|
||||||
|
result.analysis.get.callback = wrap(facet, `handlerSym`)
|
||||||
|
|
||||||
|
macro onAsserted*(class: static[RecordClass]; doHandler: untyped) =
|
||||||
|
onEvent(addedEvent, class, doHandler)
|
||||||
|
|
||||||
|
macro onRetracted*(class: static[RecordClass]; doHandler: untyped) =
|
||||||
|
onEvent(removedEvent, class, doHandler)
|
||||||
|
|
||||||
|
macro onMessage*(class: static[RecordClass]; doHandler: untyped) =
|
||||||
|
onEvent(messageEvent, class, doHandler)
|
||||||
|
|
||||||
|
template assert*(class: RecordClass; field: untyped): untyped =
|
||||||
|
mixin getCurrentFacet
|
||||||
|
let facet = getCurrentFacet()
|
||||||
|
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
|
||||||
|
let a = init(class, getPreserve(field))
|
||||||
|
result.assertion = some(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 spawn*(name: string; spawnBody: untyped): untyped =
|
||||||
|
mixin getCurrentFacet
|
||||||
|
spawn(getCurrentFacet(), name) do (spawnFacet: Facet):
|
||||||
|
proc getCurrentFacet(): Facet {.inject.} = spawnFacet
|
||||||
|
spawnBody
|
||||||
|
|
||||||
|
template syndicate*(name: string; dataspaceBody: untyped): untyped =
|
||||||
|
proc bootProc(rootFacet: Facet) =
|
||||||
|
proc getCurrentFacet(): Facet {.inject.} = rootFacet
|
||||||
|
dataspaceBody
|
||||||
|
waitFor bootModule(name, bootProc)
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import syndicate/assertions, syndicate/dataspaces, syndicate/events, syndicate/skeletons
|
import syndicate/assertions, syndicate/dataspaces, syndicate/events, syndicate/skeletons
|
||||||
import preserves, preserves/records
|
import preserves, preserves/records
|
||||||
|
|
||||||
import asyncdispatch, tables, options
|
import asyncdispatch, tables, options
|
||||||
|
|
||||||
const N = 100000
|
const N = 100000
|
||||||
|
@ -21,12 +20,12 @@ proc boot(facet: Facet) =
|
||||||
|
|
||||||
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
|
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
|
||||||
# echo "recomputing published BoxState ", facet.fields.value
|
# echo "recomputing published BoxState ", facet.fields.value
|
||||||
let a = BoxState.init(facet.fields.value.toPreserve)
|
let a = BoxState.init(value.getPreserve)
|
||||||
result.assertion = some a
|
result.assertion = some a
|
||||||
|
|
||||||
discard facet.addDataflow do (facet: Facet):
|
discard facet.addDataflow do (facet: Facet):
|
||||||
# echo "box dataflow saw new value ", facet.fields.value
|
# echo "box dataflow saw new value ", facet.fields.value
|
||||||
if facet.fields.value == N:
|
if value.get == N:
|
||||||
facet.stop do (facet: Facet):
|
facet.stop do (facet: Facet):
|
||||||
echo "terminated box root facet"
|
echo "terminated box root facet"
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@ proc boot(facet: Facet) =
|
||||||
proc cb(facet: Facet; evt: EventKind; vs: seq[Value]) =
|
proc cb(facet: Facet; evt: EventKind; vs: seq[Value]) =
|
||||||
if evt == messageEvent:
|
if evt == messageEvent:
|
||||||
facet.scheduleScript do (facet: Facet):
|
facet.scheduleScript do (facet: Facet):
|
||||||
facet.fields.value = vs[0]
|
value.set(vs[0])
|
||||||
# echo "box updated value ", vs[0]
|
# echo "box updated value ", vs[0]
|
||||||
result.analysis.get.callback = facet.wrap cb
|
result.analysis.get.callback = facet.wrap cb
|
||||||
const o = Observe.init(SetBox.init(`?$`))
|
const o = Observe.init(SetBox.init(`?$`))
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
# SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
import syndicate/[assertions, macros]
|
||||||
|
import preserves, preserves/records
|
||||||
|
import asyncdispatch
|
||||||
|
|
||||||
|
const
|
||||||
|
BoxState = RecordClass(label: symbol"box-state", arity: 1)
|
||||||
|
SetBox = RecordClass(label: symbol"set-box", arity: 1)
|
||||||
|
|
||||||
|
syndicate "test_dsl":
|
||||||
|
|
||||||
|
spawn "box":
|
||||||
|
field(currentValue, int, 0)
|
||||||
|
assert(BoxState, currentValue)
|
||||||
|
stopIf currentValue.get == 10:
|
||||||
|
echo "box: terminating"
|
||||||
|
onMessage(SetBox) do (newValue: int):
|
||||||
|
echo "box: taking on new value ", newValue
|
||||||
|
currentValue.set(newValue)
|
||||||
|
|
||||||
|
spawn "client":
|
||||||
|
#stopIf retracted(observe(SetBox, _)):
|
||||||
|
# echo "client: box has gone"
|
||||||
|
onAsserted(BoxState) do (v: int):
|
||||||
|
echo "client: learned that box's value is now ", v
|
||||||
|
send(SetBox, v+1)
|
||||||
|
onRetracted(BoxState) do (_):
|
||||||
|
echo "client: box state disappeared"
|
Loading…
Reference in New Issue