Initial Syndicate DSL

This commit is contained in:
Emery Hemingway 2021-06-29 17:18:09 +02:00
parent f745e8b53f
commit 77e32a214e
5 changed files with 217 additions and 4 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
tests/test_box_and_client
tests/test_dsl

View File

@ -1,3 +1,9 @@
# Syndicate
Nim implementation of [Syndicate](https://syndicate-lang.org/) dataspaces and actors.
## TODO
* Complete Syndicate DSL
* Timer driver
* Remote dataspaces
* Async-dispatch integration

178
src/syndicate/macros.nim Normal file
View File

@ -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)

View File

@ -2,7 +2,6 @@
import syndicate/assertions, syndicate/dataspaces, syndicate/events, syndicate/skeletons
import preserves, preserves/records
import asyncdispatch, tables, options
const N = 100000
@ -21,12 +20,12 @@ proc boot(facet: Facet) =
discard facet.addEndpoint do (facet: Facet) -> EndpointSpec:
# 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
discard facet.addDataflow do (facet: Facet):
# echo "box dataflow saw new value ", facet.fields.value
if facet.fields.value == N:
if value.get == N:
facet.stop do (facet: Facet):
echo "terminated box root facet"
@ -36,7 +35,7 @@ proc boot(facet: Facet) =
proc cb(facet: Facet; evt: EventKind; vs: seq[Value]) =
if evt == messageEvent:
facet.scheduleScript do (facet: Facet):
facet.fields.value = vs[0]
value.set(vs[0])
# echo "box updated value ", vs[0]
result.analysis.get.callback = facet.wrap cb
const o = Observe.init(SetBox.init(`?$`))

29
tests/test_dsl.nim Normal file
View File

@ -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"