syndicate-nim/src/sam/syndicate.nim

228 lines
7.8 KiB
Nim

# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense
## This module implements the `Syndicate DSL <https://syndicate-lang.org/doc/syndicate/>`_.
import std/[macros, tables, typetraits]
import pkg/cps
# import pkg/sys/ioqueue
import preserves
export preserves
import ./[actors, dataspaces, patterns]
# durings
import ./protocols/dataspace
export actors, dataspace, dataspaces, patterns
type Assertion* {.deprecated: "Assertion and Preserve[void] replaced by Value".} = Value
proc `!`*(typ: static typedesc): Pattern {.inline.} =
patterns.dropType(typ)
proc `?`*[T](val: T): Pattern {.inline.} =
patterns.grab[T](val)
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 `?:`*(typ: static typedesc; bindings: sink openArray[(Value, Pattern)]): Pattern {.inline.} =
patterns.grab(typ, bindings)
proc `??`*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern {.inline.} =
patterns.inject(pat, bindings)
type
PublishProc = proc (v: Value; h: Handle) {.closure.}
RetractProc = proc (h: Handle) {.closure.}
MessageProc = proc (v: Value) {.closure.}
ClosureEntity = ref object of Entity
publishCb*: PublishProc
retractCb*: RetractProc
messageCb*: MessageProc
proc publishCont(e: Entity; v: Value; h: Handle) {.cps: Cont.} =
var ce = ClosureEntity(e)
if not ce.publishCb.isNil: ce.publishCb(v, h)
proc retractCont(e: Entity; h: Handle) {.cps: Cont.} =
var ce = ClosureEntity(e)
if not ce.retractCb.isNil: ce.retractCb(h)
proc messageCont(e: Entity; v: Value) {.cps: Cont.} =
var ce = ClosureEntity(e)
if not ce.messageCb.isNil: ce.messageCb(v)
proc argumentCount(handler: NimNode): int =
handler.expectKind {nnkDo, nnkStmtList}
if handler.kind == nnkDo: result = pred handler[3].len
type HandlerNodes = tuple
valuesSym, varSection, body: NimNode
proc generateHandlerNodes(handler: NimNode): HandlerNodes =
handler.expectKind {nnkStmtList, nnkDo}
result.valuesSym = genSym(nskVar, "values")
let valuesTuple = newNimNode(nnkTupleTy, handler)
case handler.kind
of nnkStmtList:
result.body = handler
of nnkDo:
let
innerTuple = newNimNode(nnkVarTuple, handler)
varSectionInner = newNimNode(nnkVarSection, handler).add(innerTuple)
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(), result.valuesSym)
result.body = newStmtList(varSectionInner, handler[6])
else:
discard # caught earlier by expectKind
result.varSection = newNimNode(nnkVarSection, handler).
add(newIdentDefs(result.valuesSym, valuesTuple))
proc wrapPublishHandler(handler: NimNode): NimNode =
var
(valuesSym, varSection, publishBody) =
generateHandlerNodes(handler)
handleSym = ident"handle"
handlerSym = genSym(nskProc, "publish")
bindingsSym = ident"bindings"
quote do:
proc `handlerSym`(turn: Turn; `bindingsSym`: Value; `handleSym`: Handle) =
`varSection`
if fromPreserves(`valuesSym`, bindings):
`publishBody`
proc wrapMessageHandler(handler: NimNode): NimNode =
var
(valuesSym, varSection, body) =
generateHandlerNodes(handler)
handlerSym = genSym(nskProc, "message")
bindingsSym = ident"bindings"
quote do:
proc `handlerSym`(`bindingsSym`: Value) =
`varSection`
if fromPreserves(`valuesSym`, bindings):
`body`
proc wrapDuringHandler(entryBody, exitBody: NimNode): NimNode =
var
(valuesSym, varSection, publishBody) =
generateHandlerNodes(entryBody)
bindingsSym = ident"bindings"
handleSym = ident"duringHandle"
duringSym = genSym(nskProc, "during")
if exitBody.isNil:
quote do:
proc `duringSym`(turn: Turn; `bindingsSym`: Value; `handleSym`: Handle): TurnAction =
`varSection`
if fromPreserves(`valuesSym`, `bindingsSym`):
`publishBody`
else:
quote do:
proc `duringSym`(turn: Turn; `bindingsSym`: Value; `handleSym`: Handle): TurnAction =
`varSection`
if fromPreserves(`valuesSym`, `bindingsSym`):
`publishBody`
proc action(turn: Turn) =
`exitBody`
result = action
#[
macro onPublish*(ds: Cap; 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:
if `argCount` != 0 and `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments - " & $`pattern`)
`handlerProc`
discard observe(activeTurn(), `ds`, `pattern`, ClosureEntity(publishImpl: `handlerSym`))
]#
macro onMessage*(cap: Cap; pattern: Pattern; handler: untyped) =
## Call `handler` when an message matching `pattern` is broadcasted at `cap`.
let
argCount = argumentCount(handler)
handlerProc = wrapMessageHandler(handler)
handlerSym = handlerProc[0]
result = quote do:
if `argCount` != 0 and `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments - " & $`pattern`)
`handlerProc`
discard observe(`cap`, `pattern`, ClosureEntity(
messageImpl: whelp messageCont,
messageCb: `handlerSym`,
))
#[
macro during*(ds: Cap; 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:
## - `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:
if `argCount` != 0 and `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments - " & $`pattern`)
`callbackProc`
discard observe(activeTurn(), `ds`, `pattern`, during(`callbackSym`))
macro during*(ds: Cap; 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:
if `argCount` != 0 and `pattern`.analyse.capturePaths.len != `argCount`:
raiseAssert($`pattern`.analyse.capturePaths.len & " values captured but handler has " & $`argCount` & " arguments - " & $`pattern`)
`callbackProc`
discard observe(activeTurn(), `ds`, `pattern`, during(`callbackSym`))
]#
proc wrapHandler(body: NimNode; ident: string): NimNode =
var sym = genSym(nskProc, ident)
quote do:
proc `sym`() =
`body`
#[
macro onStop*(facet: Facet; body: untyped) =
let
handlerDef = wrapHandler(body, "onStop")
handlerSym = handlerDef[0]
result = quote do:
`handlerDef`
addOnStopHandler(facet, `handlerSym`)
macro onStop*(body: untyped) =
quote do:
block:
let facet = activeFacet()
facet.installStopHook()
if facet.stopped():
`body`
return
]#