# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway # SPDX-License-Identifier: Unlicense ## This module implements the `Syndicate DSL `_. 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] from ./syndicate/relays import connectStdio, connectUnix export Assertion, Facet, Handle, Ref, Symbol, Turn, TurnAction, bootDataspace, `$`, connectStdio, connectUnix, drop, facet, grab, message, publish, retract, replace, run, stop, unembed proc `?`*(T: static typedesc): Pattern = ## Construct a `Pattern` from type `T`. runnableExamples: import preserves type Point = tuple[x: int; y: int] assert $(?Point) == "> >]>" type Rect {.preservesRecord: "rect".} = tuple[a: Point; B: Point] assert $(?Rect) == "> >]> > >]>]>" type ColoredRect {.preservesDictionary.} = tuple[color: string; rect: Rect] assert $(?ColoredRect) == ">, rect: > >]> > >]>]>}>" ## Derive a `Pattern` from type `T`. ## This works for `tuple` and `object` types but in the ## general case will return a wildcard binding. when T is ref: ?pointerBase(T) elif T is Preserve: grab() elif T.hasPreservesRecordPragma: var label = T.recordLabel.tosymbol(Ref) fields = newSeq[Pattern]() for key, val in fieldPairs(default T): fields.add ?(typeOf val) result = ?DCompound( orKind: DCompoundKind.rec, rec: DCompoundRec( label: label, fields: fields)) elif T.hasPreservesDictionaryPragma: var dict = DCompoundDict() for key, val in fieldPairs(default T): dict.entries[key.toSymbol(Ref)] = ?(typeOf val) ?DCompound( orKind: DCompoundKind.dict, dict: dict) elif T.hasPreservesTuplePragma or T is tuple: var arr = DCompoundArr() for key, val in fieldPairs(default T): arr.items.add ?(typeOf val) ?DCompound( orKind: DCompoundKind.arr, arr: arr) else: grab() # otherwise an abritrary capture proc `?`*(T: typedesc; bindings: sink openArray[(int, Pattern)]): Pattern = ## Construct a `Pattern` from type `T` that selectively captures fields. runnableExamples: import preserves type Point = tuple[x: int; y: int; z: int] assert $(Point ? {2: grab()}) == " <_> >]>" when T is ref: `?`(pointerBase(T), bindings) elif T.hasPreservesRecordPragma: var label = T.recordLabel.tosymbol(Ref) fields = newSeq[Pattern]() for (i, pat) in bindings: if i > fields.high: fields.setLen(succ i) fields[i] = pat for pat in bindings.mitems: if pat.isNil: pat = drop() result = ?DCompound( orKind: DCompoundKind.rec, rec: DCompoundRec( label: label, fields: fields)) elif T is tuple: var arr = DCompoundArr() for (i, pat) in bindings: if i > arr.items.high: arr.items.setLen(succ i) arr.items[i] = pat for pat in arr.items.mitems: if pat.isNil: pat = drop() result = ?DCompound( orKind: DCompoundKind.arr, arr: arr) else: {.error: "no preserves pragma on " & $T.} type PublishProc = proc (turn: var Turn; v: Assertion; h: Handle) {.closure.} RetractProc = proc (turn: var Turn; h: Handle) {.closure.} MessageProc = proc (turn: var Turn; v: Assertion) {.closure.} ClosureEntity = ref object of Entity publishImpl: PublishProc retractImpl: RetractProc messageImpl: MessageProc method publish(e: ClosureEntity; turn: var Turn; v: Assertion; h: Handle) = if not e.publishImpl.isNil: e.publishImpl(turn, v, 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; v: Assertion) = if not e.messageImpl.isNil: e.messageImpl(turn, v) proc wrapPublishHandler(handler: NimNode): NimNode = handler.expectKind nnkDo var innerProc = newNimNode(nnkProcDef) handler.copyChildrenTo innerProc innerProc[0] = genSym(nskProc, "message") var formalArgs = handler[3] valuesSym = genSym(nskVar, "values") valuesTuple = newNimNode(nnkTupleTy, handler) innerTuple = newNimNode(nnkVarTuple, handler) varSectionInner = newNimNode(nnkVarSection, handler).add(innerTuple) for i, arg in formalArgs: 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 = 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 var innerProc = newNimNode(nnkProcDef) handler.copyChildrenTo innerProc innerProc[0] = genSym(nskProc, "message") var formalArgs = handler[3] valuesSym = genSym(nskVar, "values") valuesTuple = newNimNode(nnkTupleTy, handler) innerTuple = newNimNode(nnkVarTuple, handler) varSectionInner = newNimNode(nnkVarSection, handler).add(innerTuple) for i, arg in formalArgs: 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 handlerProc = wrapPublishHandler(handler) handlerSym = handlerProc[0] result = quote do: `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 handlerProc = wrapMessageHandler(handler) handlerSym = handlerProc[0] result = quote do: `handlerProc` discard observe(`turn`, `ds`, `pattern`, ClosureEntity(messageImpl: `handlerSym`)) proc wrapDuringHandler(entryBody, exitBody: NimNode): NimNode = entryBody.expectKind nnkDo var innerProc = newNimNode(nnkProcDef) entryBody.copyChildrenTo innerProc innerProc[0] = genSym(nskProc, "during") var formalArgs = entryBody[3] valuesSym = ident("rawValues") valuesTuple = newNimNode(nnkTupleTy, entryBody) innerTuple = newNimNode(nnkVarTuple, entryBody) varSectionInner = newNimNode(nnkVarSection, entryBody).add(innerTuple) for i, arg in formalArgs: 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 = newStmtList(varSectionInner, entryBody[6]) turnSym = ident"turn" bindingsSym = ident"bindings" handleSym = ident"duringHandle" entrySym = genSym(nskProc, "during") 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(turn: var Turn) = `exitBody` result = action macro during*(turn: 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` ## - `bindings` - raw Preserves sequence that matched `pattern` ## - `duringHandle` - dataspace handle of the assertion that triggered `publishBody` let callbackProc = wrapDuringHandler(publishBody, retractBody) callbackSym = callbackProc[0] result = quote do: `callbackProc` discard observe(`turn`, `ds`, `pattern`, during(`callbackSym`)) macro during*(turn: Turn; ds: Ref; pattern: Pattern; publishBody: untyped) = ## Variant of `during` without a retract body. let callbackProc = wrapDuringHandler(publishBody, nil) callbackSym = callbackProc[0] result = quote do: `callbackProc` discard observe(`turn`, `ds`, `pattern`, during(`callbackSym`))