# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway # SPDX-License-Identifier: Unlicense import std/[asyncdispatch, macros, options] import preserves import syndicate/[actors] #[ import syndicate/[assertions, dataspaces, events, skeletons] export preserves.fromPreserve export assertions.Observe export dataspaces.Facet export dataspaces.FieldId export dataspaces.Fields export dataspaces.`==` export dataspaces.addEndpoint export dataspaces.addStartScript export dataspaces.addStopScript export dataspaces.beginExternalTask export dataspaces.defineObservableProperty export dataspaces.endExternalTask export dataspaces.generateId export dataspaces.hash export dataspaces.recordDamage export dataspaces.recordObservation export dataspaces.scheduleScript export dataspaces.stop export events.EventKind export skeletons.Analysis export asyncdispatch.`callback=` export options.get proc getCurrentFacet*(): Facet = raiseAssert("must be called from within the DSL") ## Return the current `Facet` for this 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 getCurrentFacet().addDataflow do (facet: Facet): if cond: facet.stop do (facet: Facet): proc getCurrentFacet(): Facet {.inject, used.} = facet body template send*(msg: Preserve): untyped = mixin getCurrentFacet send(getCurrentFacet(), msg) proc wrapDoHandler(pattern, handler: NimNode): NimNode = ## Generate a procedure that unpacks a `pattern` match to fit the ## parameters of `handler`, and calls the body of `handler`. handler.expectKind nnkDo var formalArgs = handler[3] cbFacetSym = genSym(nskParam, "facet") scriptFacetSym = genSym(nskParam, "facet") recSym = genSym(nskParam, "bindings") varSection = newNimNode(nnkVarSection, handler) conditional: NimNode argCount: 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 varDef = newNimNode(nnkIdentDefs, arg) arg.copyChildrenTo varDef varSection.add(varDef) var conversion = newCall("fromPreserve", varDef[0], newNimNode(nnkBracketExpr).add(recSym, newLit(pred i))) if conditional.isNil: conditional = conversion else: conditional = infix(conditional, "and", conversion) inc(argCount) var scriptBody = newStmtList() if argCount > 0: scriptBody.add( varSection, newNimNode(nnkIfStmt).add( newNimNode(nnkElifBranch).add( conditional, handler[6]))) else: scriptBody.add(handler[6]) var scriptSym = genSym(nskProc, "script") handlerSym = genSym(nskProc, "handler") litArgCount = newLit argCount quote do: proc `handlerSym`(`cbFacetSym`: Facet; `recSym`: seq[Preserve]) = assert(`litArgCount` == captureCount(`pattern`), "pattern does not match handler") # this should be a compile-time check assert( `litArgCount` == len(`recSym`), "cannot unpack " & $`litArgCount` & " bindings from " & $(toPreserve `recSym`)) proc `scriptSym`(`scriptFacetSym`: Facet) = proc getCurrentFacet(): Facet {.inject, used.} = `scriptFacetSym` `scriptBody` scheduleScript(`cbFacetSym`, `scriptSym`) proc wrapHandler(pattern, handler: NimNode): NimNode = case handler.kind of nnkDo: result = wrapDoHandler(pattern, handler) of nnkStmtList: let sym = genSym(nskProc, "handler") result = quote do: proc `sym`(facet: Facet; _: seq[Preserve]) = proc getCurrentFacet(): Facet {.inject, used.} = facet `handler` else: error("unhandled event handler", handler) proc onEvent(event: EventKind, pattern, handler: NimNode): NimNode = let handler = wrapHandler(pattern, handler) handlerSym = handler[0] result = quote do: mixin getCurrentFacet getCurrentFacet().addEndpoint do (facet: Facet) -> EndpointSpec: proc getCurrentFacet(): Facet {.inject, used.} = facet `handler` let a = `pattern` result.assertion = observe(a) result.analysis = some(analyzeAssertion(a)) result.callback = wrap(facet, EventKind(`event`), `handlerSym`) macro onAsserted*(pattern: Preserve; handler: untyped) = onEvent(addedEvent, pattern, handler) macro onRetracted*(pattern: Preserve; handler: untyped) = onEvent(removedEvent, pattern, handler) macro onMessage*(pattern: Preserve; doHandler: untyped) = onEvent(messageEvent, pattern, doHandler) template onStart*(body: untyped): untyped = mixin getCurrentFacet getCurrentFacet().addStartScript do (facet: Facet): proc getCurrentFacet(): Facet {.inject, used.} = facet body template onStop*(body: untyped): untyped = mixin getCurrentFacet getCurrentFacet().addStopScript do (facet: Facet): proc getCurrentFacet(): Facet {.inject, used.} = facet body template publish*(a: Preserve): untyped = mixin getCurrentFacet getCurrentFacet().addEndpoint do (_: Facet) -> EndpointSpec: result.assertion = 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 react*(body: untyped): untyped = mixin getCurrentFacet addChildFacet(getCurrentFacet()) do (facet: Facet): proc getCurrentFacet(): Facet {.inject, used.} = facet body template stop*(body: untyped): untyped = mixin getCurrentFacet stop(getCurrentFacet()) do (facet: Facet): proc getCurrentFacet(): Facet {.inject, used.} = facet body template stop*(): untyped = mixin getCurrentFacet stop(getCurrentFacet()) template during*(pattern: Preserve; handler: untyped) = onAsserted(pattern): react: onAsserted(pattern, handler) onRetracted(pattern): stop() template spawn*(name: string; spawnBody: untyped): untyped = mixin getCurrentFacet spawn(getCurrentFacet(), name) do (spawnFacet: Facet): proc getCurrentFacet(): Facet {.inject, used.} = spawnFacet spawnBody template withFacet*(f: Facet; body: untyped): untyped = ## Execute a Syndicate ``body`` using the ``Facet`` at ``f``. runnableExamples: import preserves, preserves/records type Foo {.record: "foo".} = ref object facet: Facet i: int proc incAndAssert(foo: Foo) = inc(foo.i) withFacet foo.facet: react: assert: foo proc getCurrentFacet(): Facet {.inject, used.} = f body template syndicate*(ident, dataspaceBody: untyped): untyped = proc `ident`*(facet: Facet) = proc getCurrentFacet(): Facet {.inject, used.} = facet dataspaceBody proc `ident`*(name = ""): Future[void] = bootModule(name, `ident`) type BootProc* = proc (facet: Facet) {.gcsafe.} template boot*(module: BootProc) = mixin getCurrentFacet module(getCurrentFacet()) macro `?`*(x: untyped): untyped = ## Sugar for generating Syndicate patterns. ## `?_` is a pattern that matches but discards arbitrary ## values and `?` combined with any other identifier is ## a match and capture. if eqIdent(x, "_"): quote: toPreserve(Discard()) else: quote: toPreserve(Capture()) ]# proc startActorSystem*(name: string; bootProc: TurnAction): Future[void] = # from python let actor = newActor(name, bootProc) result = actor.future