From 0c6f57c6cd35c374db86c2cc9b49de593292b494 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 9 Jun 2023 13:10:56 +0200 Subject: [PATCH] Try using Cap instead of Ref to allow variation in embedded reference types --- packages/compiler/src/compiler/codegen.ts | 4 +- packages/core/src/runtime/actor.ts | 100 +++++++++++++-------- packages/core/src/runtime/dataspace.ts | 12 +-- packages/core/src/runtime/pattern.ts | 8 +- packages/core/src/runtime/quasivalue.ts | 4 +- packages/core/src/runtime/rewrite.ts | 33 ++++--- packages/core/src/runtime/skeleton.ts | 22 ++--- packages/core/src/transport/relay.ts | 102 +++++++++++----------- packages/create/template/.dir-locals.el | 22 ----- packages/create/template/src/index.ts | 8 +- packages/fs/src/index.ts | 6 +- packages/html/src/index.ts | 22 ++--- packages/service/src/index.ts | 2 +- packages/timer/src/index.ts | 6 +- packages/ws-relay/src/index.ts | 28 +++--- 15 files changed, 194 insertions(+), 185 deletions(-) delete mode 100644 packages/create/template/.dir-locals.el diff --git a/packages/compiler/src/compiler/codegen.ts b/packages/compiler/src/compiler/codegen.ts index 154d18e..d09d30d 100644 --- a/packages/compiler/src/compiler/codegen.ts +++ b/packages/compiler/src/compiler/codegen.ts @@ -194,7 +194,7 @@ ${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')} }); x(ctx.parser.atStatement, (s, t) => { - return t`(((${ctx.argDecl(t, 'currentSyndicateTarget', '__SYNDICATE__.Ref')}) => {${walk(s.body)}})(${walk(s.target)}));`; + return t`(((${ctx.argDecl(t, 'currentSyndicateTarget', '__SYNDICATE__.Cap')}) => {${walk(s.body)}})(${walk(s.target)}));`; }); x(ctx.parser.createExpression, (s, t) => { @@ -280,7 +280,7 @@ ${joinItems(sa.captureBinders.map(binderTypeGuard(t)), '\n')} const fns = JSON.stringify(s.fields.map(f => f.id.text)); const formatBinder = (b: Binder) => t`${[b.id]}: ${b.type ?? '__SYNDICATE__.AnyValue'}`; const fs = ctx.typescript - ? t`<{${commaJoin(s.fields.map(formatBinder))}}, __SYNDICATE__.Ref>` + ? t`<{${commaJoin(s.fields.map(formatBinder))}}, __SYNDICATE__.Cap>` : ''; return t`const ${[s.label]} = __SYNDICATE__.Record.makeConstructor${fs}()(${maybeWalk(s.wireName) ?? l}, ${fns});`; }); diff --git a/packages/core/src/runtime/actor.ts b/packages/core/src/runtime/actor.ts index 71f5b25..5c7cacc 100644 --- a/packages/core/src/runtime/actor.ts +++ b/packages/core/src/runtime/actor.ts @@ -9,7 +9,11 @@ import { ActionDescription, StructuredTask, TaskAction } from './task.js'; import { randomId } from './randomid.js'; import * as Q from '../gen/queuedTasks.js'; -export type AnyValue = Value; +export interface Cap { + toRef(): Ref | undefined; +} + +export type AnyValue = Value; //--------------------------------------------------------------------------- @@ -17,7 +21,7 @@ if ('stackTraceLimit' in Error) { Error.stackTraceLimit = Infinity; } -export type Assertion = Value; +export type Assertion = Value; export type Handle = number; export type ExitReason = null | { ok: true } | { ok: false, err: unknown }; export type LocalAction = () => void; @@ -29,19 +33,17 @@ export interface Entity { assert(assertion: Assertion, handle: Handle): void; retract(handle: Handle): void; message(body: Assertion): void; - sync(peer: Ref): void; + sync(peer: Cap): void; data?: unknown; } -export type Cap = Ref; - export interface Ref { readonly relay: Facet; readonly target: Partial; readonly attenuation?: Caveat[]; } -export class RefImpl implements Ref { +export class RefImpl implements Ref, Cap { readonly relay: Facet; readonly target: Partial; readonly attenuation?: Caveat[]; @@ -64,16 +66,24 @@ export class RefImpl implements Ref { if ('sync' in this.target) sig = sig + 'S'; return `⌜${this.relay.idChain()}<${sig}>${entityRepr}⌝`; } + + toRef(): Ref | undefined { + return this; + } } //--------------------------------------------------------------------------- +export function isCap(v: any): v is Cap { + return 'toRef' in v && typeof(v.toRef) === 'function'; +} + export function isRef(v: any): v is Ref { return 'relay' in v && v.relay instanceof Facet && 'target' in v; } export function toRef(_v: any): Ref | undefined { - return isRef(_v) ? _v : void 0; + return isCap(_v) ? _v.toRef() : void 0; } export function assertionFrom(a: Assertable): Assertion { @@ -84,7 +94,7 @@ export function assertionFrom(a: Assertable): Assertion { } } -type OutboundAssertion = { handle: Handle, peer: Ref, established: boolean }; +type OutboundAssertion = { handle: Handle, peer: Cap, established: boolean }; type OutboundMap = Map; let nextActorId = 0; @@ -205,7 +215,7 @@ export class Facet { _halfLink(other: Facet): void { const h = nextHandle++; - const e = { handle: h, peer: { relay: other, target: new StopOnRetract() }, established: true }; + const e = { handle: h, peer: new RefImpl(other, new StopOnRetract()), established: true }; this.outbound.set(h, e); } @@ -265,7 +275,7 @@ export class StopOnRetract implements Partial { data = STOP_ON_RETRACT; } -export function _sync_impl(e: Partial, peer: Ref): void { +export function _sync_impl(e: Partial, peer: Cap): void { e.sync ? e.sync!(peer) : Turn.active.message(peer, true); } @@ -279,7 +289,7 @@ export class Turn { return Turn.active.activeFacet; } - static ref>(e: T): Ref { + static ref>(e: T): Cap { return Turn.active.ref(e); } @@ -324,7 +334,7 @@ export class Turn { this._activeFacet = saved; } - ref>(e: T): Ref { + ref>(e: T): Cap { return new RefImpl(this.activeFacet, e); } @@ -359,7 +369,7 @@ export class Turn { // ^ we trust initialAssertions, so can use `!` safely const newActor = Actor.__unsafeNew(this.activeFacet.actor.space, newOutbound); - const detail: Q.OptionalAny = 'detail' in bootProc ? Q.OptionalAny.some(bootProc.detail) : Q.OptionalAny.none(); + const detail: Q.OptionalAny = 'detail' in bootProc ? Q.OptionalAny.some(bootProc.detail) : Q.OptionalAny.none(); const spawningFacet = this.activeFacet; this.enqueue(spawningFacet, () => { @@ -370,7 +380,7 @@ export class Turn { }); }, () => { - const a = new KeyedSet(); + const a = new KeyedSet(); initialAssertions.forEach(h => a.add(h)); return Q.ActionDescription.spawnActor({ detail, initialAssertions: a }); }); @@ -423,11 +433,11 @@ export class Turn { } assertDataflow(assertionFunction: () => { - target: Ref | undefined, + target: Cap | undefined, assertion: Assertable | undefined }) { let handle: Handle | undefined = void 0; - let target: Ref | undefined = void 0; + let target: Cap | undefined = void 0; let assertion: Assertable | undefined = void 0; this.dataflow(() => { let {target: nextTarget, assertion: nextAssertion} = assertionFunction(); @@ -439,17 +449,19 @@ export class Turn { }); } - assert(ref: Ref, assertion: Assertable): Handle { + assert(cap: Cap, assertion: Assertable): Handle { const h = nextHandle++; - this._assert(ref, assertion, h); + this._assert(cap, assertion, h); return h; } - _assert(ref: Ref, assertable: Assertable, h: Handle) { + _assert(cap: Cap, assertable: Assertable, h: Handle) { + const ref = cap.toRef(); + if (ref === void 0) return; const assertion = assertionFrom(assertable); const a = runRewrites(ref.attenuation, assertion); if (a !== null) { - const e = { handle: h, peer: ref, established: false }; + const e = { handle: h, peer: cap, established: false }; this.activeFacet.outbound.set(h, e); this.enqueue(ref.relay, () => { @@ -457,7 +469,7 @@ export class Turn { ref.target.assert?.(a, h); }, () => Q.ActionDescription.assert({ - target: ref, + target: cap, handle: h, assertion, })); @@ -472,21 +484,22 @@ export class Turn { } } - replace(ref: Ref | undefined, h: Handle | undefined, assertion: Assertable | undefined): Handle | undefined { - const newHandle = (assertion === void 0 || ref === void 0) + replace(cap: Cap | undefined, h: Handle | undefined, assertion: Assertable | undefined): Handle | undefined { + const newHandle = (assertion === void 0 || cap === void 0) ? void 0 - : this.assert(ref, assertion); + : this.assert(cap, assertion); this.retract(h); return newHandle; } _retract(e: OutboundAssertion): void { this.activeFacet.outbound.delete(e.handle); - this.enqueue(e.peer.relay, + const ref = e.peer.toRef()!; + this.enqueue(ref.relay, () => { if (e.established) { e.established = false; - e.peer.target.retract?.(e.handle); + ref.target.retract?.(e.handle); } }, () => Q.ActionDescription.retract({ @@ -495,27 +508,34 @@ export class Turn { })); } - sync(ref: Ref): Promise { - return new Promise(resolve => this._sync(ref, this.ref({ message() { resolve() } }))); + sync(cap: Cap): Promise { + return new Promise(resolve => this._sync(cap, this.ref({ message() { resolve() } }))); } - _sync(ref: Ref, peer: Ref): void { - this.enqueue(ref.relay, - () => _sync_impl(ref.target, peer), - () => Q.ActionDescription.sync({ - target: ref, - callback: peer, - })); + _sync(cap: Cap, peer: Cap): void { + const ref = cap.toRef(); + if (ref === void 0) { + this.message(peer, true); + } else { + this.enqueue(ref.relay, + () => _sync_impl(ref.target, peer), + () => Q.ActionDescription.sync({ + target: cap, + callback: peer, + })); + } } - message(ref: Ref, assertable: Assertable): void { + message(cap: Cap, assertable: Assertable): void { + const ref = cap.toRef(); + if (ref === void 0) return; const assertion = assertionFrom(assertable); const a = runRewrites(ref.attenuation, assertion); if (a !== null) { this.enqueue(ref.relay, () => ref.target.message?.(assertion), () => Q.ActionDescription.message({ - target: ref, + target: cap, assertion, })); } @@ -579,3 +599,9 @@ function stopIfInertAfter(a: LocalAction): LocalAction { Q.ActionDescription.inertCheck); }; } + +export const INERT_CAP: Cap & Ref = { + relay: Actor.boot(() => Turn.active.stop()).root, + target: {}, + toRef(): Ref | undefined { return this; }, +}; diff --git a/packages/core/src/runtime/dataspace.ts b/packages/core/src/runtime/dataspace.ts index 9521e1f..5962558 100644 --- a/packages/core/src/runtime/dataspace.ts +++ b/packages/core/src/runtime/dataspace.ts @@ -3,7 +3,7 @@ import { IdentityMap, KeyedDictionary, stringify } from '@preserves/core'; import { Index, IndexObserver } from './skeleton.js'; -import { Actor, AnyValue, Assertion, Entity, Facet, Handle, LocalAction, Ref, Turn } from './actor.js'; +import { Actor, AnyValue, Assertion, Cap, Entity, Facet, Handle, LocalAction, Turn } from './actor.js'; import { Observe, toObserve } from '../gen/dataspace.js'; import * as P from '../gen/dataspacePatterns.js'; @@ -16,10 +16,10 @@ export type DataspaceOptions = { }; export class DataspaceObserver implements IndexObserver { - readonly captureMap = new KeyedDictionary, Handle, Ref>(); + readonly captureMap = new KeyedDictionary, Handle, Cap>(); constructor( - public readonly target: Ref, + public readonly target: Cap, ) {} onAssert(captures: Assertion[], t: Turn) { @@ -49,7 +49,7 @@ export class Dataspace implements Partial { readonly options: DataspaceOptions; readonly index = new Index(); readonly handleMap = new IdentityMap(); - readonly observerMap = new IdentityMap(); + readonly observerMap = new IdentityMap(); readonly data = this; constructor(options?: DataspaceOptions) { @@ -95,7 +95,7 @@ export class Dataspace implements Partial { this.index.deliverMessage(v, Turn.active); } - static boot(bootProc: (ds: Ref) => void, options?: DataspaceOptions): Actor { + static boot(bootProc: (ds: Cap) => void, options?: DataspaceOptions): Actor { return Actor.boot(() => { Turn.activeFacet.preventInertCheck(); const ds = Turn.active.ref(new Dataspace(options)); @@ -137,6 +137,6 @@ export function assertionFacetObserver(f: (a: Assertion) => void, inertOk: boole }; } -export function assertObserve(ds: Ref, pattern: P.Pattern, e: Partial): Handle { +export function assertObserve(ds: Cap, pattern: P.Pattern, e: Partial): Handle { return Turn.active.assert(ds, Observe({ pattern, observer: Turn.ref(e) })); } diff --git a/packages/core/src/runtime/pattern.ts b/packages/core/src/runtime/pattern.ts index 69bbdad..eb4e541 100644 --- a/packages/core/src/runtime/pattern.ts +++ b/packages/core/src/runtime/pattern.ts @@ -2,7 +2,7 @@ /// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones import { canonicalString, Dictionary, is, Record, RecordConstructorInfo, Value } from '@preserves/core'; -import { AnyValue, Ref } from './actor.js'; +import { AnyValue, Cap } from './actor.js'; import * as P from '../gen/dataspacePatterns.js'; export type Path = Array; @@ -217,7 +217,7 @@ export function drop_lit(p: P.Pattern): AnyValue | null { case 'DCompound': switch (p.value._variant) { case 'rec': { - const v = [] as unknown as Record; + const v = [] as unknown as Record; v.label = p.value.label; p.value.fields.forEach(tt => v.push(walk(tt))); return v; @@ -225,7 +225,7 @@ export function drop_lit(p: P.Pattern): AnyValue | null { case 'arr': return p.value.items.map(walk); case 'dict': { - const v = new Dictionary(); + const v = new Dictionary(); p.value.entries.forEach((pp, key) => v.set(key, walk(pp))); return v; } @@ -253,5 +253,5 @@ export function arr(... patterns: P.Pattern[]): P.Pattern { } export function dict(... entries: [AnyValue, P.Pattern][]): P.Pattern { - return P.Pattern.DCompound(P.DCompound.dict(new Dictionary(entries))); + return P.Pattern.DCompound(P.DCompound.dict(new Dictionary(entries))); } diff --git a/packages/core/src/runtime/quasivalue.ts b/packages/core/src/runtime/quasivalue.ts index 8b4c568..922ec50 100644 --- a/packages/core/src/runtime/quasivalue.ts +++ b/packages/core/src/runtime/quasivalue.ts @@ -1,14 +1,14 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2021-2023 Tony Garnock-Jones -import { AnyValue, Ref } from './actor.js'; +import { AnyValue, Cap } from './actor.js'; import { Pattern, toPattern } from '../gen/dataspacePatterns.js'; import * as P from './pattern.js'; import { RecordConstructorInfo, is, Record } from '@preserves/core'; import { Meta, Type, GenType, SchemaDefinition } from '@preserves/schema'; export type QuasiValueConstructorInfo = - | { constructorInfo: RecordConstructorInfo } + | { constructorInfo: RecordConstructorInfo } | { schema(): SchemaDefinition } | { quasiValue(... args: QuasiValue[]): QuasiValue } ; diff --git a/packages/core/src/runtime/rewrite.ts b/packages/core/src/runtime/rewrite.ts index 4c27c7b..5b84e3c 100644 --- a/packages/core/src/runtime/rewrite.ts +++ b/packages/core/src/runtime/rewrite.ts @@ -1,9 +1,9 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones -import { Turn } from "./actor.js"; +import { INERT_CAP, Turn } from "./actor.js"; import { Bytes, Dictionary, DoubleFloat, embed, IdentityMap, is, isEmbedded, Record, SingleFloat, Tuple, stringify } from "@preserves/core"; -import type { Assertion, Handle, Ref } from "./actor.js"; +import type { Assertion, Cap, Handle, Ref } from "./actor.js"; import type { SturdyValue } from "../transport/sturdy.js"; import { @@ -68,7 +68,7 @@ export function match(p: Pattern, v: Assertion): Bindings | null { return is(p.value.value, v); case 'PCompound': switch (p.value._variant) { case 'rec': { - if (!Record.isRecord, Ref>(v)) return false; + if (!Record.isRecord, Cap>(v)) return false; if (!is(p.value.label, v.label)) return false; if (p.value.fields.length !== v.length) return false; let index = 0; @@ -88,7 +88,7 @@ export function match(p: Pattern, v: Assertion): Bindings | null { return true; } case 'dict':{ - if (!Dictionary.isDictionary(v)) return false; + if (!Dictionary.isDictionary(v)) return false; for (const [key, pp] of p.value.entries.entries()) { const vv = v.get(key); if (vv === void 0) return false; @@ -115,7 +115,7 @@ export function instantiate(t: Template, b: Bindings): Assertion { throw new Error(`Attempt to attenuate non-capability: ${stringify(v)}`); } const r = v.embeddedValue; - return embed(attenuate(r, ... t.value.attenuation)); + return embed(attenuate(r, ... t.value.attenuation) ?? INERT_CAP); } case 'TRef': { const n = t.value.binding; @@ -128,7 +128,7 @@ export function instantiate(t: Template, b: Bindings): Assertion { case 'TCompound': switch (t.value._variant) { case 'rec': { - const v = [] as unknown as Record; + const v = [] as unknown as Record; v.label = t.value.label; t.value.fields.forEach(tt => v.push(walk(tt))); return v; @@ -136,7 +136,7 @@ export function instantiate(t: Template, b: Bindings): Assertion { case 'arr': return t.value.items.map(walk); case 'dict': { - const v = new Dictionary(); + const v = new Dictionary(); t.value.entries.forEach((tt, key) => v.set(key, walk(tt))); return v; } @@ -193,13 +193,20 @@ export function rfilter(... patterns: Pattern[]): Caveat { return ps.length === 1 ? Caveat.Rewrite(ps[0]) : Caveat.Alts(Alts(ps)); } -export function attenuate(ref: Ref, ... a: Caveat[]): Ref { - if (a.length === 0) return ref; - return { ... ref, attenuation: [... (ref.attenuation ?? []), ... a] }; +export function attenuate(cap: Cap, ... a: Caveat[]): Cap | undefined { + if (a.length === 0) return cap; + const ref = cap.toRef(); + if (ref === void 0) return void 0; + const newCap: Cap & Ref = { + ... ref, + attenuation: [... (ref.attenuation ?? []), ... a], + toRef(): Ref | undefined { return this; } + }; + return newCap; } -export function forwarder(ref: Ref): { proxy: Ref, revoker: Ref } { - let underlying: Ref | null = ref; +export function forwarder(cap: Cap): { proxy: Cap, revoker: Cap } { + let underlying: Cap | null = cap; let handleMap = new IdentityMap(); let proxy = Turn.ref({ assert(assertion: Assertion, handle: Handle): void { @@ -215,7 +222,7 @@ export function forwarder(ref: Ref): { proxy: Ref, revoker: Ref } { if (underlying === null) return; Turn.active.message(underlying, body); }, - sync(peer: Ref): void { + sync(peer: Cap): void { if (underlying === null) return; Turn.active._sync(underlying, peer); }, diff --git a/packages/core/src/runtime/skeleton.ts b/packages/core/src/runtime/skeleton.ts index 7fc7bed..c8596b0 100644 --- a/packages/core/src/runtime/skeleton.ts +++ b/packages/core/src/runtime/skeleton.ts @@ -2,7 +2,7 @@ /// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones import { Set, Dictionary, KeyedDictionary, IdentitySet, stringify, Value } from '@preserves/core'; -import { AnyValue, Assertion, Ref } from './actor.js'; +import { AnyValue, Assertion, Cap } from './actor.js'; import { Bag, ChangeDescription } from './bag.js'; import * as Stack from './stack.js'; import * as P from '../gen/dataspacePatterns.js'; @@ -27,7 +27,7 @@ export interface IndexObserver { } export class Index { - readonly allAssertions: Bag = new Bag(); + readonly allAssertions: Bag = new Bag(); readonly root: Node = new Node(new Continuation(new Set())); addObserver(pattern: P.Pattern, observer: IndexObserver, parameter: T) { @@ -54,7 +54,7 @@ export class Index { } let observerGroup = leaf.observerGroups.get(capturePaths); if (!observerGroup) { - const cachedCaptures = new Bag>(); + const cachedCaptures = new Bag>(); leaf.cachedAssertions.forEach((a) => cachedCaptures._items.update(projectPaths(a, capturePaths), n => n! + 1, 0)); observerGroup = new ObserverGroup(cachedCaptures); @@ -156,7 +156,7 @@ type Selector = [number, AnyValue]; class Node { readonly continuation: Continuation; - readonly edges: KeyedDictionary }, Ref> = new KeyedDictionary(); + readonly edges: KeyedDictionary }, Cap> = new KeyedDictionary(); constructor(continuation: Continuation) { this.continuation = continuation; @@ -266,10 +266,10 @@ class Node { } class Continuation { - readonly cachedAssertions: Set; - readonly leafMap: KeyedDictionary, Dictionary>, Ref> = new KeyedDictionary(); + readonly cachedAssertions: Set; + readonly leafMap: KeyedDictionary, Dictionary>, Cap> = new KeyedDictionary(); - constructor(cachedAssertions: Set) { + constructor(cachedAssertions: Set) { this.cachedAssertions = cachedAssertions; } @@ -285,8 +285,8 @@ class Continuation { } class Leaf { - readonly cachedAssertions: Set = new Set(); - readonly observerGroups: KeyedDictionary, ObserverGroup, Ref> = new KeyedDictionary(); + readonly cachedAssertions: Set = new Set(); + readonly observerGroups: KeyedDictionary, ObserverGroup, Cap> = new KeyedDictionary(); isEmpty(): boolean { return this.cachedAssertions.size === 0 && this.observerGroups.size === 0; @@ -301,10 +301,10 @@ class Leaf { } class ObserverGroup { - readonly cachedCaptures: Bag>; + readonly cachedCaptures: Bag>; readonly observers = new IdentitySet>(); - constructor(cachedCaptures: Bag>) { + constructor(cachedCaptures: Bag>) { this.cachedCaptures = cachedCaptures; } diff --git a/packages/core/src/transport/relay.ts b/packages/core/src/transport/relay.ts index dbc32e1..338a085 100644 --- a/packages/core/src/transport/relay.ts +++ b/packages/core/src/transport/relay.ts @@ -1,7 +1,7 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones -import { Actor, Assertion, Entity, Facet, Handle, Ref, Turn } from '../runtime/actor.js'; +import { Assertion, Cap, Entity, Facet, Handle, INERT_CAP, Turn } from '../runtime/actor.js'; import { BytesLike, Decoder, Dictionary, embed, encode, IdentityMap, mapEmbeddeds, stringify, underlying, Value } from '@preserves/core'; import * as IO from '../gen/protocol.js'; import { wireRefEmbeddedType } from './protocol.js'; @@ -16,25 +16,25 @@ export class WireSymbol { constructor ( public side: Membrane, public oid: IO.Oid, - public ref: Ref, + public cap: Cap, ) {} drop(): void { this.count--; if (this.count === 0) { this.side.byOid.delete(this.oid); - this.side.byRef.delete(this.ref); + this.side.byCap.delete(this.cap); } } }; export class SyncPeerEntity implements Entity { readonly relay: Relay; - readonly peer: Ref; + readonly peer: Cap; readonly handleMap = new IdentityMap(); pins: Array = []; - constructor(relay: Relay, peer: Ref) { + constructor(relay: Relay, peer: Cap) { this.relay = relay; this.peer = peer; } @@ -54,7 +54,7 @@ export class SyncPeerEntity implements Entity { Turn.active.message(this.peer, body); } - sync(peer: Ref): void { + sync(peer: Cap): void { Turn.active._sync(this.peer, peer); } } @@ -88,7 +88,7 @@ export class RelayEntity implements Entity { this.send(IO.Event.Message(IO.Message(this.relay.register(null, body, null)))); } - sync(peer: Ref): void { + sync(peer: Cap): void { const peerEntity = new SyncPeerEntity(this.relay, peer); this.relay.grabImportedOid(this.oid, peerEntity.pins); const ior = this.relay.rewriteRefOut(Turn.ref(peerEntity), false, peerEntity.pins); @@ -96,11 +96,11 @@ export class RelayEntity implements Entity { } } -export type WhichTable = "byOid" | "byRef"; +export type WhichTable = "byOid" | "byCap"; export class Membrane { readonly byOid = new IdentityMap(); - readonly byRef = new IdentityMap(); + readonly byCap = new IdentityMap(); grab(table: Table, key: Parameters[0], @@ -118,7 +118,7 @@ export class Membrane { if (e === void 0) { if (f === null) return null; e = f(); - this.byRef.set(e.ref, e); + this.byCap.set(e.cap, e); this.byOid.set(e.oid, e); } if (!transient) e.count++; @@ -126,11 +126,6 @@ export class Membrane { } } -export const INERT_REF: Ref = { - relay: Actor.boot(() => Turn.active.stop()).root, - target: {}, -}; - export type PacketWriter = (bs: Uint8Array) => void; export interface RelayOptions { @@ -139,13 +134,13 @@ export interface RelayOptions { debug?: boolean; trustPeer?: boolean; initialOid?: IO.Oid; - initialRef?: Ref; + initialCap?: Cap; nextLocalOid?: IO.Oid; } export class Relay { readonly facet: Facet; - readonly selfRef: Ref; + readonly selfCap: Cap; readonly w: PacketWriter; readonly inboundAssertions = new IdentityMap>(); readonly exported = new Membrane(); readonly imported = new Membrane(); - readonly peer: Ref | null; + readonly peer: Cap | null; nextLocalOid: IO.Oid = 0; pendingTurn: IO.Turn = []; debug: boolean; @@ -167,7 +162,7 @@ export class Relay { constructor(options: RelayOptions) { this.facet = Turn.activeFacet; - this.selfRef = Turn.ref(this); + this.selfCap = Turn.ref(this); this.w = options.packetWriter; this.debug = options.debug ?? false; this.trustPeer = options.trustPeer ?? true; @@ -175,8 +170,8 @@ export class Relay { this.facet.preventInertCheck(); options.setup(this); - if (options.initialRef !== void 0) { - this.rewriteRefOut(options.initialRef, false, []); + if (options.initialCap !== void 0) { + this.rewriteRefOut(options.initialCap, false, []); } this.peer = (options.initialOid !== void 0) @@ -216,59 +211,62 @@ export class Relay { pins.push(e); } - grabExportedOid(oid: IO.Oid, pins: Array): Ref { + grabExportedOid(oid: IO.Oid, pins: Array): Cap { const e = this.exported.grab("byOid", oid, false, null); - if (e === null) return INERT_REF; + if (e === null) return INERT_CAP; pins.push(e); - return e.ref; + return e.cap; } - rewriteRefOut(r: Ref, transient: boolean, pins: Array): WireRef { - if (r.target instanceof RelayEntity && r.target.relay === this) { - if (r.attenuation === void 0 || r.attenuation.length === 0) { - // No extra conditions on this reference since it was sent to us. - this.grabImportedOid(r.target.oid, pins); - return WireRef.yours({ oid: r.target.oid, attenuation: [] }); - } else { - // This reference has been attenuated since it was sent to us. - // Do we trust the peer to enforce such attenuation on our behalf? - if (this.trustPeer) { + rewriteRefOut(c: Cap, transient: boolean, pins: Array): WireRef { + const r = c.toRef(); + if (r !== void 0) { + if (r.target instanceof RelayEntity && r.target.relay === this) { + if (r.attenuation === void 0 || r.attenuation.length === 0) { + // No extra conditions on this reference since it was sent to us. this.grabImportedOid(r.target.oid, pins); - return WireRef.yours({ oid: r.target.oid, attenuation: r.attenuation }); + return WireRef.yours({ oid: r.target.oid, attenuation: [] }); } else { - // fall through: treat the attenuated ref as a local ref, and re-export it. + // This reference has been attenuated since it was sent to us. + // Do we trust the peer to enforce such attenuation on our behalf? + if (this.trustPeer) { + this.grabImportedOid(r.target.oid, pins); + return WireRef.yours({ oid: r.target.oid, attenuation: r.attenuation }); + } else { + // fall through: treat the attenuated ref as a local ref, and re-export it. + } } } } const e = this.exported.grab( - "byRef", r, transient, () => { + "byCap", c, transient, () => { if (transient) throw new Error("Cannot send transient reference"); - return new WireSymbol(this.exported, this.nextLocalOid++, r); + return new WireSymbol(this.exported, this.nextLocalOid++, c); }); pins.push(e); return WireRef.mine(e.oid); } - rewriteRefIn(n: WireRef, pins: Array): Ref { + rewriteRefIn(n: WireRef, pins: Array): Cap { switch (n._variant) { case 'yours': { const e = this.exported.grab("byOid", n.oid, false, null); if (e === null) { - return INERT_REF; + return INERT_CAP; } else { pins.push(e); - const r = e.ref; + const c = e.cap; if (n.attenuation.length === 0) { - return r; + return c; } else { - type AttenuatedRef = Ref & { __attenuations?: Dictionary }; - const ar = r as AttenuatedRef; - if (ar.__attenuations === void 0) { - ar.__attenuations = new Dictionary(); + type AttenuatedCap = Cap & { __attenuations?: Dictionary }; + const ac = c as AttenuatedCap; + if (ac.__attenuations === void 0) { + ac.__attenuations = new Dictionary(); } - return ar.__attenuations.getOrSet(n.attenuation.map(fromCaveat), () => - attenuate(r, ... n.attenuation)); + return ac.__attenuations.getOrSet(n.attenuation.map(fromCaveat), () => + attenuate(c, ... n.attenuation) ?? INERT_CAP); } } } @@ -276,7 +274,7 @@ export class Relay { const e = this.imported.grab("byOid", n.oid, false, () => new WireSymbol(this.imported, n.oid, Turn.ref(new RelayEntity(this, n.oid)))); pins.push(e); - return e.ref; + return e.cap; } } } @@ -294,7 +292,7 @@ export class Relay { send(remoteOid: IO.Oid, m: IO.Event): void { if (this.pendingTurn.length === 0) { - Turn.active.message(this.selfRef, FLUSH); + Turn.active.message(this.selfCap, FLUSH); } this.pendingTurn.push(IO.TurnEvent({ oid: remoteOid, event: m })); } @@ -343,8 +341,8 @@ export class Relay { case 'Message': { const [a, pins] = this.rewriteIn(m.value.body); if (pins.length > 0) throw new Error("Cannot receive transient reference"); - const r = this.exported.byOid.get(localOid)?.ref; - if (r) Turn.active.message(r, a); + const c = this.exported.byOid.get(localOid)?.cap; + if (c) Turn.active.message(c, a); break; } case 'Sync': { diff --git a/packages/create/template/.dir-locals.el b/packages/create/template/.dir-locals.el deleted file mode 100644 index 70ecb07..0000000 --- a/packages/create/template/.dir-locals.el +++ /dev/null @@ -1,22 +0,0 @@ -((typescript-mode - . ((eval - . (progn - ;; For TIDE: - (setq tide-tsserver-executable - (concat - (let ((d (dir-locals-find-file "."))) - (if (stringp d) d (car d))) - "node_modules/typescript/lib/tsserver.js")) - ;; For LSP: - (require 'lsp-javascript) - (let ((node-modules (concat - (let ((d (dir-locals-find-file "."))) - (if (stringp d) d (car d))) - "node_modules/"))) - (lsp-dependency 'typescript-language-server - `(:system ,(concat node-modules - "typescript-language-server/lib/cli.mjs"))) - (lsp-dependency 'typescript - `(:system ,(concat node-modules - "typescript/lib/tsserver.js"))))) - )))) diff --git a/packages/create/template/src/index.ts b/packages/create/template/src/index.ts index f24c187..ca01f3e 100644 --- a/packages/create/template/src/index.ts +++ b/packages/create/template/src/index.ts @@ -1,7 +1,7 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2023 Tony Garnock-Jones -import { Dataspace, Ref, Sturdy, Reader, Schemas, Embedded, randomId, fromJS } from "@syndicate-lang/core"; +import { Dataspace, Cap, Sturdy, Reader, Schemas, Embedded, randomId, fromJS } from "@syndicate-lang/core"; import html from "@syndicate-lang/html"; import wsRelay from "@syndicate-lang/ws-relay"; import { ExampleDefinition } from './gen/example'; @@ -15,7 +15,7 @@ export function main() { }); } -function bootApp(ds: Ref) { +function bootApp(ds: Cap) { spawn named 'app' { at ds { /* @@ -26,11 +26,11 @@ function bootApp(ds: Ref) { const this_instance = randomId(16); - const route = G.Route({ + const route = G.Route({ "transports": [fromJS(Schemas.transportAddress.WebSocket( `ws://${document.location.hostname}:9001/`))], "pathSteps": [G.asPathStep(fromJS(Sturdy.asSturdyRef( - new Reader( + new Reader( '').next())))], }); diff --git a/packages/fs/src/index.ts b/packages/fs/src/index.ts index e4be83e..7ce64f8 100644 --- a/packages/fs/src/index.ts +++ b/packages/fs/src/index.ts @@ -1,7 +1,7 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2023 Tony Garnock-Jones -import { Bytes, Observe, Ref, Turn, stringify } from "@syndicate-lang/core"; +import { Bytes, Observe, Cap, Turn, stringify } from "@syndicate-lang/core"; import { QuasiValue as Q } from "@syndicate-lang/core"; import { service } from '@syndicate-lang/service'; import { Contents, File, asConfig, Config, Encoding, toEncoding } from './gen/fs.js'; @@ -20,7 +20,7 @@ export function main(_argv: string[]) { }); } -export function serve(config: Config) { +export function serve(config: Config) { at config.core.dataspace { during Observe({ "pattern": :pattern File({ @@ -38,7 +38,7 @@ export function serve(config: Config) { } } -function trackFile(config: Config, relativePath: string, encoding: Encoding) { +function trackFile(config: Config, relativePath: string, encoding: Encoding) { const facet = Turn.activeFacet; const absolutePath = path.resolve(config.core.path, relativePath); diff --git a/packages/html/src/index.ts b/packages/html/src/index.ts index ab29004..6384b18 100644 --- a/packages/html/src/index.ts +++ b/packages/html/src/index.ts @@ -1,7 +1,7 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones -import { randomId, Observe, FlexMap, embed, Embedded, Ref, Turn, AnyValue } from "@syndicate-lang/core"; +import { randomId, Observe, FlexMap, embed, Embedded, Cap, Turn, AnyValue } from "@syndicate-lang/core"; import { QuasiValue as Q } from "@syndicate-lang/core"; import * as P from "./protocol"; @@ -11,7 +11,7 @@ export type UIFragmentRecord = ReturnType; import { HtmlFragments } from "./html"; export * from "./html"; -export function boot(ds: Ref) { +export function boot(ds: Cap) { spawnGlobalEventFactory(ds); spawnWindowEventFactory(ds); spawnUIFragmentFactory(ds); @@ -34,7 +34,7 @@ export function newFragmentId() { //--------------------------------------------------------------------------- -export function spawnGlobalEventFactory(ds: Ref) { +export function spawnGlobalEventFactory(ds: Cap) { spawn named 'GlobalEventFactory' { at ds { during Observe({ @@ -68,7 +68,7 @@ export function spawnGlobalEventFactory(ds: Ref) { } } -export function spawnWindowEventFactory(ds: Ref) { +export function spawnWindowEventFactory(ds: Cap) { spawn named 'WindowEventFactory' { at ds { during Observe({ @@ -111,7 +111,7 @@ function isNodeOrderKey(x: any): x is NodeOrderKey { type HandlerClosure = (event: Event) => void; -function spawnUIFragmentFactory(ds: Ref) { +function spawnUIFragmentFactory(ds: Cap) { type RegistrationKey = [string, string]; // [selector, eventType] spawn named 'UIFragmentFactory' { @@ -149,7 +149,7 @@ function spawnUIFragmentFactory(ds: Ref) { removeNodes(); selector = newSelector; - html = (newHtml as Embedded).embeddedValue.target.data as ChildNode[]; + html = (newHtml as Embedded).embeddedValue.toRef()!.target.data as ChildNode[]; orderBy = newOrderBy; anchorNodes = (selector !== null) ? selectorMatch(document.body, selector) : []; @@ -320,7 +320,7 @@ function configureNode(n: ChildNode) { //--------------------------------------------------------------------------- -function spawnUIAttributeFactory(ds: Ref) { +function spawnUIAttributeFactory(ds: Cap) { spawn named 'UIAttributeFactory' { at ds { during P.UIAttribute($selector: string, $attribute: string, $value) => @@ -331,7 +331,7 @@ function spawnUIAttributeFactory(ds: Ref) { } } -function spawnUIPropertyFactory(ds: Ref) { +function spawnUIPropertyFactory(ds: Cap) { spawn named 'UIPropertyFactory' { at ds { during P.UIProperty($selector: string, $property: string, $value) => @@ -417,7 +417,7 @@ function splitClassValue(v: string | null): Array { //--------------------------------------------------------------------------- -function spawnUIChangeablePropertyFactory(ds: Ref) { +function spawnUIChangeablePropertyFactory(ds: Cap) { spawn named 'UIChangeablePropertyFactory' { at ds { during Observe({ @@ -513,7 +513,7 @@ export class Anchor { //--------------------------------------------------------------------------- -function spawnLocationHashTracker(ds: Ref) { +function spawnLocationHashTracker(ds: Cap) { spawn named 'LocationHashTracker' { at ds { field hashValue: string = '/'; @@ -542,7 +542,7 @@ function spawnLocationHashTracker(ds: Ref) { //--------------------------------------------------------------------------- -function spawnAttributeUpdater(ds: Ref) { +function spawnAttributeUpdater(ds: Cap) { spawn named 'AttributeUpdater' { at ds { on message P.SetAttribute($s: string, $k: string, $v: string) => diff --git a/packages/service/src/index.ts b/packages/service/src/index.ts index 4053b4c..8b82851 100644 --- a/packages/service/src/index.ts +++ b/packages/service/src/index.ts @@ -25,7 +25,7 @@ export function service(handler: (args: AnyValue) => void, options?: ServiceOpti process.stdin.on('close', () => facet.turn(() => { stop {} })); process.stdin.on('end', () => facet.turn(() => { stop {} })); }, - initialRef: Turn.ref(assertionFacetObserver(handler)), + initialCap: Turn.ref(assertionFacetObserver(handler)), }, options ?? {})); }); } diff --git a/packages/timer/src/index.ts b/packages/timer/src/index.ts index 87921be..139ab35 100644 --- a/packages/timer/src/index.ts +++ b/packages/timer/src/index.ts @@ -1,7 +1,7 @@ /// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones -import { preserves, DoubleFloat, Observe, floatValue, Turn, Ref, Schemas } from "@syndicate-lang/core"; +import { preserves, Cap, DoubleFloat, Observe, floatValue, Turn, Schemas } from "@syndicate-lang/core"; import { QuasiValue as Q } from "@syndicate-lang/core"; export message type PeriodicTick(interval: DoubleFloat); @@ -9,7 +9,7 @@ export message type PeriodicTick(interval: DoubleFloat); export const LaterThan = Schemas.timer.LaterThan; export type LaterThan = Schemas.timer.LaterThan; -export function sleep(ds: Ref, seconds: number, cb: () => void): void { +export function sleep(ds: Cap, seconds: number, cb: () => void): void { react { at ds { const deadline = +(new Date()) / 1000.0 + seconds; @@ -20,7 +20,7 @@ export function sleep(ds: Ref, seconds: number, cb: () => void): void { } } -export function boot(ds: Ref) { +export function boot(ds: Cap) { spawn named 'timer/PeriodicTick' { at ds { during Observe({ diff --git a/packages/ws-relay/src/index.ts b/packages/ws-relay/src/index.ts index 53d02e2..977d29f 100644 --- a/packages/ws-relay/src/index.ts +++ b/packages/ws-relay/src/index.ts @@ -4,12 +4,12 @@ import { Assertion, Bytes, + Cap, Dataflow, Embedded, IdentitySet, Observe, QuasiValue as Q, - Ref, Relay, Schemas, Supervisor, @@ -29,11 +29,11 @@ import * as SaltyCrypto from 'salty-crypto'; type TransportState = { addr: T.WebSocket, - control: Ref, - peer: Ref, + control: Cap, + peer: Cap, }; -export function boot(ds: Ref, debug: boolean = false) { +export function boot(ds: Cap, debug: boolean = false) { spawn named 'transportConnector' { at ds { during Observe({ "pattern": :pattern G.TransportConnection({ @@ -86,7 +86,7 @@ export function boot(ds: Ref, debug: boolean = false) { }); console.log('succeed', addr.url); at ds { - assert G.TransportConnection({ + assert G.TransportConnection({ "addr": fromJS(addr), "control": create controlEntity, "resolved": G.Resolved.accepted(relay.peer!), @@ -98,7 +98,7 @@ export function boot(ds: Ref, debug: boolean = false) { final = true; console.log('fail', addr.url, detail); at ds { - assert G.TransportConnection({ + assert G.TransportConnection({ "addr": fromJS(addr), "control": create controlEntity, "resolved": G.Resolved.Rejected(G.Rejected(detail)), @@ -152,7 +152,7 @@ export function boot(ds: Ref, debug: boolean = false) { } }); field best: TransportState | null = null; - field rootPeer: Ref | null = null; + field rootPeer: Cap | null = null; dataflow { best.value = null; for (const c of candidates.value) { @@ -163,7 +163,7 @@ export function boot(ds: Ref, debug: boolean = false) { } resolve(() => rootPeer.value, route.pathSteps, (r) => { console.log('leaf', best.value?.addr?.url); - assert G.ResolvePath({ + assert G.ResolvePath({ "route": route, "addr": fromJS(best.value!.addr), "control": best.value!.control, @@ -175,7 +175,7 @@ export function boot(ds: Ref, debug: boolean = false) { } function resolve( - e: () => Ref | null, + e: () => Cap | null, steps: G.PathStep[], k: (r: () => G.Resolved | null) => void, ) { @@ -221,7 +221,7 @@ export function boot(ds: Ref, debug: boolean = false) { const detail0 = Q.drop_lit(detailPatValue, N.toNoisePathStepDetail); if (!detail0) return; - const spec: N.NoiseSpec = detail0; + const spec: N.NoiseSpec = detail0; const algorithms = SaltyCrypto.Noise_25519_ChaChaPoly_BLAKE2s; const protocol = @@ -247,7 +247,7 @@ export function boot(ds: Ref, debug: boolean = false) { }); let transportState: SaltyCrypto.TransportState | null = null; - let responderSession: Ref | null = null; + let responderSession: Cap | null = null; let relay: Relay.Relay | null = null; function maybeTransition(s: SaltyCrypto.TransportState | null) { @@ -273,7 +273,7 @@ export function boot(ds: Ref, debug: boolean = false) { }, initialOid: 0, }).peer!; - assert G.ResolvedPathStep({ + assert G.ResolvedPathStep({ "origin": origin, "pathStep": G.PathStep({ "stepType": N.$noise, @@ -307,7 +307,7 @@ export function boot(ds: Ref, debug: boolean = false) { case "Rejected": stop { at ds { - assert G.ResolvedPathStep({ + assert G.ResolvedPathStep({ "origin": origin, "pathStep": G.PathStep({ "stepType": N.$noise, @@ -374,7 +374,7 @@ export function boot(ds: Ref, debug: boolean = false) { const response = G.toResolved(e); if (!response) return; at ds { - assert G.ResolvedPathStep({ + assert G.ResolvedPathStep({ "origin": origin, "pathStep": G.PathStep({ "stepType": S.$ref,