249 lines
9.5 KiB
Nim
249 lines
9.5 KiB
Nim
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
|
# SPDX-License-Identifier: Unlicense
|
|
|
|
## This module implements the `Syndicate DSL <https://syndicate-lang.org/doc/syndicate/>`_.
|
|
|
|
runnableExamples:
|
|
from syndicate/protocols/simpleChatProtocol import Present, Says
|
|
import std/asyncdispatch
|
|
|
|
bootDataspace("example") do (ds: Ref; turn: var Turn):
|
|
let
|
|
me = "user"
|
|
presenceHandle = publish(turn, ds, Present(username: me))
|
|
|
|
onMessage(turn, ds, ?Says) do (who: string; what: string):
|
|
echo who, ": ", what
|
|
retract(turn, presenceHandle)
|
|
|
|
during(turn, ds, ?Present) do (username: string):
|
|
echo "[", username, " arrived]"
|
|
message(turn, ds, Says(who: me, what: "users are losers"))
|
|
do:
|
|
echo "[", username, "departed]"
|
|
|
|
poll()
|
|
|
|
import std/[macros, tables, typetraits]
|
|
import preserves
|
|
import ./syndicate/[actors, dataspaces, durings, patterns]
|
|
import ./syndicate/protocols/dataspace
|
|
|
|
when defined(posix):
|
|
from ./syndicate/relays import connectStdio, connectUnix, SturdyRef
|
|
export connectStdio, connectUnix
|
|
else:
|
|
from ./syndicate/relays import SturdyRef
|
|
export SturdyRef
|
|
|
|
export Actor, Assertion, Facet, Handle, Ref, Symbol, Turn, TurnAction,
|
|
`$`, addCallback, analyse, asyncCheck, bootDataspace,
|
|
drop, facet, future, grab, grabLit, inFacet, message, newDataspace, publish,
|
|
retract, replace, run, stop, unembed
|
|
|
|
proc `!`*(typ: static typedesc): Pattern {.inline.} =
|
|
patterns.dropType(typ)
|
|
|
|
proc `?`*(typ: static typedesc): Pattern {.inline.} =
|
|
patterns.grabType(typ)
|
|
|
|
proc `?`*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern {.inline.} =
|
|
patterns.grab(typ, bindings)
|
|
|
|
proc `??`*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern {.inline.} =
|
|
patterns.inject(pat, bindings)
|
|
|
|
proc `?`*[T](val: T): Pattern {.inline.} =
|
|
patterns.grab[T](val)
|
|
|
|
type
|
|
Observe* = dataspace.Observe[Ref]
|
|
PublishProc = proc (turn: var Turn; v: Assertion; h: Handle) {.closure, gcsafe.}
|
|
RetractProc = proc (turn: var Turn; h: Handle) {.closure, gcsafe.}
|
|
MessageProc = proc (turn: var Turn; v: Assertion) {.closure, gcsafe.}
|
|
ClosureEntity = ref object of Entity
|
|
publishImpl: PublishProc
|
|
retractImpl: RetractProc
|
|
messageImpl: MessageProc
|
|
|
|
method publish(e: ClosureEntity; turn: var Turn; a: AssertionRef; h: Handle) {.gcsafe.} =
|
|
if not e.publishImpl.isNil: e.publishImpl(turn, a.value, h)
|
|
|
|
method retract(e: ClosureEntity; turn: var Turn; h: Handle) {.gcsafe.} =
|
|
if not e.retractImpl.isNil: e.retractImpl(turn, h)
|
|
|
|
method message(e: ClosureEntity; turn: var Turn; a: AssertionRef) {.gcsafe.} =
|
|
if not e.messageImpl.isNil: e.messageImpl(turn, a.value)
|
|
|
|
proc argumentCount(handler: NimNode): int =
|
|
handler.expectKind {nnkDo, nnkStmtList}
|
|
if handler.kind == nnkDo: result = pred handler[3].len
|
|
|
|
proc wrapPublishHandler(handler: NimNode): NimNode =
|
|
handler.expectKind {nnkDo, nnkStmtList}
|
|
var innerProc = newNimNode(nnkProcDef)
|
|
handler.copyChildrenTo innerProc
|
|
innerProc[0] = genSym(nskProc, "message")
|
|
var
|
|
valuesSym = genSym(nskVar, "values")
|
|
valuesTuple = newNimNode(nnkTupleTy, handler)
|
|
innerTuple = newNimNode(nnkVarTuple, handler)
|
|
varSectionInner = newNimNode(nnkVarSection, handler).add(innerTuple)
|
|
if handler.kind == nnkDo:
|
|
for i, arg in handler[3]:
|
|
if i > 0:
|
|
arg.expectKind nnkIdentDefs
|
|
if arg[1].kind == nnkEmpty:
|
|
error("type required for capture", arg)
|
|
var def = newNimNode(nnkIdentDefs, arg)
|
|
arg.copyChildrenTo def
|
|
valuesTuple.add(def)
|
|
innerTuple.add(arg[0])
|
|
innerTuple.add(newEmptyNode(), valuesSym)
|
|
var
|
|
varSectionOuter = newNimNode(nnkVarSection, handler).add(
|
|
newIdentDefs(valuesSym, valuesTuple))
|
|
publishBody =
|
|
if handler.kind == nnkStmtList: handler
|
|
else: newStmtList(varSectionInner, handler[6])
|
|
turnSym = ident"turn"
|
|
handleSym = ident"handle"
|
|
handlerSym = genSym(nskProc, "publish")
|
|
quote do:
|
|
proc `handlerSym`(`turnSym`: var Turn; bindings: Assertion; `handleSym`: Handle) =
|
|
`varSectionOuter`
|
|
if fromPreserve(`valuesSym`, bindings):
|
|
`publishBody`
|
|
|
|
proc wrapMessageHandler(handler: NimNode): NimNode =
|
|
handler.expectKind {nnkDo, nnkStmtList}
|
|
var innerProc = newNimNode(nnkProcDef)
|
|
handler.copyChildrenTo innerProc
|
|
innerProc[0] = genSym(nskProc, "message")
|
|
var
|
|
valuesSym = genSym(nskVar, "values")
|
|
valuesTuple = newNimNode(nnkTupleTy, handler)
|
|
innerTuple = newNimNode(nnkVarTuple, handler)
|
|
varSectionInner = newNimNode(nnkVarSection, handler).add(innerTuple)
|
|
if handler.kind == nnkDo:
|
|
for i, arg in handler[3]:
|
|
if i > 0:
|
|
arg.expectKind nnkIdentDefs
|
|
if arg[1].kind == nnkEmpty:
|
|
error("type required for capture", arg)
|
|
var def = newNimNode(nnkIdentDefs, arg)
|
|
arg.copyChildrenTo def
|
|
valuesTuple.add(def)
|
|
innerTuple.add(arg[0])
|
|
innerTuple.add(newEmptyNode(), valuesSym)
|
|
var
|
|
varSectionOuter = newNimNode(nnkVarSection, handler).add(
|
|
newIdentDefs(valuesSym, valuesTuple))
|
|
body = newStmtList(varSectionInner, handler[6])
|
|
turnSym = ident"turn"
|
|
handlerSym = genSym(nskProc, "message")
|
|
quote do:
|
|
proc `handlerSym`(`turnSym`: var Turn; bindings: Assertion) =
|
|
`varSectionOuter`
|
|
if fromPreserve(`valuesSym`, bindings):
|
|
`body`
|
|
|
|
macro onPublish*(turn: Turn; ds: Ref; pattern: Pattern; handler: untyped) =
|
|
## Call `handler` when an assertion matching `pattern` is published at `ds`.
|
|
let
|
|
argCount = argumentCount(handler)
|
|
handlerProc = wrapPublishHandler(handler)
|
|
handlerSym = handlerProc[0]
|
|
result = quote do:
|
|
doAssert `pattern`.analyse.capturePaths.len == `argCount`, "mismatch between pattern capture and handler arguments"
|
|
`handlerProc`
|
|
discard observe(`turn`, `ds`, `pattern`, ClosureEntity(publishImpl: `handlerSym`))
|
|
|
|
macro onMessage*(turn: Turn; ds: Ref; pattern: Pattern; handler: untyped) =
|
|
## Call `handler` when an message matching `pattern` is broadcasted at `ds`.
|
|
let
|
|
argCount = argumentCount(handler)
|
|
handlerProc = wrapMessageHandler(handler)
|
|
handlerSym = handlerProc[0]
|
|
result = quote do:
|
|
doAssert `pattern`.analyse.capturePaths.len == `argCount`, "mismatch between pattern capture and handler arguments"
|
|
`handlerProc`
|
|
discard observe(`turn`, `ds`, `pattern`, ClosureEntity(messageImpl: `handlerSym`))
|
|
|
|
proc wrapDuringHandler(entryBody, exitBody: NimNode): NimNode =
|
|
entryBody.expectKind {nnkDo, nnkStmtList}
|
|
var innerProc = newNimNode(nnkProcDef)
|
|
entryBody.copyChildrenTo innerProc
|
|
innerProc[0] = genSym(nskProc, "during")
|
|
var
|
|
valuesSym = ident("rawValues")
|
|
valuesTuple = newNimNode(nnkTupleTy, entryBody)
|
|
innerTuple = newNimNode(nnkVarTuple, entryBody)
|
|
varSectionInner = newNimNode(nnkVarSection, entryBody).add(innerTuple)
|
|
if entryBody.kind == nnkDo:
|
|
for i, arg in entryBody[3]:
|
|
if i > 0:
|
|
arg.expectKind nnkIdentDefs
|
|
if arg[1].kind == nnkEmpty:
|
|
error("type required for capture", arg)
|
|
var def = newNimNode(nnkIdentDefs, arg)
|
|
arg.copyChildrenTo def
|
|
valuesTuple.add(def)
|
|
innerTuple.add(arg[0])
|
|
innerTuple.add(newEmptyNode(), valuesSym)
|
|
var
|
|
varSectionOuter = newNimNode(nnkVarSection, entryBody).add(
|
|
newIdentDefs(valuesSym, valuesTuple))
|
|
publishBody =
|
|
if entryBody.kind == nnkStmtList: entryBody
|
|
else: newStmtList(varSectionInner, entryBody[6])
|
|
turnSym = ident"turn"
|
|
bindingsSym = ident"bindings"
|
|
handleSym = ident"duringHandle"
|
|
duringSym = genSym(nskProc, "during")
|
|
if exitBody.isNil:
|
|
quote do:
|
|
proc `duringSym`(`turnSym`: var Turn; `bindingsSym`: Assertion; `handleSym`: Handle): TurnAction =
|
|
`varSectionOuter`
|
|
if fromPreserve(`valuesSym`, `bindingsSym`):
|
|
`publishBody`
|
|
else:
|
|
quote do:
|
|
proc `duringSym`(`turnSym`: var Turn; `bindingsSym`: Assertion; `handleSym`: Handle): TurnAction =
|
|
`varSectionOuter`
|
|
if fromPreserve(`valuesSym`, `bindingsSym`):
|
|
`publishBody`
|
|
proc action(`turnSym`: var Turn) =
|
|
`exitBody`
|
|
result = action
|
|
|
|
macro during*(turn: var Turn; ds: Ref; pattern: Pattern; publishBody, retractBody: untyped) =
|
|
## Call `publishBody` when an assertion matching `pattern` is published to `ds` and
|
|
## call `retractBody` on retraction. Assertions that match `pattern` but are not
|
|
## convertable to the arguments of `publishBody` are silently discarded.
|
|
##
|
|
## The following symbols are injected into the scope of both bodies:
|
|
## - `turn` - active turn at entry of `publishBody` and `retractBody`
|
|
## - `bindings` - raw Preserves sequence that matched `pattern`
|
|
## - `duringHandle` - dataspace handle of the assertion that triggered `publishBody`
|
|
let
|
|
argCount = argumentCount(publishBody)
|
|
callbackProc = wrapDuringHandler(publishBody, retractBody)
|
|
callbackSym = callbackProc[0]
|
|
result = quote do:
|
|
doAssert `pattern`.analyse.capturePaths.len == `argCount`, "mismatch between pattern capture and handler arguments"
|
|
`callbackProc`
|
|
discard observe(`turn`, `ds`, `pattern`, during(`callbackSym`))
|
|
|
|
macro during*(turn: var Turn; ds: Ref; pattern: Pattern; publishBody: untyped) =
|
|
## Variant of `during` without a retract body.
|
|
let
|
|
argCount = argumentCount(publishBody)
|
|
callbackProc = wrapDuringHandler(publishBody, nil)
|
|
callbackSym = callbackProc[0]
|
|
result = quote do:
|
|
doAssert `pattern`.analyse.capturePaths.len == `argCount`,
|
|
("capture path has " & $`pattern`.analyse.capturePaths.len & " args for " & $`pattern`)
|
|
`callbackProc`
|
|
discard observe(`turn`, `ds`, `pattern`, during(`callbackSym`))
|