# SPDX-FileCopyrightText: ☭ Emery Hemingway # SPDX-License-Identifier: Unlicense ## This module implements the `Syndicate DSL `_. import std/[asyncdispatch, macros, tables, typetraits] import preserves export fromPreserves, toPreserves import ./syndicate/[actors, dataspaces, durings, patterns] import ./syndicate/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 (turn: var Turn; v: Value; h: Handle) {.closure.} RetractProc = proc (turn: var Turn; h: Handle) {.closure.} MessageProc = proc (turn: var Turn; v: Value) {.closure.} ClosureEntity = ref object of Entity publishImpl: PublishProc retractImpl: RetractProc messageImpl: MessageProc method publish(e: ClosureEntity; turn: var Turn; a: AssertionRef; h: Handle) = if not e.publishImpl.isNil: e.publishImpl(turn, a.value, h) method retract(e: ClosureEntity; turn: var Turn; h: Handle) = if not e.retractImpl.isNil: e.retractImpl(turn, h) method message(e: ClosureEntity; turn: var Turn; a: AssertionRef) = 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 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(turn, handler: NimNode): NimNode = var (valuesSym, varSection, publishBody) = generateHandlerNodes(handler) handleSym = ident"handle" handlerSym = genSym(nskProc, "publish") bindingsSym = ident"bindings" quote do: proc `handlerSym`(`turn`: var Turn; `bindingsSym`: Value; `handleSym`: Handle) = `varSection` if fromPreserves(`valuesSym`, bindings): `publishBody` proc wrapMessageHandler(turn, handler: NimNode): NimNode = var (valuesSym, varSection, body) = generateHandlerNodes(handler) handlerSym = genSym(nskProc, "message") bindingsSym = ident"bindings" quote do: proc `handlerSym`(`turn`: var Turn; `bindingsSym`: Value) = `varSection` if fromPreserves(`valuesSym`, bindings): `body` proc wrapDuringHandler(turn, 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`: var Turn; `bindingsSym`: Value; `handleSym`: Handle): TurnAction = `varSection` if fromPreserves(`valuesSym`, `bindingsSym`): `publishBody` else: quote do: proc `duringSym`(`turn`: var Turn; `bindingsSym`: Value; `handleSym`: Handle): TurnAction = `varSection` if fromPreserves(`valuesSym`, `bindingsSym`): `publishBody` proc action(`turn`: var Turn) = `exitBody` result = action macro onPublish*(turn: untyped; ds: Cap; pattern: Pattern; handler: untyped) = ## Call `handler` when an assertion matching `pattern` is published at `ds`. let argCount = argumentCount(handler) handlerProc = wrapPublishHandler(turn, 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(`turn`, `ds`, `pattern`, ClosureEntity(publishImpl: `handlerSym`)) macro onMessage*(turn: untyped; ds: Cap; pattern: Pattern; handler: untyped) = ## Call `handler` when an message matching `pattern` is broadcasted at `ds`. let argCount = argumentCount(handler) handlerProc = wrapMessageHandler(turn, 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(`turn`, `ds`, `pattern`, ClosureEntity(messageImpl: `handlerSym`)) macro during*(turn: untyped; 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(turn, 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(`turn`, `ds`, `pattern`, during(`callbackSym`)) macro during*(turn: untyped; ds: Cap; pattern: Pattern; publishBody: untyped) = ## Variant of `during` without a retract body. let `argCount` = argumentCount(publishBody) callbackProc = wrapDuringHandler(turn, 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(`turn`, `ds`, `pattern`, during(`callbackSym`)) type BootProc = proc (turn: var Turn; ds: Cap) {.closure.} type DeprecatedBootProc = proc (ds: Cap; turn: var Turn) {.closure.} proc runActor*(name: string; bootProc: BootProc) = ## Run an `Actor` to completion. let actor = bootDataspace(name, bootProc) while actor.running: waitFor sleepAsync(500) proc runActor*(name: string; bootProc: DeprecatedBootProc) {.deprecated.} = ## Run an `Actor` to completion. runActor(name) do (turn: var Turn, ds: Cap): bootProc(ds, turn)