From b2506d81bb9c8b4f0fefa52600224c8c4fe7557b Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Sun, 18 Feb 2024 00:56:59 +0000 Subject: [PATCH] Hash brownie --- src/Tupfile | 2 - src/{syndicate => sam}/Tupfile | 0 src/sam/actors.bak | 699 +++++++++++ src/{syndicate => sam}/actors.nim | 57 +- src/{syndicate => sam}/actors/Tupfile | 0 src/{syndicate => sam}/actors/timers.nim | 2 +- src/{syndicate => sam}/bags.nim | 0 src/{syndicate => sam}/capabilities.nim | 0 src/{syndicate => sam}/dataflow.nim | 0 src/{syndicate => sam}/dataspaces.nim | 0 src/{syndicate => sam}/durings.nim | 0 src/sam/htmldocs/nimdoc.out.css | 1026 +++++++++++++++++ src/{syndicate => sam}/membranes.nim | 0 src/sam/observers.nim | 9 + src/{syndicate => sam}/patterns.nim | 0 src/{syndicate => sam}/protocols/Tupfile | 0 .../protocols/dataspace.nim | 0 .../protocols/dataspacePatterns.nim | 0 .../protocols/gatekeeper.nim | 0 src/{syndicate => sam}/protocols/http.nim | 0 src/{syndicate => sam}/protocols/noise.nim | 0 src/{syndicate => sam}/protocols/protocol.nim | 0 src/{syndicate => sam}/protocols/service.nim | 0 src/{syndicate => sam}/protocols/stdenv.nim | 0 src/{syndicate => sam}/protocols/stream.nim | 0 src/{syndicate => sam}/protocols/sturdy.nim | 0 src/{syndicate => sam}/protocols/tcp.nim | 0 src/{syndicate => sam}/protocols/timer.nim | 0 src/{syndicate => sam}/protocols/trace.nim | 0 .../protocols/transportAddress.nim | 0 src/{syndicate => sam}/protocols/worker.nim | 0 src/{syndicate => sam}/relays.nim | 2 +- src/sam/relays.nim.old | 443 +++++++ src/{syndicate => sam}/skeletons.nim | 0 src/{ => sam}/syndicate.nim | 7 +- src/sam/tracing.nim | 19 + tests/test_chat.nim | 19 +- tests/test_timers.nim | 23 +- 38 files changed, 2247 insertions(+), 61 deletions(-) delete mode 100644 src/Tupfile rename src/{syndicate => sam}/Tupfile (100%) create mode 100644 src/sam/actors.bak rename src/{syndicate => sam}/actors.nim (88%) rename src/{syndicate => sam}/actors/Tupfile (100%) rename src/{syndicate => sam}/actors/timers.nim (96%) rename src/{syndicate => sam}/bags.nim (100%) rename src/{syndicate => sam}/capabilities.nim (100%) rename src/{syndicate => sam}/dataflow.nim (100%) rename src/{syndicate => sam}/dataspaces.nim (100%) rename src/{syndicate => sam}/durings.nim (100%) create mode 100644 src/sam/htmldocs/nimdoc.out.css rename src/{syndicate => sam}/membranes.nim (100%) create mode 100644 src/sam/observers.nim rename src/{syndicate => sam}/patterns.nim (100%) rename src/{syndicate => sam}/protocols/Tupfile (100%) rename src/{syndicate => sam}/protocols/dataspace.nim (100%) rename src/{syndicate => sam}/protocols/dataspacePatterns.nim (100%) rename src/{syndicate => sam}/protocols/gatekeeper.nim (100%) rename src/{syndicate => sam}/protocols/http.nim (100%) rename src/{syndicate => sam}/protocols/noise.nim (100%) rename src/{syndicate => sam}/protocols/protocol.nim (100%) rename src/{syndicate => sam}/protocols/service.nim (100%) rename src/{syndicate => sam}/protocols/stdenv.nim (100%) rename src/{syndicate => sam}/protocols/stream.nim (100%) rename src/{syndicate => sam}/protocols/sturdy.nim (100%) rename src/{syndicate => sam}/protocols/tcp.nim (100%) rename src/{syndicate => sam}/protocols/timer.nim (100%) rename src/{syndicate => sam}/protocols/trace.nim (100%) rename src/{syndicate => sam}/protocols/transportAddress.nim (100%) rename src/{syndicate => sam}/protocols/worker.nim (100%) rename src/{syndicate => sam}/relays.nim (99%) create mode 100644 src/sam/relays.nim.old rename src/{syndicate => sam}/skeletons.nim (100%) rename src/{ => sam}/syndicate.nim (97%) create mode 100644 src/sam/tracing.nim diff --git a/src/Tupfile b/src/Tupfile deleted file mode 100644 index 625570a..0000000 --- a/src/Tupfile +++ /dev/null @@ -1,2 +0,0 @@ -include_rules -: foreach *.nim |> !nim_check |> diff --git a/src/syndicate/Tupfile b/src/sam/Tupfile similarity index 100% rename from src/syndicate/Tupfile rename to src/sam/Tupfile diff --git a/src/sam/actors.bak b/src/sam/actors.bak new file mode 100644 index 0000000..34a06a8 --- /dev/null +++ b/src/sam/actors.bak @@ -0,0 +1,699 @@ +# SPDX-FileCopyrightText: ☭ Emery Hemingway +# SPDX-License-Identifier: Unlicense + +import std/[deques, hashes, monotimes, options, sets, sequtils, tables, times] +import pkg/cps +import preserves +import ../syndicate/protocols/[protocol, sturdy] + +export cps + +const traceSyndicate {.booldefine.}: bool = true + +when traceSyndicate: + import std/streams + from std/os import getEnv + import ./protocols/trace + + type TraceSink = ref object + stream: FileStream + + proc newTraceSink: TraceSink = + new result + let path = getEnv("SYNDICATE_TRACE_FILE", "") + case path + of "": quit"$SYNDICATE_TRACE_FILE unset" + of "-": actor.stream = newFileStream(stderr) + else: result.stream = openFileStream(path, fmWrite) + + proc write(s: TraceSink; e: TraceEntry) = s.write(e.toPreserves) + +export Handle + +template generateIdType(typ: untyped) = + type typ* = distinct Natural + proc `==`*(x, y: typ): bool {.borrow.} + proc `$`*(id: typ): string {.borrow.} + +generateIdType(ActorId) +generateIdType(FacetId) +generateIdType(EndpointId) +generateIdType(FieldId) +generateIdType(TurnId) + +type + Oid = sturdy.Oid + Caveat = sturdy.Caveat + Attenuation = seq[Caveat] + Rewrite = sturdy.Rewrite + + AssertionRef* = ref object + value*: Value + # if the Enity methods take a Value object then the generated + # C code has "redefinition of struct" problems when orc is enabled + + Entity* = ref object of RootObj + oid*: Oid # oid is how Entities are identified over the wire + + Cap* {.preservesEmbedded.} = ref object of EmbeddedObj + relay*: Facet + target*: Entity + attenuation*: Attenuation + + Ref* {.deprecated: "Ref was renamed to Cap".} = Cap + + OutboundAssertion = ref object + handle: Handle + peer: Cap + established: bool + OutboundTable = Table[Handle, OutboundAssertion] + + Actor* = ref object + name: string + handleAllocator: ref Handle + # a fresh actor gets a new ref Handle and + # all actors spawned from it get the same ref. + root: Facet + exitReason: ref Exception + exitHooks: seq[TurnAction] + id: ActorId + exiting, exited: bool + when traceSyndicate: + turnIdAllocator: ref TurnId + traceStream: FileStream + + TurnAction* = proc (t: Turn) + + Queues = TableRef[Facet, Deque[Cont]] + + Turn* = ref object + facet: Facet + queues: Queues + when traceSyndicate: + desc: TurnDescription + + Cont* = ref object of Continuation + turn*: Turn + + Facet* = ref FacetObj + FacetObj = object + actor*: Actor + parent: Facet + children: HashSet[Facet] + outbound: OutboundTable + shutdownActions: seq[TurnAction] + inertCheckPreventers: int + id: FacetId + isAlive: bool + +proc pass*(a, b: Cont): Cont = + assert not a.turn.isNil + b.turn = move a.turn + return b + +template turnAction*(prc: typed): untyped = + cps(Cont, prc) + +proc activeTurn*(c: Cont): Turn {.cpsVoodoo.} = + assert not c.turn.isNil + c.turn + +when traceSyndicate: + + proc nextTurnId(facet: Facet): TurnId = + result = succ(facet.actor.turnIdAllocator[]) + facet.actor.turnIdAllocator[] = result + + proc trace(actor: Actor; act: ActorActivation) = + assert not actor.traceStream.isNil + var entry = TraceEntry( + timestamp: getTime().toUnixFloat(), + actor: initRecord("named", actor.name.toPreserves), + item: act) + actor.traceStream.writeLine($entry.toPreserves) + + proc path(facet: Facet): seq[trace.FacetId] = + var f = facet + while not f.isNil: + result.add f.id.toPreserves + f = f.parent + +method publish*(e: Entity; turn: Turn; v: AssertionRef; h: Handle) {.base.} = discard +method retract*(e: Entity; turn: Turn; h: Handle) {.base.} = discard +method message*(e: Entity; turn: Turn; v: AssertionRef) {.base.} = discard +method sync*(e: Entity; turn: Turn; peer: Cap) {.base.} = discard + +using + actor: Actor + facet: Facet + turn: Turn + action: TurnAction + +proc labels(f: Facet): string = + proc catLabels(f: Facet; labels: var string) = + labels.add ':' + if not f.parent.isNil: + catLabels(f.parent, labels) + labels.add ':' + when traceSyndicate: + labels.add $f.id + result.add f.actor.name + catLabels(f, result) + +proc `$`*(f: Facet): string = + "" + +proc `$`*(r: Cap): string = + "" + +proc `$`*(actor: Actor): string = + "" # TODO: ambigous + +proc attenuate(r: Cap; a: Attenuation): Cap = + if a.len == 0: result = r + else: result = Cap( + relay: r.relay, + target: r.target, + attenuation: a & r.attenuation) + +proc hash*(facet): Hash = + facet.id.hash + +proc hash*(r: Cap): Hash = !$(r.relay.hash !& r.target.unsafeAddr.hash) + +proc nextHandle(facet: Facet): Handle = + result = succ(facet.actor.handleAllocator[]) + facet.actor.handleAllocator[] = result + +proc facet*(turn: Turn): Facet = turn.facet + +proc enqueue(turn: Turn; target: Facet; cont: Cont) = + cont.turn = turn + if target in turn.queues: + turn.queues[target].addLast cont + else: + turn.queues[target] = toDeque([cont]) + +type Bindings = Table[Value, Value] + +proc match(bindings: var Bindings; p: Pattern; v: Value): bool = + case p.orKind + of PatternKind.Pdiscard: result = true + of PatternKind.Patom: + result = case p.patom + of PAtom.Boolean: v.isBoolean + of PAtom.Double: v.isDouble + of PAtom.Signedinteger: v.isInteger + of PAtom.String: v.isString + of PAtom.Bytestring: v.isByteString + of PAtom.Symbol: v.isSymbol + of PatternKind.Pembedded: + result = v.isEmbedded + of PatternKind.Pbind: + if match(bindings, p.pbind.pattern, v): + bindings[p.pbind.pattern.toPreserves] = v + result = true + of PatternKind.Pand: + for pp in p.pand.patterns: + result = match(bindings, pp, v) + if not result: break + of PatternKind.Pnot: + var b: Bindings + result = not match(b, p.pnot.pattern, v) + of PatternKind.Lit: + result = p.lit.value == v + of PatternKind.PCompound: + case p.pcompound.orKind + of PCompoundKind.rec: + if v.isRecord and + p.pcompound.rec.label == v.label and + p.pcompound.rec.fields.len == v.arity: + result = true + for i, pp in p.pcompound.rec.fields: + if not match(bindings, pp, v[i]): + result = false + break + of PCompoundKind.arr: + if v.isSequence and p.pcompound.arr.items.len == v.sequence.len: + result = true + for i, pp in p.pcompound.arr.items: + if not match(bindings, pp, v[i]): + result = false + break + of PCompoundKind.dict: + if v.isDictionary: + result = true + for key, pp in p.pcompound.dict.entries: + let vv = step(v, key) + if vv.isNone or not match(bindings, pp, get vv): + result = true + break + +proc match(p: Pattern; v: Value): Option[Bindings] = + var b: Bindings + if match(b, p, v): + result = some b + +proc instantiate(t: Template; bindings: Bindings): Value = + case t.orKind + of TemplateKind.Tattenuate: + let v = instantiate(t.tattenuate.template, bindings) + let cap = v.unembed(Cap) + if cap.isNone: + raise newException(ValueError, "Attempt to attenuate non-capability") + result = attenuate(get cap, t.tattenuate.attenuation).embed + of TemplateKind.TRef: + let n = $t.tref.binding.int + try: result = bindings[n.toPreserves] + except KeyError: + raise newException(ValueError, "unbound reference: " & n) + of TemplateKind.Lit: + result = t.lit.value + of TemplateKind.Tcompound: + case t.tcompound.orKind + of TCompoundKind.rec: + result = initRecord(t.tcompound.rec.label, t.tcompound.rec.fields.len) + for i, tt in t.tcompound.rec.fields: + result[i] = instantiate(tt, bindings) + of TCompoundKind.arr: + result = initSequence(t.tcompound.arr.items.len) + for i, tt in t.tcompound.arr.items: + result[i] = instantiate(tt, bindings) + of TCompoundKind.dict: + result = initDictionary() + for key, tt in t.tcompound.dict.entries: + result[key] = instantiate(tt, bindings) + +proc rewrite(r: Rewrite; v: Value): Value = + let bindings = match(r.pattern, v) + if bindings.isSome: + result = instantiate(r.template, get bindings) + +proc examineAlternatives(cav: Caveat; v: Value): Value = + case cav.orKind + of CaveatKind.Rewrite: + result = rewrite(cav.rewrite, v) + of CaveatKind.Alts: + for r in cav.alts.alternatives: + result = rewrite(r, v) + if not result.isFalse: break + of CaveatKind.Reject: discard + of CaveatKind.unknown: discard + +proc runRewrites*(a: Attenuation; v: Value): Value = + result = v + for stage in a: + result = examineAlternatives(stage, result) + if result.isFalse: break + +proc publish(target: Entity; e: OutboundAssertion; a: AssertionRef) {.turnAction.} = + e.established = true + publish(target, activeTurn(), a, e.handle) + +proc publish(turn: Turn; r: Cap; v: Value; h: Handle) = + var a = runRewrites(r.attenuation, v) + if not a.isFalse: + let e = OutboundAssertion( + handle: h, peer: r, established: false) + turn.facet.outbound[h] = e + enqueue(turn, r.relay, whelp publish(r.target, e, AssertionRef(value: a))) + when traceSyndicate: + var act = ActionDescription(orKind: ActionDescriptionKind.enqueue) + act.enqueue.event.target.actor = turn.facet.actor.id.toPreserves + act.enqueue.event.target.facet = turn.facet.id.toPreserves + act.enqueue.event.target.oid = r.target.oid.toPreserves + act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.assert) + act.enqueue.event.detail.assert.assertion.value.value = + mapEmbeds(v) do (cap: Value) -> Value: discard + act.enqueue.event.detail.assert.handle = h + turn.desc.actions.add act + +proc publish*(turn: Turn; r: Cap; a: Value): Handle = + result = turn.facet.nextHandle() + publish(turn, r, a, result) + +proc publish*(r: Cap; a: Value): Handle {.turnAction.} = + publish(activeTurn(), r, a, result) + +proc retract(e: OutboundAssertion) {.turnAction.} = + if e.established: + e.established = false + e.peer.target.retract(activeTurn(), e.handle) + +proc retract(turn: Turn; e: OutboundAssertion) = + enqueue(turn, e.peer.relay, whelp retract(e)) + +proc retract*(turn: Turn; h: Handle) = + var e: OutboundAssertion + if turn.facet.outbound.pop(h, e): + turn.retract(e) + +proc message(target: Entity; a: AssertionRef) {.turnAction.} = + target.message(activeTurn(), a) + +proc message*(turn: Turn; r: Cap; v: Value) = + var a = runRewrites(r.attenuation, v) + if not a.isFalse: + enqueue(turn, r.relay, whelp message(r.target, AssertionRef(value: a))) + +proc message*(target: Cap; value: Value) {.turnAction.} = + message(activeTurn(), target, value) + +proc sync(e: Entity; peer: Cap) {.turnAction.} = + e.sync(activeTurn(), peer) + +proc sync(turn: Turn; e: Entity; peer: Cap) = + e.sync(turn, peer) + +proc sync*(turn: Turn; r, peer: Cap) = + enqueue(turn, r.relay, whelp sync(r.target, peer)) + +proc replace*[T](turn: Turn; cap: Cap; h: Handle; v: T): Handle = + result = publish(turn, cap, v) + if h != default(Handle): + retract(turn, h) + +proc replace*[T](turn: Turn; cap: Cap; h: var Handle; v: T): Handle {.discardable.} = + var old = h + h = publish(turn, cap, v) + if old != default(Handle): + retract(turn, old) + h + +proc stop*(turn: Turn) + +proc run*(facet; action: TurnAction; zombieTurn = false) + +proc newFacet(actor; parent: Facet; initialAssertions: OutboundTable): Facet = + result = Facet( + id: getMonoTime().ticks.FacetId, + actor: actor, + parent: parent, + outbound: initialAssertions, + isAlive: true) + if not parent.isNil: parent.children.incl result + +proc newFacet(actor; parent: Facet): Facet = + var initialAssertions: OutboundTable + newFacet(actor, parent, initialAssertions) + +proc isInert(facet): bool = + result = facet.children.len == 0 and + (facet.outbound.len == 0 or facet.parent.isNil) and + facet.inertCheckPreventers == 0 + +proc preventInertCheck*(facet): (proc() {.gcsafe.}) {.discardable.} = + var armed = true + inc facet.inertCheckPreventers + proc disarm() = + if armed: + armed = false + dec facet.inertCheckPreventers + result = disarm + +proc inFacet(turn: Turn; facet; act: TurnAction) = + ## Call an action with a facet using a temporary `Turn` + ## that shares the `Queues` of the calling `Turn`. + var t = Turn(facet: facet, queues: turn.queues) + act(t) + +proc terminate(actor; turn; reason: ref Exception) + +proc terminate(facet; turn: Turn; orderly: bool) = + if facet.isAlive: + facet.isAlive = false + let parent = facet.parent + if not parent.isNil: + parent.children.excl facet + block: + var turn = Turn(facet: facet, queues: turn.queues) + while facet.children.len > 0: + facet.children.pop.terminate(turn, orderly) + if orderly: + for act in facet.shutdownActions: + act(turn) + for a in facet.outbound.values: turn.retract(a) + if orderly: + if not parent.isNil: + if parent.isInert: + parent.terminate(turn, true) + else: + terminate(facet.actor, turn, nil) + when traceSyndicate: + var act = ActionDescription(orKind: ActionDescriptionKind.facetStop) + act.facetstop.path = facet.path + turn.desc.actions.add act + +proc stopIfInert() {.turnAction.} = + let turn = activeTurn() + if (not turn.facet.parent.isNil and (not turn.facet.parent.isAlive)) or turn.facet.isInert: + stop(turn) + +proc stopIfInertAfter(action: TurnAction): TurnAction = + proc wrapper(turn: Turn) = + action(turn) + enqueue(turn, turn.facet, whelp stopIfInert()) + wrapper + +proc newFacet*(turn: Turn): Facet = newFacet(turn.facet.actor, turn.facet) + +proc inFacet*(turn: Turn; bootProc: TurnAction): Facet = + result = newFacet(turn) + when traceSyndicate: + var act = ActionDescription(orKind: ActionDescriptionKind.facetstart) + act.facetstart.path.add result.path + turn.desc.actions.add act + inFacet(turn, result, stopIfInertAfter(bootProc)) + +proc facet*(turn: Turn; bootProc: TurnAction): Facet {.deprecated.} = inFacet(turn, bootProc) + +proc run(actor; bootProc: TurnAction; initialAssertions: OutboundTable) = + run(newFacet(actor, actor.root, initialAssertions), stopIfInertAfter(bootProc)) + +proc run(actor; bootProc: TurnAction) = + var initialAssertions: OutboundTable + run(newFacet(actor, actor.root, initialAssertions), stopIfInertAfter(bootProc)) + +proc newActor(name: string; handleAlloc: ref Handle): Actor = + let + now = getTime() + seed = now.toUnix * 1_000_000_000 + now.nanosecond + result = Actor( + name: name, + id: ActorId(seed), + handleAllocator: handleAlloc, + ) + result.root = newFacet(result, nil) + when traceSyndicate: + new result.turnIdAllocator + +proc newActor*(name: string): Actor = + newActor(name, new(ref Handle)) + +proc bootActor*(name: string; bootProc: TurnAction) = + var + initialAssertions: OutboundTable + actor = newActor(name) + when traceSyndicate: + let path = getEnv("SYNDICATE_TRACE_FILE", "/tmp/" & name & ".trace.pr") + case path + of "": stderr.writeLine "$SYNDICATE_TRACE_FILE unset, not tracing actor ", name + of "-": actor.traceStream = newFileStream(stderr) + else: actor.traceStream = openFileStream(path, fmWrite) + when traceSyndicate: + var act = ActorActivation(orKind: ActorActivationKind.start) + act.start.actorName = Name(orKind: NameKind.named) + act.start.actorName.named.name = name.toPreserves + var entry = TraceEntry( + timestamp: getTime().toUnixFloat(), + item: act) + actor.traceStream.writeLine($entry.toPreserves) + let turn = newTurn(actor, TurnCauseExternal(description: "top-level actor")) + run(actor, bootProc, initialAssertions) + +proc bootActor*(name: string; cont: Cont) = + bootActor(name) do (turn: Turn): + enqueue(turn, turn.facet, cont) + +proc spawnActor(actor: Actor; bootProc: TurnAction; initialAssertions: HashSet[Handle]) {.turnAction.} = + let turn = activeTurn() + var newOutBound: Table[Handle, OutboundAssertion] + for key in initialAssertions: + discard turn.facet.outbound.pop(key, newOutbound[key]) + when traceSyndicate: + actor.turnIdAllocator = turn.facet.actor.turnIdAllocator + actor.traceStream = turn.facet.actor.traceStream + var act = ActionDescription(orKind: ActionDescriptionKind.spawn) + act.spawn.id = actor.id.toPreserves + turn.desc.actions.add act + run(actor, bootProc, newOutBound) + +proc spawn*(name: string; turn: Turn; bootProc: TurnAction; initialAssertions = initHashSet[Handle]()): Actor {.discardable.} = + let actor = newActor(name, turn.facet.actor.handleAllocator) + enqueue(turn, turn.facet, whelp spawnActor(actor, bootProc, initialAssertions)) + actor + +proc newInertCap*(): Cap = + let a = newActor("inert") + run(a) do (turn: Turn): turn.stop() + Cap(relay: a.root) + +proc atExit*(actor; action) = actor.exitHooks.add action + +proc terminate(actor: Actor; orderly: bool) {.turnAction.} = + actor.root.terminate(activeTurn(), orderly) + actor.exited = true + +proc terminate(actor; turn; reason: ref Exception) = + if not actor.exiting: + actor.exiting = true + actor.exitReason = reason + when traceSyndicate: + var act = ActorActivation(orKind: ActorActivationKind.stop) + if not reason.isNil: + act.stop.status = ExitStatus(orKind: ExitStatusKind.Error) + act.stop.status.error.message = reason.msg + trace(actor, act) + for hook in actor.exitHooks: hook(turn) + enqueue(turn, actor.root, whelp terminate(actor, reason.isNil)) + +proc terminate*(facet; e: ref Exception) = + run(facet.actor.root) do (turn: Turn): + facet.actor.terminate(turn, e) + +#[ +proc asyncCheck*(facet: Facet; fut: FutureBase) = + ## Sets a callback on `fut` which propagates exceptions to `facet`. + addCallback(fut) do (): + if fut.failed: terminate(facet, fut.error) + +proc asyncCheck*(turn; fut: FutureBase) = + ## Sets a callback on `fut` which propagates exceptions to the facet of `turn`. + asyncCheck(turn.facet, fut) +]# + +template tryFacet(facet; body: untyped) = + try: body + except CatchableError as err: terminate(facet, err) + +proc run(facet: Facet; turn: Turn; deq: var Deque[Cont]): int = + ## Return the number of continuations processed. + while deq.len > 0: + var c = deq.popFirst() + try: + while not c.isNil and not c.fn.isNil: + c.turn = turn + var y = c.fn + var x = y(c) + inc(result) + c = Cont(x) + except CatchableError as err: + if not c.dismissed: + writeStackFrames c + terminate(facet, err) + stderr.writeLine("ran ", result, " continuations for ", facet) + +proc run*(facet; action: TurnAction; zombieTurn = false) = + if zombieTurn or (facet.actor.exitReason.isNil and facet.isAlive): + tryFacet(facet): + var queues = newTable[Facet, Deque[Cont]]() + var turn = Turn(facet: facet, queues: queues) + action(turn) + when traceSyndicate: + turn.desc.id = facet.nextTurnId.toPreserves + facet.actor.trace ActorActivation( + orKind: ActorActivationKind.turn, turn: turn.desc) + assert not turn.isNil + var n = 1 + while n > 0: + n = 0 + var facets = queues.keys.toSeq + for facet in facets: + n.inc run(facet, turn, queues[facet]) + +proc run*(cap: Cap; action: TurnAction) = + ## Convenience proc to run a `TurnAction` in the scope of a `Cap`. + run(cap.relay, action) + +#[ +proc addCallback*(fut: FutureBase; facet: Facet; act: TurnAction) = + ## Add a callback to a `Future` that will be called at a later `Turn` + ## within the context of `facet`. + addCallback(fut) do (): + if fut.failed: terminate(facet, fut.error) + else: + when traceSyndicate: + run(facet) do (turn: Turn): + turn.desc.cause = TurnCause(orKind: TurnCauseKind.external) + turn.desc.cause.external.description = "Future".toPreserves + act(turn) + else: + run(facet, act) + +proc addCallback*(fut: FutureBase; turn: Turn; act: TurnAction) = + ## Add a callback to a `Future` that will be called at a later `Turn` + ## with the same context as the current. + if fut.failed: + terminate(turn.facet, fut.error) + elif fut.finished: + enqueue(turn, turn.facet, act) + else: + addCallback(fut, turn.facet, act) + +proc addCallback*[T](fut: Future[T]; turn: Turn; act: proc (t: Turn, x: T) {.gcsafe.}) = + addCallback(fut, turn) do (turn: Turn): + if fut.failed: terminate(turn.facet, fut.error) + else: + when traceSyndicate: + turn.desc.cause = TurnCause(orKind: TurnCauseKind.external) + turn.desc.cause.external.description = "Future".toPreserves + act(turn, read fut) +]# + +proc stop*(turn: Turn, facet: Facet) = + if facet.parent.isNil: + facet.terminate(turn, true) + else: + enqueue(turn, facet.parent, whelp terminate(facet.actor, true)) + # TODO: terminate the actor? + +proc stop*(turn: Turn) = + stop(turn, turn.facet) + +proc onStop*(facet: Facet; act: TurnAction) = + ## Add a `proc (turn: Turn)` action to `facet` to be called as it stops. + add(facet.shutdownActions, act) + +proc stopActor*(turn: Turn) = + let actor = turn.facet.actor + enqueue(turn, actor.root, whelp terminate(actor, true)) + +proc freshen*(turn: Turn, act: TurnAction) = + assert(turn.queues.len == 0, "Attempt to freshen a non-stale Turn") + run(turn.facet, act) + +proc newCap*(relay: Facet; e: Entity): Cap = + Cap(relay: relay, target: e) + +proc newCap*(turn; e: Entity): Cap = + Cap(relay: turn.facet, target: e) + +proc newCap*(e: Entity; turn): Cap = + Cap(relay: turn.facet, target: e) + +type SyncContinuation {.final.} = ref object of Entity + action: TurnAction + +method message(entity: SyncContinuation; turn: Turn; v: AssertionRef) = + entity.action(turn) + +proc sync*(turn: Turn; refer: Cap; act: TurnAction) = + sync(turn, refer, newCap(turn, SyncContinuation(action: act))) + +proc running*(actor): bool = + result = not actor.exited + if not (result or actor.exitReason.isNil): + raise actor.exitReason + +proc newCap*(e: Entity): Cap {.turnAction.} = + Cap(relay: activeTurn().facet, target: e) diff --git a/src/syndicate/actors.nim b/src/sam/actors.nim similarity index 88% rename from src/syndicate/actors.nim rename to src/sam/actors.nim index 2fbee13..26cf115 100644 --- a/src/syndicate/actors.nim +++ b/src/sam/actors.nim @@ -41,7 +41,7 @@ type when traceSyndicate: id: FacetId - Turn* = ref object + Turn = ref object ## https://synit.org/book/glossary.html#turn facet: Facet entity: Entity @@ -54,6 +54,10 @@ type ## https://synit.org/book/glossary.html#entity facet*: Facet oid*: sturdy.Oid # oid is how Entities are identified over the wire + publishImpl*: PublishProc + retractImpl*: RetractProc + messageImpl*: MessageProc + syncImpl*: SyncProc when traceSyndicate: id: FacetId @@ -62,8 +66,9 @@ type target*: Entity attenuation*: seq[sturdy.Caveat] - Actor* = ref object + Actor = ref object ## https://synit.org/book/glossary.html#actor + # TODO: run on a seperate thread. # crashHandlers: HandlerDeque root: Facet handleAllocator: Handle @@ -76,21 +81,11 @@ type template syndicate*(prc: typed): untyped = cps(Cont, prc) -proc activeTurn*(c: Cont): Turn {.cpsVoodoo.} = - ## Return the active `Turn` within a `{.syndicate.}` context. - assert not c.turn.isNil - c.turn - proc activeFacet*(c: Cont): Facet {.cpsVoodoo.} = ## Return the active `Facet` within a `{.syndicate.}` context. assert not c.facet.isNil c.facet -proc activeActor*(c: Cont): Actor {.cpsVoodoo.} = - ## Return the active `Actor` within a `{.syndicate.}` context. - assert not c.turn.isNil - c.facet.actor - using actor: Actor facet: Facet @@ -268,11 +263,6 @@ type # if the Enity methods take a Value object then the generated # C code has "redefinition of struct" problems when orc is enabled -method publish*(e: Entity; turn: Turn; v: AssertionRef; h: Handle) {.base.} = discard -method retract*(e: Entity; turn: Turn; h: Handle) {.base.} = discard -method message*(e: Entity; turn: Turn; v: AssertionRef) {.base.} = discard -method sync*(e: Entity; turn: Turn; peer: Cap) {.base.} = discard - proc newCap*(f: Facet; e: Entity): Cap = Cap(relay: f, target: e) @@ -280,8 +270,8 @@ proc nextHandle(facet: Facet): Handle = inc(facet.actor.handleAllocator) facet.actor.handleAllocator -proc publish*(turn: Turn; cap: Cap; val: Value): Handle = - result = turn.facet.nextHandle() +proc publish*(cap: Cap; val: Value): Handle = + result = cap.relay.nextHandle() when traceSyndicate: var act = ActionDescription(orKind: ActionDescriptionKind.enqueue) act.enqueue.event = TargetedTurnEvent( @@ -295,8 +285,9 @@ proc publish*(turn: Turn; cap: Cap; val: Value): Handle = ) act.enqueue.event.detail.assert.assertion.value.value = val turn.desc.actions.add act + cap.target.publish(val, result) -proc retract*(turn; h: Handle) = +proc retract*(cap: Cap; h: Handle) = when traceSyndicate: var act = ActionDescription(orKind: ActionDescriptionKind.enqueue) act.enqueue.event = TargetedTurnEvent( @@ -305,8 +296,9 @@ proc retract*(turn; h: Handle) = ) act.enqueue.event.detail.retract = TurnEventRetract(handle: h) turn.desc.actions.add act + cap.target.retract(h) -proc message*(turn; cap; val: Value) = +proc message*(cap; val: Value) = when traceSyndicate: var act = ActionDescription(orKind: ActionDescriptionKind.enqueue) act.enqueue.event = TargetedTurnEvent( @@ -315,8 +307,9 @@ proc message*(turn; cap; val: Value) = ) act.enqueue.event.detail.message.body.value.value = val turn.desc.actions.add act + cap.entity.message(val) -proc sync*(turn; peer: Cap) = +proc sync*(peer: Cap) = when traceSyndicate: var act = ActionDescription(orKind: ActionDescriptionKind.enqueue) act.enqueue.event = TargetedTurnEvent( @@ -325,18 +318,7 @@ proc sync*(turn; peer: Cap) = ) act.enqueue.event.detail.sync.peer = peer.traceTarget turn.desc.actions.add act - -proc publish*(cap: Cap; val: Value): Handle {.syndicate.} = - publish(activeTurn(), cap, val) - -proc retract*(h: Handle) {.syndicate.} = - activeTurn().retract(h) - -proc message*(cap: Cap; val: Value) {.syndicate.} = - activeTurn().message(cap, val) - -proc sync*(cap: Cap) {.syndicate.} = - activeTurn().sync(cap) + peer.entity.sync() proc installStopHook*(c: Cont, facet: Facet): Cont {.cpsMagic.} = facet.stopHandlers.add(c.fn) @@ -345,3 +327,10 @@ proc installStopHook*(c: Cont, facet: Facet): Cont {.cpsMagic.} = proc addOnStopHandler*(c: Cont; cb: Callback): Cont {.cpsMagic.} = c.facet.stopCallbacks.add(cb) result = c + +type FacetProc* = proc (f: Facet) {.nimcall.} + +proc bootActor*(name: string; bootProc: FacetProc): Actor = + result = newActor(name) + # TODO: start a turn, trace reason is Eternal, + bootProc(result.root) diff --git a/src/syndicate/actors/Tupfile b/src/sam/actors/Tupfile similarity index 100% rename from src/syndicate/actors/Tupfile rename to src/sam/actors/Tupfile diff --git a/src/syndicate/actors/timers.nim b/src/sam/actors/timers.nim similarity index 96% rename from src/syndicate/actors/timers.nim rename to src/sam/actors/timers.nim index ea0d00e..a1915b6 100644 --- a/src/syndicate/actors/timers.nim +++ b/src/sam/actors/timers.nim @@ -6,7 +6,7 @@ import preserves import syndicate import ../protocols/timer -from syndicate/protocols/dataspace import Observe +from ../syndicate/protocols/dataspace import Observe export timer diff --git a/src/syndicate/bags.nim b/src/sam/bags.nim similarity index 100% rename from src/syndicate/bags.nim rename to src/sam/bags.nim diff --git a/src/syndicate/capabilities.nim b/src/sam/capabilities.nim similarity index 100% rename from src/syndicate/capabilities.nim rename to src/sam/capabilities.nim diff --git a/src/syndicate/dataflow.nim b/src/sam/dataflow.nim similarity index 100% rename from src/syndicate/dataflow.nim rename to src/sam/dataflow.nim diff --git a/src/syndicate/dataspaces.nim b/src/sam/dataspaces.nim similarity index 100% rename from src/syndicate/dataspaces.nim rename to src/sam/dataspaces.nim diff --git a/src/syndicate/durings.nim b/src/sam/durings.nim similarity index 100% rename from src/syndicate/durings.nim rename to src/sam/durings.nim diff --git a/src/sam/htmldocs/nimdoc.out.css b/src/sam/htmldocs/nimdoc.out.css new file mode 100644 index 0000000..1417d9e --- /dev/null +++ b/src/sam/htmldocs/nimdoc.out.css @@ -0,0 +1,1026 @@ +/* +Stylesheet for use with Docutils/rst2html. + +See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to +customize this style sheet. + +Modified from Chad Skeeters' rst2html-style +https://bitbucket.org/cskeeters/rst2html-style/ + +Modified by Boyd Greenfield and narimiran +*/ + +:root { + --primary-background: #fff; + --secondary-background: ghostwhite; + --third-background: #e8e8e8; + --info-background: #50c050; + --warning-background: #c0a000; + --error-background: #e04040; + --border: #dde; + --text: #222; + --anchor: #07b; + --anchor-focus: #607c9f; + --input-focus: #1fa0eb; + --strong: #3c3c3c; + --hint: #9A9A9A; + --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAN4AAAA9CAYAAADCt9ebAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjAzOjQ4KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1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"); + + --keyword: #5e8f60; + --identifier: #222; + --comment: #484a86; + --operator: #155da4; + --punctuation: black; + --other: black; + --escapeSequence: #c4891b; + --number: #252dbe; + --literal: #a4255b; + --program: #6060c0; + --option: #508000; + --raw-data: #a4255b; + + --clipboard-image-normal: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: black' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E %3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' /%3E %3C/svg%3E"); + --clipboard-image-selected: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: black' viewBox='0 0 20 20' fill='currentColor'%3E %3Cpath d='M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z' /%3E %3Cpath d='M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z' /%3E %3C/svg%3E"); + --clipboard-image: var(--clipboard-image-normal) +} + +[data-theme="dark"] { + --primary-background: #171921; + --secondary-background: #1e202a; + --third-background: #2b2e3b; + --info-background: #008000; + --warning-background: #807000; + --error-background: #c03000; + --border: #0e1014; + --text: #fff; + --anchor: #8be9fd; + --anchor-focus: #8be9fd; + --input-focus: #8be9fd; + --strong: #bd93f9; + --hint: #7A7C85; + --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARMAAABMCAYAAABOBlMuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjE4OjIyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZGViMzU3MC1iNmZjLWQy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"); + + --keyword: #ff79c6; + --identifier: #f8f8f2; + --comment: #6272a4; + --operator: #ff79c6; + --punctuation: #f8f8f2; + --other: #f8f8f2; + --escapeSequence: #bd93f9; + --number: #bd93f9; + --literal: #f1fa8c; + --program: #9090c0; + --option: #90b010; + --raw-data: #8be9fd; + + --clipboard-image-normal: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: lightgray' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E %3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' /%3E %3C/svg%3E"); + --clipboard-image-selected: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: lightgray' viewBox='0 0 20 20' fill='currentColor'%3E %3Cpath d='M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z' /%3E %3Cpath d='M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z' /%3E %3C/svg%3E"); + --clipboard-image: var(--clipboard-image-normal); +} + +@media (prefers-color-scheme: dark) { + [data-theme="auto"] { + --primary-background: #171921; + --secondary-background: #1e202a; + --third-background: #2b2e3b; + --info-background: #008000; + --warning-background: #807000; + --error-background: #c03000; + --border: #0e1014; + --text: #fff; + --anchor: #8be9fd; + --anchor-focus: #8be9fd; + --input-focus: #8be9fd; + --strong: #bd93f9; + --hint: #7A7C85; + --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARMAAABMCAYAAABOBlMuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjE4OjIyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZGViMzU3MC1iNmZjLWQy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"); + + --keyword: #ff79c6; + --identifier: #f8f8f2; + --comment: #6272a4; + --operator: #ff79c6; + --punctuation: #f8f8f2; + --other: #f8f8f2; + --escapeSequence: #bd93f9; + --number: #bd93f9; + --literal: #f1fa8c; + --program: #9090c0; + --option: #90b010; + --raw-data: #8be9fd; + + --clipboard-image-normal: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: lightgray' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E %3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2' /%3E %3C/svg%3E"); + --clipboard-image-selected: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' style='color: lightgray' viewBox='0 0 20 20' fill='currentColor'%3E %3Cpath d='M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z' /%3E %3Cpath d='M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z' /%3E %3C/svg%3E"); + --clipboard-image: var(--clipboard-image-normal); + } +} + +.theme-select-wrapper { + display: flex; + align-items: center; +} + +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; } + +body { + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; + font-weight: 400; + font-size: 1.125em; + line-height: 1.5; + color: var(--text); + background-color: var(--primary-background); } + +/* Skeleton grid */ +.container { + position: relative; + width: 100%; + max-width: 1050px; + margin: 0 auto; + padding: 0; + box-sizing: border-box; } + +.column, .columns { + width: 100%; + float: left; + box-sizing: border-box; + margin-left: 1%; } + +@media print { + #global-links, .link-seesrc, .theme-switch-wrapper, #searchInputDiv, .search-groupby { + display:none; + } + .columns { + width:100% !important; + } +} + +.column:first-child, .columns:first-child { + margin-left: 0; } + +.container .row { + display: flex; } + +.three.columns { + width: 25.0%; + height: 100vh; + position: sticky; + top: 0px; + overflow-y: auto; + padding: 2px; +} + +.nine.columns { + width: 75.0%; + padding-left: 1.5em; } + +.twelve.columns { + width: 100%; + margin-left: 0; } + +@media screen and (max-width: 860px) { + .three.columns { + display: none; + } + .nine.columns { + width: 98.0%; + } + body { + font-size: 1em; + line-height: 1.35; + } +} + +cite { + font-style: italic !important; } + + +/* Nim search input */ +div#searchInputDiv { + margin-bottom: 1em; +} +input#searchInput { + width: 80%; +} + +/* + * Some custom formatting for input forms. + * This also fixes input form colors on Firefox with a dark system theme on Linux. + */ +input { + -moz-appearance: none; + background-color: var(--secondary-background); + color: var(--text); + border: 1px solid var(--border); + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; + font-size: 0.9em; + padding: 6px; +} + +input:focus { + border: 1px solid var(--input-focus); + box-shadow: 0 0 3px var(--input-focus); +} + +select { + -moz-appearance: none; + background-color: var(--secondary-background); + color: var(--text); + border: 1px solid var(--border); + font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; + font-size: 0.9em; + padding: 6px; +} + +select:focus { + border: 1px solid var(--input-focus); + box-shadow: 0 0 3px var(--input-focus); +} + +/* Docgen styles */ + +:target { + border: 2px solid #B5651D; + border-style: dotted; +} + +/* Links */ +a { + color: var(--anchor); + text-decoration: none; +} + +a span.Identifier { + text-decoration: underline; + text-decoration-color: #aab; +} + +a.reference-toplevel { + font-weight: bold; +} + +a.nimdoc { + word-spacing: 0.3em; +} + +a.toc-backref { + text-decoration: none; + color: var(--text); +} + +a.link-seesrc { + color: #607c9f; + font-size: 0.9em; + font-style: italic; +} + +a:hover, a:focus { + color: var(--anchor-focus); + text-decoration: underline; +} + +a:hover span.Identifier { + color: var(--anchor); +} + + +sub, sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; } + +sup { + top: -0.5em; } + +sub { + bottom: -0.25em; } + +img { + width: auto; + height: auto; + max-width: 100%; + vertical-align: middle; + border: 0; + -ms-interpolation-mode: bicubic; } + +@media print { + * { + color: black !important; + text-shadow: none !important; + background: transparent !important; + box-shadow: none !important; } + + a, a:visited { + text-decoration: underline; } + + a[href]:after { + content: " (" attr(href) ")"; } + + abbr[title]:after { + content: " (" attr(title) ")"; } + + .ir a:after, + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; } + + pre, blockquote { + border: 1px solid #999; + page-break-inside: avoid; } + + thead { + display: table-header-group; } + + tr, img { + page-break-inside: avoid; } + + img { + max-width: 100% !important; } + + @page { + margin: 0.5cm; } + + h1 { + page-break-before: always; } + + h1.title { + page-break-before: avoid; } + + p, h2, h3 { + orphans: 3; + widows: 3; } + + h2, h3 { + page-break-after: avoid; } +} + + +p { + margin-top: 0.5em; + margin-bottom: 0.5em; } + +small { + font-size: 85%; } + +strong { + font-weight: 600; + font-size: 0.95em; + color: var(--strong); } + +em { + font-style: italic; } + +h1 { + font-size: 1.8em; + font-weight: 400; + padding-bottom: .25em; + border-bottom: 6px solid var(--third-background); + margin-top: 2.5em; + margin-bottom: 1em; + line-height: 1.2em; } + +h1.title { + padding-bottom: 1em; + border-bottom: 0px; + font-size: 2.5em; + text-align: center; + font-weight: 900; + margin-top: 0.75em; + margin-bottom: 0em; } + +h2 { + font-size: 1.3em; + margin-top: 2em; } + +h2.subtitle { + margin-top: 0em; + text-align: center; } + +h3 { + font-size: 1.125em; + font-style: italic; + margin-top: 1.5em; } + +h4 { + font-size: 1.125em; + margin-top: 1em; } + +h5 { + font-size: 1.125em; + margin-top: 0.75em; } + +h6 { + font-size: 1.1em; } + + +ul, ol { + padding: 0; + margin-top: 0.5em; + margin-left: 0.75em; } + +ul ul, ul ol, ol ol, ol ul { + margin-bottom: 0; + margin-left: 1.25em; } + +ul.simple > li { + list-style-type: circle; } + +ul.simple-boot li { + list-style-type: none; + margin-left: 0em; + margin-bottom: 0.5em; } + +ol.simple > li, ul.simple > li { + margin-bottom: 0.2em; + margin-left: 0.4em } + +ul.simple.simple-toc > li { + margin-top: 1em; } + +ul.simple-toc { + list-style: none; + font-size: 0.9em; + margin-left: -0.3em; + margin-top: 1em; } + +ul.simple-toc > li { + list-style-type: none; } + +ul.simple-toc-section { + list-style-type: circle; + margin-left: 0.8em; + color: #6c9aae; } + +ul.nested-toc-section { + list-style-type: circle; + margin-left: -0.75em; + color: var(--text); } + +ul.nested-toc-section > li { + margin-left: 1.25em; } + + +ol.arabic { + list-style: decimal; } + +ol.loweralpha { + list-style: lower-alpha; } + +ol.upperalpha { + list-style: upper-alpha; } + +ol.lowerroman { + list-style: lower-roman; } + +ol.upperroman { + list-style: upper-roman; } + +ul.auto-toc { + list-style-type: none; } + + +dl { + margin-bottom: 1.5em; } + +dt { + margin-bottom: -0.5em; + margin-left: 0.0em; } + +dd { + margin-left: 2.0em; + margin-bottom: 3.0em; + margin-top: 0.5em; } + + +hr { + margin: 2em 0; + border: 0; + border-top: 1px solid #aaa; } + +hr.footnote { + width: 25%; + border-top: 0.15em solid #999; + margin-bottom: 0.15em; + margin-top: 0.15em; +} +div.footnote-group { + margin-left: 1em; +} +div.footnote-label { + display: inline-block; + min-width: 1.7em; +} + +div.option-list { + border: 0.1em solid var(--border); +} +div.option-list-item { + padding-left: 12em; + padding-right: 0; + padding-bottom: 0.3em; + padding-top: 0.3em; +} +div.odd { + background-color: var(--secondary-background); +} +div.option-list-label { + margin-left: -11.5em; + margin-right: 0em; + min-width: 11.5em; + display: inline-block; + vertical-align: top; +} +div.option-list-description { + width: calc(100% - 1em); + padding-left: 1em; + padding-right: 0; + display: inline-block; +} + +blockquote { + font-size: 0.9em; + font-style: italic; + padding-left: 0.5em; + margin-left: 0; + border-left: 5px solid #bbc; +} + +blockquote.markdown-quote { + font-size: 0.9rem; /* use rem to avoid recursion */ + font-style: normal; +} + +.pre, span.tok { + font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; + font-weight: 500; + font-size: 0.85em; + color: var(--text); + background-color: var(--third-background); + padding-left: 3px; + padding-right: 3px; + border-radius: 4px; +} + +span.tok { + border: 1px solid #808080; + padding-bottom: 0.1em; + margin-right: 0.2em; +} + +.copyToClipBoard { + position: relative; +} + +pre { + font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; + color: var(--text); + font-weight: 500; + display: inline-block; + box-sizing: border-box; + min-width: 100%; + padding: 0.5em; + margin-top: 0.5em; + margin-bottom: 0.5em; + font-size: 0.85em; + white-space: pre !important; + overflow-y: hidden; + overflow-x: visible; + background-color: var(--secondary-background); + border: 1px solid var(--border); + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; +} + +.copyToClipBoardBtn { + visibility: hidden; + position: absolute; + width: 24px; + border-radius: 4px; + background-image: var(--clipboard-image); + right: 5px; + top: 13px; + background-color: var(--secondary-background); + padding: 11px; + border: 0; +} + +.copyToClipBoard:hover .copyToClipBoardBtn { + visibility: visible; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; } + + +/* Nim line-numbered tables */ +.line-nums-table { + width: 100%; + table-layout: fixed; } + +table.line-nums-table { + border-radius: 4px; + border: 1px solid #cccccc; + background-color: ghostwhite; + border-collapse: separate; + margin-top: 15px; + margin-bottom: 25px; } + +.line-nums-table tbody { + border: none; } + +.line-nums-table td pre { + border: none; + background-color: transparent; } + +.line-nums-table td.blob-line-nums { + width: 28px; } + +.line-nums-table td.blob-line-nums pre { + color: #b0b0b0; + -webkit-filter: opacity(75%); + filter: opacity(75%); + text-align: right; + border-color: transparent; + background-color: transparent; + padding-left: 0px; + margin-left: 0px; + padding-right: 0px; + margin-right: 0px; } + + +table { + max-width: 100%; + background-color: transparent; + margin-top: 0.5em; + margin-bottom: 1.5em; + border-collapse: collapse; + border-color: var(--third-background); + border-spacing: 0; + font-size: 0.9em; +} + +table th, table td { + padding: 0px 0.5em 0px; + border-color: var(--third-background); +} + +table th { + background-color: var(--third-background); + border-color: var(--third-background); + font-weight: bold; } + +table th.docinfo-name { + background-color: transparent; + text-align: right; +} + +table tr:hover { + background-color: var(--third-background); } + + +/* rst2html default used to remove borders from tables and images */ +.borderless, table.borderless td, table.borderless th { + border: 0; } + +table.borderless td, table.borderless th { + /* Override padding for "table.docutils td" with "! important". + The right padding separates the table cells. */ + padding: 0 0.5em 0 0 !important; } + +.admonition { + padding: 0.3em; + background-color: var(--secondary-background); + border-left: 0.4em solid #7f7f84; + margin-bottom: 0.5em; + -webkit-box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); + -moz-box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); + box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); +} +.admonition-info { + border-color: var(--info-background); +} +.admonition-info-text { + color: var(--info-background); +} +.admonition-warning { + border-color: var(--warning-background); +} +.admonition-warning-text { + color: var(--warning-background); +} +.admonition-error { + border-color: var(--error-background); +} +.admonition-error-text { + color: var(--error-background); +} + +.first { + /* Override more specific margin styles with "! important". */ + margin-top: 0 !important; } + +.last, .with-subtitle { + margin-bottom: 0 !important; } + +.hidden { + display: none; } + +blockquote.epigraph { + margin: 2em 5em; } + +dl.docutils dd { + margin-bottom: 0.5em; } + +object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { + overflow: hidden; } + + +div.figure { + margin-left: 2em; + margin-right: 2em; } + +div.footer, div.header { + clear: both; + text-align: center; + color: #666; + font-size: smaller; } + +div.footer { + padding-top: 5em; } + +div.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; } + +div.line-block div.line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; } + +div.topic { + margin: 2em; } + +div.search_results { + background-color: var(--third-background); + margin: 3em; + padding: 1em; + border: 1px solid #4d4d4d; } + +div#global-links ul { + margin-left: 0; + list-style-type: none; } + +div#global-links > simple-boot { + margin-left: 3em; } + +hr.docutils { + width: 75%; } + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; } + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; } + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; } + +.align-left { + text-align: left; } + +.align-center { + clear: both; + text-align: center; } + +.align-right { + text-align: right; } + +/* reset inner alignment in figures */ +div.align-right { + text-align: inherit; } + +p.attribution { + text-align: right; + margin-left: 50%; } + +p.caption { + font-style: italic; } + +p.credits { + font-style: italic; + font-size: smaller; } + +p.label { + white-space: nowrap; } + +p.rubric { + font-weight: bold; + font-size: larger; + color: maroon; + text-align: center; } + +p.topic-title { + font-weight: bold; } + +pre.address { + margin-bottom: 0; + margin-top: 0; + font: inherit; } + +pre.literal-block, pre.doctest-block, pre.math, pre.code { + margin-left: 2em; + margin-right: 2em; } + +pre.code .ln { + color: grey; } + +/* line numbers */ +pre.code, code { + background-color: #eeeeee; } + +pre.code .comment, code .comment { + color: #5c6576; } + +pre.code .keyword, code .keyword { + color: #3B0D06; + font-weight: bold; } + +pre.code .literal.string, code .literal.string { + color: #0c5404; } + +pre.code .name.builtin, code .name.builtin { + color: #352b84; } + +pre.code .deleted, code .deleted { + background-color: #DEB0A1; } + +pre.code .inserted, code .inserted { + background-color: #A3D289; } + +span.classifier { + font-style: oblique; } + +span.classifier-delimiter { + font-weight: bold; } + +span.problematic { + color: #b30000; } + +span.section-subtitle { + /* font-size relative to parent (h1..h6 element) */ + font-size: 80%; } + +span.DecNumber { + color: var(--number); } + +span.BinNumber { + color: var(--number); } + +span.HexNumber { + color: var(--number); } + +span.OctNumber { + color: var(--number); } + +span.FloatNumber { + color: var(--number); } + +span.Identifier { + color: var(--identifier); } + +span.Keyword { + font-weight: 600; + color: var(--keyword); } + +span.StringLit { + color: var(--literal); } + +span.LongStringLit { + color: var(--literal); } + +span.CharLit { + color: var(--literal); } + +span.EscapeSequence { + color: var(--escapeSequence); } + +span.Operator { + color: var(--operator); } + +span.Punctuation { + color: var(--punctuation); } + +span.Comment, span.LongComment { + font-style: italic; + font-weight: 400; + color: var(--comment); } + +span.RegularExpression { + color: darkviolet; } + +span.TagStart { + color: darkviolet; } + +span.TagEnd { + color: darkviolet; } + +span.Key { + color: #252dbe; } + +span.Value { + color: #252dbe; } + +span.RawData { + color: var(--raw-data); } + +span.Assembler { + color: #252dbe; } + +span.Preprocessor { + color: #252dbe; } + +span.Directive { + color: #252dbe; } + +span.option { + font-weight: bold; + font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; + color: var(--option); } + +span.Prompt { + font-weight: bold; + color: red; } + +span.ProgramOutput { + font-weight: bold; + color: #808080; } + +span.program { + font-weight: bold; + color: var(--program); + text-decoration: underline; + text-decoration-color: var(--hint); + text-decoration-thickness: 0.05em; + text-underline-offset: 0.15em; } + +span.Command, span.Rule, span.Hyperlink, +span.Label, span.Reference, span.Other { + color: var(--other); } + +/* Pop type, const, proc, and iterator defs in nim def blocks */ +dt pre > span.Identifier, dt pre > span.Operator { + color: var(--identifier); + font-weight: 700; } + +dt pre > span.Keyword ~ span.Identifier, dt pre > span.Identifier ~ span.Identifier, +dt pre > span.Operator ~ span.Identifier, dt pre > span.Other ~ span.Identifier { + color: var(--identifier); + font-weight: inherit; } + +/* Nim sprite for the footer (taken from main page favicon) */ +.nim-sprite { + display: inline-block; + width: 51px; + height: 14px; + background-position: 0 0; + background-size: 51px 14px; + -webkit-filter: opacity(50%); + filter: opacity(50%); + background-repeat: no-repeat; + background-image: var(--nim-sprite-base64); + margin-bottom: 5px; } + +span.pragmadots { + /* Position: relative frees us up to make the dots + look really nice without fucking up the layout and + causing bulging in the parent container */ + position: relative; + /* 1px down looks slightly nicer */ + top: 1px; + padding: 2px; + background-color: var(--third-background); + border-radius: 4px; + margin: 0 2px; + cursor: pointer; + font-size: 0.8em; } + +span.pragmadots:hover { + background-color: var(--hint); } + +span.pragmawrap { + display: none; } + +span.attachedType { + display: none; + visibility: hidden; } diff --git a/src/syndicate/membranes.nim b/src/sam/membranes.nim similarity index 100% rename from src/syndicate/membranes.nim rename to src/sam/membranes.nim diff --git a/src/sam/observers.nim b/src/sam/observers.nim new file mode 100644 index 0000000..21da65a --- /dev/null +++ b/src/sam/observers.nim @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: ☭ Emery Hemingway +# SPDX-License-Identifier: Unlicense + +import preserves +import ./actors, ./patterns, ./protocols/dataspace + +proc observe*(turn: var Turn; ds: Cap; pat: Pattern; e: Entity): Cap {.discardable.} = + result = newCap(turn, e) + publish(turn, ds, Observe(pattern: pat, observer: result)) diff --git a/src/syndicate/patterns.nim b/src/sam/patterns.nim similarity index 100% rename from src/syndicate/patterns.nim rename to src/sam/patterns.nim diff --git a/src/syndicate/protocols/Tupfile b/src/sam/protocols/Tupfile similarity index 100% rename from src/syndicate/protocols/Tupfile rename to src/sam/protocols/Tupfile diff --git a/src/syndicate/protocols/dataspace.nim b/src/sam/protocols/dataspace.nim similarity index 100% rename from src/syndicate/protocols/dataspace.nim rename to src/sam/protocols/dataspace.nim diff --git a/src/syndicate/protocols/dataspacePatterns.nim b/src/sam/protocols/dataspacePatterns.nim similarity index 100% rename from src/syndicate/protocols/dataspacePatterns.nim rename to src/sam/protocols/dataspacePatterns.nim diff --git a/src/syndicate/protocols/gatekeeper.nim b/src/sam/protocols/gatekeeper.nim similarity index 100% rename from src/syndicate/protocols/gatekeeper.nim rename to src/sam/protocols/gatekeeper.nim diff --git a/src/syndicate/protocols/http.nim b/src/sam/protocols/http.nim similarity index 100% rename from src/syndicate/protocols/http.nim rename to src/sam/protocols/http.nim diff --git a/src/syndicate/protocols/noise.nim b/src/sam/protocols/noise.nim similarity index 100% rename from src/syndicate/protocols/noise.nim rename to src/sam/protocols/noise.nim diff --git a/src/syndicate/protocols/protocol.nim b/src/sam/protocols/protocol.nim similarity index 100% rename from src/syndicate/protocols/protocol.nim rename to src/sam/protocols/protocol.nim diff --git a/src/syndicate/protocols/service.nim b/src/sam/protocols/service.nim similarity index 100% rename from src/syndicate/protocols/service.nim rename to src/sam/protocols/service.nim diff --git a/src/syndicate/protocols/stdenv.nim b/src/sam/protocols/stdenv.nim similarity index 100% rename from src/syndicate/protocols/stdenv.nim rename to src/sam/protocols/stdenv.nim diff --git a/src/syndicate/protocols/stream.nim b/src/sam/protocols/stream.nim similarity index 100% rename from src/syndicate/protocols/stream.nim rename to src/sam/protocols/stream.nim diff --git a/src/syndicate/protocols/sturdy.nim b/src/sam/protocols/sturdy.nim similarity index 100% rename from src/syndicate/protocols/sturdy.nim rename to src/sam/protocols/sturdy.nim diff --git a/src/syndicate/protocols/tcp.nim b/src/sam/protocols/tcp.nim similarity index 100% rename from src/syndicate/protocols/tcp.nim rename to src/sam/protocols/tcp.nim diff --git a/src/syndicate/protocols/timer.nim b/src/sam/protocols/timer.nim similarity index 100% rename from src/syndicate/protocols/timer.nim rename to src/sam/protocols/timer.nim diff --git a/src/syndicate/protocols/trace.nim b/src/sam/protocols/trace.nim similarity index 100% rename from src/syndicate/protocols/trace.nim rename to src/sam/protocols/trace.nim diff --git a/src/syndicate/protocols/transportAddress.nim b/src/sam/protocols/transportAddress.nim similarity index 100% rename from src/syndicate/protocols/transportAddress.nim rename to src/sam/protocols/transportAddress.nim diff --git a/src/syndicate/protocols/worker.nim b/src/sam/protocols/worker.nim similarity index 100% rename from src/syndicate/protocols/worker.nim rename to src/sam/protocols/worker.nim diff --git a/src/syndicate/relays.nim b/src/sam/relays.nim similarity index 99% rename from src/syndicate/relays.nim rename to src/sam/relays.nim index 0792039..48ef652 100644 --- a/src/syndicate/relays.nim +++ b/src/sam/relays.nim @@ -6,7 +6,7 @@ from std/os import getEnv, `/` import pkg/sys/[ioqueue, sockets] import preserves -import ../syndicate, /capabilities, ./durings, ./membranes, ./protocols/[gatekeeper, protocol, sturdy, transportAddress] +import ./syndicate, /capabilities, ./durings, ./membranes, ./protocols/[gatekeeper, protocol, sturdy, transportAddress] when defined(traceSyndicate): when defined(posix): diff --git a/src/sam/relays.nim.old b/src/sam/relays.nim.old new file mode 100644 index 0000000..80ac17a --- /dev/null +++ b/src/sam/relays.nim.old @@ -0,0 +1,443 @@ +# SPDX-FileCopyrightText: ☭ Emery Hemingway +# SPDX-License-Identifier: Unlicense + +import std/[asyncdispatch, options, tables] +from std/os import getEnv, `/` +import preserves +import ../syndicate, /capabilities, ./durings, ./membranes, ./protocols/[gatekeeper, protocol, sturdy, transportAddress] + +when defined(traceSyndicate): + when defined(posix): + template trace(args: varargs[untyped]): untyped = stderr.writeLine(args) + else: + template trace(args: varargs[untyped]): untyped = echo(args) +else: + template trace(args: varargs[untyped]): untyped = discard + +export `$` + +type + Oid = sturdy.Oid + +export Stdio, Tcp, WebSocket, Unix + +type + Assertion = Value + WireRef = sturdy.WireRef + Turn = syndicate.Turn + Handle = actors.Handle + +type + PacketWriter = proc (pkt: sink Packet): Future[void] {.gcsafe.} + RelaySetup = proc (turn: var Turn; relay: Relay) {.gcsafe.} + + Relay* = ref object of RootObj + facet: Facet + inboundAssertions: Table[Handle, + tuple[localHandle: Handle, imported: seq[WireSymbol]]] + outboundAssertions: Table[Handle, seq[WireSymbol]] + exported: Membrane + imported: Membrane + nextLocalOid: Oid + pendingTurn: protocol.Turn + packetWriter: PacketWriter + untrusted: bool + + SyncPeerEntity = ref object of Entity + relay: Relay + peer: Cap + handleMap: Table[Handle, Handle] + e: WireSymbol + + RelayEntity = ref object of Entity + ## https://synit.org/book/protocol.html#relay-entities + label: string + relay: Relay + +proc releaseCapOut(r: Relay; e: WireSymbol) = + r.exported.drop e + +method publish(spe: SyncPeerEntity; t: var Turn; a: AssertionRef; h: Handle) = + spe.handleMap[h] = publish(t, spe.peer, a.value) + +method retract(se: SyncPeerEntity; t: var Turn; h: Handle) = + var other: Handle + if se.handleMap.pop(h, other): + retract(t, other) + +method message(se: SyncPeerEntity; t: var Turn; a: AssertionRef) = + if not se.e.isNil: + se.relay.releaseCapOut(se.e) + message(t, se.peer, a.value) + +method sync(se: SyncPeerEntity; t: var Turn; peer: Cap) = + sync(t, se.peer, peer) + +proc newSyncPeerEntity(r: Relay; p: Cap): SyncPeerEntity = + SyncPeerEntity(relay: r, peer: p) + +proc rewriteCapOut(relay: Relay; cap: Cap; exported: var seq[WireSymbol]): WireRef = + if cap.target of RelayEntity and cap.target.RelayEntity.relay == relay and cap.attenuation.len == 0: + result = WireRef(orKind: WireRefKind.yours, yours: WireRefYours(oid: cap.target.oid)) + else: + var ws = grab(relay.exported, cap) + if ws.isNil: + ws = newWireSymbol(relay.exported, relay.nextLocalOid, cap) + inc relay.nextLocalOid + exported.add ws + result = WireRef( + orKind: WireRefKind.mine, + mine: WireRefMine(oid: ws.oid)) + +proc rewriteOut(relay: Relay; v: Assertion): + tuple[rewritten: Value, exported: seq[WireSymbol]] {.gcsafe.} = + var exported: seq[WireSymbol] + result.rewritten = mapEmbeds(v) do (pr: Value) -> Value: + let o = pr.unembed(Cap); if o.isSome: + rewriteCapOut(relay, o.get, exported).toPreserves + else: pr + result.exported = exported + +proc register(relay: Relay; v: Assertion; h: Handle): tuple[rewritten: Value, exported: seq[WireSymbol]] = + result = rewriteOut(relay, v) + relay.outboundAssertions[h] = result.exported + +proc deregister(relay: Relay; h: Handle) = + var outbound: seq[WireSymbol] + if relay.outboundAssertions.pop(h, outbound): + for e in outbound: releaseCapOut(relay, e) + +proc send(r: Relay; turn: var Turn; rOid: protocol.Oid; m: Event) = + if r.pendingTurn.len == 0: + # If the pending queue is empty then schedule a packet + # to be sent after pending I/O is processed. + callSoon do (): + r.facet.run do (turn: var Turn): + var pkt = Packet( + orKind: PacketKind.Turn, + turn: move r.pendingTurn) + trace "C: ", pkt + assert(not r.packetWriter.isNil, "missing packetWriter proc") + r.packetWriter(turn, encode pkt) + r.pendingTurn.add TurnEvent(oid: rOid, event: m) + +proc send(re: RelayEntity; turn: var Turn; ev: Event) = + send(re.relay, turn, protocol.Oid re.oid, ev) + +method publish(re: RelayEntity; t: var Turn; a: AssertionRef; h: Handle) {.gcsafe.} = + re.send(t, Event( + orKind: EventKind.Assert, + `assert`: protocol.Assert( + assertion: re.relay.register(a.value, h).rewritten, + handle: h))) + +method retract(re: RelayEntity; t: var Turn; h: Handle) {.gcsafe.} = + re.relay.deregister h + re.send(t, Event( + orKind: EventKind.Retract, + retract: Retract(handle: h))) + +method message(re: RelayEntity; turn: var Turn; msg: AssertionRef) {.gcsafe.} = + var (value, exported) = rewriteOut(re.relay, msg.value) + assert(len(exported) == 0, "cannot send a reference in a message") + if len(exported) == 0: + re.send(turn, Event(orKind: EventKind.Message, message: Message(body: value))) + +method sync(re: RelayEntity; turn: var Turn; peer: Cap) {.gcsafe.} = + var + peerEntity = newSyncPeerEntity(re.relay, peer) + exported: seq[WireSymbol] + wr = rewriteCapOut(re.relay, turn.newCap(peerEntity), exported) + peerEntity.e = exported[0] + var ev = Event(orKind: EventKind.Sync) + ev.sync.peer = wr.toPreserves.embed + re.send(turn, ev) + +proc newRelayEntity(label: string; r: Relay; o: Oid): RelayEntity = + RelayEntity(label: label, relay: r, oid: o) + +using + relay: Relay + facet: Facet + +proc lookupLocal(relay; oid: Oid): Cap = + let sym = relay.exported.grab oid + if sym.isNil: newInertCap() + else: sym.cap + +proc isInert(r: Cap): bool = + r.target.isNil + +proc rewriteCapIn(relay; facet; n: WireRef, imported: var seq[WireSymbol]): Cap = + case n.orKind + of WireRefKind.mine: + var e = relay.imported.grab(n.mine.oid) + if e.isNil: + e = newWireSymbol( + relay.imported, + n.mine.oid, + newCap(facet, newRelayEntity("rewriteCapIn", relay, n.mine.oid)), + ) + imported.add e + result = e.cap + of WireRefKind.yours: + let r = relay.lookupLocal(n.yours.oid) + if n.yours.attenuation.len == 0 or r.isInert: result = r + else: raiseAssert "attenuation not implemented" + +proc rewriteIn(relay; facet; v: Value): + tuple[rewritten: Assertion; imported: seq[WireSymbol]] {.gcsafe.} = + var imported: seq[WireSymbol] + result.rewritten = mapEmbeds(v) do (pr: Value) -> Value: + let wr = pr.preservesTo WireRef; if wr.isSome: + result = rewriteCapIn(relay, facet, wr.get, imported).embed + else: + result = pr + result.imported = imported + +proc close(r: Relay) = discard + +proc dispatch*(relay: Relay; turn: var Turn; cap: Cap; event: Event) {.gcsafe.} = + case event.orKind + of EventKind.Assert: + let (a, imported) = rewriteIn(relay, turn.facet, event.assert.assertion) + relay.inboundAssertions[event.assert.handle] = (publish(turn, cap, a), imported,) + + of EventKind.Retract: + let remoteHandle = event.retract.handle + var outbound: tuple[localHandle: Handle, imported: seq[WireSymbol]] + if relay.inboundAssertions.pop(remoteHandle, outbound): + for e in outbound.imported: relay.imported.drop e + turn.retract(outbound.localHandle) + + of EventKind.Message: + let (a, imported) = rewriteIn(relay, turn.facet, event.message.body) + assert imported.len == 0, "Cannot receive transient reference" + turn.message(cap, a) + + of EventKind.Sync: + discard # TODO + #[ + var imported: seq[WireSymbol] + let k = relay.rewriteCapIn(turn, evenr.sync.peer, imported) + turn.sync(cap) do (turn: var Turn): + turn.message(k, true) + for e in imported: relay.imported.del e + ]# + +proc dispatch*(relay: Relay; v: Value) {.gcsafe.} = + trace "S: ", v + run(relay.facet) do (t: var Turn): + var pkt: Packet + if pkt.fromPreserves(v): + case pkt.orKind + of PacketKind.Turn: + # https://synit.org/book/protocol.html#turn-packets + for te in pkt.turn: + let r = lookupLocal(relay, te.oid.Oid) + if not r.isInert: + dispatch(relay, t, r, te.event) + else: + stderr.writeLine("discarding event for unknown Cap; ", te.event) + of PacketKind.Error: + # https://synit.org/book/protocol.html#error-packets + when defined(posix): + stderr.writeLine("Error from server: ", pkt.error.message, " (detail: ", pkt.error.detail, ")") + close relay + of PacketKind.Extension: + # https://synit.org/book/protocol.html#extension-packets + discard + else: + when defined(posix): + stderr.writeLine("discarding undecoded packet ", v) + +type + RelayOptions* = object of RootObj + packetWriter*: PacketWriter + untrusted*: bool + RelayActorOptions* = object of RelayOptions + initialOid*: Option[Oid] + initialCap*: Cap + nextLocalOid*: Option[Oid] + +proc newRelay(turn: var Turn; opts: RelayOptions; setup: RelaySetup): Relay = + result = Relay( + facet: turn.facet, + packetWriter: opts.packetWriter, + untrusted: opts.untrusted) + discard result.facet.preventInertCheck() + setup(turn, result) + +proc transportConnectionResolve(addrAss: Assertion; ds: Cap): gatekeeper.TransportConnection = + result.`addr` = addrAss + result.resolved = Resolved(orKind: ResolvedKind.accepted) + result.resolved.accepted.responderSession = ds + +proc spawnRelay*(name: string; turn: var Turn; ds: Cap; addrAss: Assertion; opts: RelayActorOptions; setup: RelaySetup) = + discard spawn(name, turn) do (turn: var Turn): + let relay = newRelay(turn, opts, setup) + if not opts.initialCap.isNil: + var exported: seq[WireSymbol] + discard rewriteCapOut(relay, opts.initialCap, exported) + opts.nextLocalOid.map do (oid: Oid): + relay.nextLocalOid = + if oid == 0.Oid: 1.Oid + else: oid + if opts.initialOid.isSome: + var + imported: seq[WireSymbol] + wr = WireRef( + orKind: WireRefKind.mine, + mine: WireRefMine(oid: opts.initialOid.get)) + res = rewriteCapIn(relay, turn.facet, wr, imported) + discard publish(turn, ds, transportConnectionResolve(addrAss, res)) + else: + discard publish(turn, ds, transportConnectionResolve(addrAss, ds)) + +when defined(posix): + import std/asyncnet + from std/nativesockets import AF_INET, AF_UNIX, IPPROTO_TCP, SOCK_STREAM, Protocol + +type ShutdownEntity* = ref object of Entity + +method retract(e: ShutdownEntity; turn: var Turn; h: Handle) = + stopActor(turn) + +type ConnectProc* = proc (turn: var Turn; ds: Cap) {.gcsafe.} + +export Tcp + +when defined(posix): + export Unix + + proc connect*(turn: var Turn; ds: Cap; route: Route; addrAss: Assertion; socket: AsyncSocket; step: Value) = + ## Relay a dataspace over an open `AsyncSocket`. + proc socketWriter(packet: sink Packet): Future[void] = + socket.send(cast[string](encode(packet))) + const recvSize = 0x2000 + var shutdownCap: Cap + let + reenable = turn.facet.preventInertCheck() + connectionClosedCap = newCap(turn, ShutdownEntity()) + discard bootActor("socket") do (turn: var Turn): + var ops = RelayActorOptions( + packetWriter: socketWriter, + initialOid: 0.Oid.some) + spawnRelay("socket", turn, ds, addrAss, ops) do (turn: var Turn; relay: Relay): + let facet = turn.facet + var wireBuf = newBufferedDecoder(0) + + turn.facet.actor.atExit do (turn: var Turn): close(socket) + discard publish(turn, connectionClosedCap, true) + shutdownCap = newCap(turn, ShutdownEntity()) + onPublish(turn, ds, TransportConnection ?: {0: ?addrAss, 2: ?:Rejected}) do (detail: Value): + raise newException(IOError, $detail) + onPublish(turn, ds, TransportConnection ?: {0: ?addrAss, 2: ?:ResolvedAccepted}) do (gatekeeper: Cap): + run(gatekeeper.relay) do (turn: var Turn): + reenable() + discard publish(turn, shutdownCap, true) + proc duringCallback(turn: var Turn; ass: Assertion; h: Handle): TurnAction = + let facet = inFacet(turn) do (turn: var Turn): + let o = ass.preservesTo Resolved; if o.isSome: + discard publish(turn, ds, ResolvePath( + route: route, `addr`: addrAss, resolved: o.get)) + proc action(turn: var Turn) = + stop(turn, facet) + result = action + var resolve = Resolve( + step: step, + observer: newCap(turn, during(duringCallback)), + ) + discard publish(turn, gatekeeper, resolve) + + proc connect*(turn: var Turn; ds: Cap; route: Route; transport: Tcp; step: Value) = + ## Relay a dataspace over TCP. + let socket = newAsyncSocket( + domain = AF_INET, + sockType = SOCK_STREAM, + protocol = IPPROTO_TCP, + buffered = false) + let fut = connect(socket, transport.host, Port transport.port) + addCallback(fut, turn) do (turn: var Turn): + connect(turn, ds, route, transport.toPreserves, socket, step) + + proc connect*(turn: var Turn; ds: Cap; route: Route; transport: Unix; step: Value) = + ## Relay a dataspace over a UNIX socket. + let socket = newAsyncSocket( + domain = AF_UNIX, + sockType = SOCK_STREAM, + protocol = cast[Protocol](0), + buffered = false) + let fut = connectUnix(socket, transport.path) + addCallback(fut, turn) do (turn: var Turn): + connect(turn, ds, route, transport.toPreserves, socket, step) + + import std/asyncfile + + const stdinReadSize = 128 + + proc connectStdio*(turn: var Turn; ds: Cap) = + ## Connect to an external dataspace over stdin and stdout. + proc stdoutWriter(packet: sink Packet): Future[void] = + result = newFuture[void]() + var buf = encode(packet) + doAssert writeBytes(stdout, buf, 0, buf.len) == buf.len + flushFile(stdout) + complete result + var opts = RelayActorOptions( + packetWriter: stdoutWriter, + initialCap: ds, + initialOid: 0.Oid.some) + spawnRelay("stdio", turn, ds, Stdio().toPreserves, opts) do (turn: var Turn; relay: Relay): + let + facet = turn.facet + asyncStdin = openAsync("/dev/stdin") # this is universal now? + close(stdin) + facet.actor.atExit do (turn: var Turn): + close(asyncStdin) + var wireBuf = newBufferedDecoder(0) + proc readCb(pktFut: Future[string]) {.gcsafe.} = + if not pktFut.failed: + var buf = pktFut.read + if buf.len == 0: + run(facet) do (turn: var Turn): stopActor(turn) + else: + feed(wireBuf, buf) + var (success, pr) = decode(wireBuf) + if success: + dispatch(relay, pr) + asyncStdin.read(stdinReadSize).addCallback(readCb) + asyncStdin.read(stdinReadSize).addCallback(readCb) + +type BootProc* = proc (turn: var Turn; ds: Cap) {.gcsafe.} + +proc envRoute*: Route = + var text = getEnv("SYNDICATE_ROUTE") + if text == "": + var tx = (getEnv("XDG_RUNTIME_DIR", "/run/user/1000") / "dataspace").toPreserves + result.transports = @[initRecord("unix", tx)] + result.pathSteps = @[capabilities.mint().toPreserves] + else: + var pr = parsePreserves(text) + if not result.fromPreserves(pr): + raise newException(ValueError, "failed to parse $SYNDICATE_ROUTE " & $pr) + +proc resolve*(turn: var Turn; ds: Cap; route: Route; bootProc: BootProc) = + var + unix: Unix + tcp: Tcp + stdio: Stdio + doAssert(route.transports.len == 1, "only a single transport supported for routes") + doAssert(route.pathSteps.len < 2, "multiple path steps not supported for routes") + if unix.fromPreserves route.transports[0]: + connect(turn, ds, route, unix, route.pathSteps[0]) + elif tcp.fromPreserves route.transports[0]: + connect(turn, ds, route, tcp, route.pathSteps[0]) + elif stdio.fromPreserves route.transports[0]: + connectStdio(turn, ds) + bootProc(turn, ds) + else: + raise newException(ValueError, "unsupported route") + + during(turn, ds, ResolvePath ?: { 0: ?route, 3: ?:ResolvedAccepted}) do (dest: Cap): + bootProc(turn, dest) diff --git a/src/syndicate/skeletons.nim b/src/sam/skeletons.nim similarity index 100% rename from src/syndicate/skeletons.nim rename to src/sam/skeletons.nim diff --git a/src/syndicate.nim b/src/sam/syndicate.nim similarity index 97% rename from src/syndicate.nim rename to src/sam/syndicate.nim index 07f233b..f42ea24 100644 --- a/src/syndicate.nim +++ b/src/sam/syndicate.nim @@ -10,9 +10,9 @@ import pkg/cps import preserves export preserves -import ./syndicate/[actors, dataspaces, patterns] +import ./[actors, dataspaces, patterns] # durings -import ./syndicate/protocols/dataspace +import ./protocols/dataspace export actors, dataspace, dataspaces, patterns @@ -220,3 +220,6 @@ macro onStop*(body: untyped) = `body` return ]# + +proc runActor*(name: string; bootProc: FacetProc) = + let actor = spawnActor(name, bootProc) diff --git a/src/sam/tracing.nim b/src/sam/tracing.nim new file mode 100644 index 0000000..a1f2326 --- /dev/null +++ b/src/sam/tracing.nim @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: ☭ Emery Hemingway +# SPDX-License-Identifier: Unlicense + +import ./protocols/[protocol, trace] +export trace + +proc traceAction*(e: protocol.Event): trace.TurnEvent = + var act = ActionDescription(orKind: ActionDescriptionKind.enqueue) + act.enqueue.event = TargetedTurnEvent( + target: cap.traceTarget, + detail: trace.TurnEvent(orKind: trace.TurnEventKind.assert) + ) + act.enqueue.event.detail = trace.TurnEvent(orKind: TurnEventKind.assert) + act.enqueue.event.detail.assert = TurnEventAssert( + assertion: AssertionDescription(orKind: AssertionDescriptionKind.value), + handle: result, + ) + act.enqueue.event.detail.assert.assertion.value.value = val + turn.desc.actions.add act diff --git a/tests/test_chat.nim b/tests/test_chat.nim index 24c1b4a..ef55869 100644 --- a/tests/test_chat.nim +++ b/tests/test_chat.nim @@ -23,17 +23,17 @@ proc readStdin(facet: Facet; ds: Cap; username: string) = readLine() readLine() -proc chat(turn: var Turn; ds: Cap; username: string) = - during(turn, ds, ?:Present) do (who: string): +proc chat(facet: Facet; ds: Cap; username: string) = + during(facet, ds, ?:Present) do (who: string): echo who, " joined" do: echo who, " left" - onMessage(turn, ds, ?:Says) do (who: string, what: string): + onMessage(facet, ds, ?:Says) do (who: string, what: string): echo who, ": ", what - discard publish(turn, ds, Present(username: username)) - readStdin(turn.facet, ds, username) + discard publish(facet, ds, Present(username: username)) + readStdin(facet, ds, username) proc main = let route = envRoute() @@ -48,9 +48,10 @@ proc main = if username == "": stderr.writeLine "--user: unspecified" else: - runActor("chat") do (turn: var Turn; root: Cap): - spawnRelays(turn, root) - resolve(turn, root, route) do (turn: var Turn; ds: Cap): - chat(turn, ds, username) + runActor("chat") do (root: Facet): + let ds = facet.newDataspace() + facet.spawnRelays(ds) + resolve(facet, ds, route) do (remote: Cap): + chat(facet, remote, username) main() diff --git a/tests/test_timers.nim b/tests/test_timers.nim index 6419270..f27ceb9 100644 --- a/tests/test_timers.nim +++ b/tests/test_timers.nim @@ -9,22 +9,23 @@ import syndicate proc now: float64 = getTime().toUnixFloat() -proc main() {.syndicate.} = - let ds = newDataspace() - let h = publish(ds, "hello world!".toPreserves) +runActor("timer-test") do (root: Facet): - #onMessage(ds, grab()) do (v: Value): - # stderr.writeLine "observed message ", v + let ds = facet.newDataspace() + let h = facet.publish(ds, "hello world!".toPreserves) - message(ds, "hello world!".toPreserves) - retract(h) - sync(ds) + facet.onMessage(ds, grab()) do (v: Value): + stderr.writeLine "observed message ", v - onStop: + facet.message(ds, "hello world!".toPreserves) + facet.retract(h) + facet.sync(ds) + + facet.onStop: echo "anonymous stop handler was invoked" echo "stopping actor" - stopActor() + facet.stopActor() echo "actor stopped but still executing?" #[ @@ -38,5 +39,3 @@ proc main() {.syndicate.} = stderr.writeLine "slept one second thrice" stopActor() ]# - -bootActor("main", whelp main())