diff --git a/actor.ts b/actor.ts index b556782..7eff810 100644 --- a/actor.ts +++ b/actor.ts @@ -1,4 +1,15 @@ -import { IdentitySet, Value } from 'preserves'; +import { + Dictionary, + IdentitySet, + Record, + Value, + fold, + is, + mapPointers, + unannotate, +} from 'preserves'; + +//--------------------------------------------------------------------------- export type Assertion = Value; export type Handle = number; @@ -12,7 +23,44 @@ export interface Entity { sync(turn: Turn, peer: Ref): void; } -export interface Ref { readonly relay: Actor, readonly target: Partial }; +export interface Ref { + readonly relay: Actor; + readonly target: Partial; + readonly attenuation?: Attenuation; +} + +export type Attenuation = Array; +export type Rewrite = { pattern: Pattern, template: Template }; + +export type PBind = { type: 'bind', name: string, pattern: Pattern }; +export type PatternPointer = + | Ref + | { type: 'discard' } + | PBind + | { type: 'and', patterns: Array } + | { type: 'or', patterns: Array } + | { type: 'not', pattern: Pattern }; +export type Pattern = Value; + +export type TemplatePointer = Ref | { type: 'ref', name: string }; +export type Template = Value; + +export type Bindings = { [name: string]: Assertion }; + +//--------------------------------------------------------------------------- + +export function isRef(v: any): v is Ref { + return 'relay' in v && v.relay instanceof Actor && 'target' in v; +} + +export const pdiscard: Pattern = { type: 'discard' }; +export const pbind = (name: string, pattern: Pattern = pdiscard): Pattern => + ({ type: 'bind', name, pattern }); +export const pand = (... patterns: Pattern[]): Pattern => ({ type: 'and', patterns }); +export const por = (... patterns: Pattern[]): Pattern => ({ type: 'or', patterns }); +export const pnot = (pattern: Pattern): Pattern => ({ type: 'not', pattern }); + +export const tref = (name: string): Template => ({ type: 'ref', name }); export class Actor { readonly outbound: Map; @@ -84,14 +132,21 @@ export class Turn { assert(ref: Ref, assertion: Assertion): Handle { const h = nextHandle++; this.enqueue(ref.relay, t => { - this.actor.outbound.set(h, ref); - ref.target.assert?.(t, assertion, h); + const a = ref.attenuation === void 0 + ? assertion + : runRewrites(ref.attenuation, assertion); + if (a !== null) { + this.actor.outbound.set(h, ref); + ref.target.assert?.(t, a, h); + } }); return h; } retract(h: Handle): void { - this._retract(this.actor.outbound.get(h)!, h); + const peer = this.actor.outbound.get(h); + if (peer === void 0) return; + this._retract(peer, h); } replace(ref: Ref, h: Handle | undefined, assertion: Assertion): Handle { @@ -113,7 +168,10 @@ export class Turn { } message(ref: Ref, assertion: Assertion): void { - this.enqueue(ref.relay, t => ref.target.message?.(t, assertion)); + const a = ref.attenuation === void 0 + ? assertion + : runRewrites(ref.attenuation, assertion); + if (a !== null) this.enqueue(ref.relay, t => ref.target.message?.(t, assertion)); } enqueue(relay: Actor, a: LocalAction): void { @@ -121,37 +179,102 @@ export class Turn { } } -export type AttenuationFilter = (assertion: Assertion) => Assertion | null; +//--------------------------------------------------------------------------- -export class Attenuation implements Entity { - readonly target: Partial; - readonly filter: AttenuationFilter; +export function match(p: Pattern, v: Assertion): Bindings | null { + const bindings: Bindings = {}; - constructor(target: Partial, filter: AttenuationFilter) { - this.target = target; - this.filter = filter; + function walk(p: Pattern, v: Assertion): boolean { + return fold(p, { + boolean(_b) { return is(p, v); }, + single(_f) { return is(p, v); }, + double(_f) { return is(p, v); }, + integer(_i) { return is(p, v); }, + string(_s) { return is(p, v); }, + bytes(_b) { return is(p, v); }, + symbol(_s) { return is(p, v); }, + record(r) { + v = unannotate(v); + return Record.isRecord(v) + && r.length === v.length + && walk(r.label, v.label) + && r.every((pp, i) => walk(pp, (v as Record)[i])); + }, + array(a) { + v = unannotate(v); + return Array.isArray(v) + && a.length === v.length + && a.every((pp, i) => + walk(pp, (v as Array>)[i])); + }, + set(_s) { return false; }, // TODO: set patterns + dictionary(d) { + v = unannotate(v); + if (!Dictionary.isDictionary(v)) return false; + if (d.size !== v.size) return false; + for (const pe of d.entries()) { + const [pk, pv] = pe; + if (!v.has(pk)) return false; + if (!walk(pv, v.get(pk)!)) return false; + } + return true; + }, + annotated(a) { return walk(a.item, v); }, + pointer(t) { + if ('type' in t) { + switch (t.type) { + case 'bind': + bindings[t.name] = v; + return walk(t.pattern, v); + case 'discard': + return true; + case 'not': + return !walk(t.pattern, v); + case 'and': + for (const p of t.patterns) { + if (!walk(p, v)) return false; + } + return true; + case 'or': + for (const p of t.patterns) { + if (walk(p, v)) return true; + } + return false; + } + } else { + if (!isRef(v)) return false; + return (t.relay === v.relay) && (t.target === v.target); + } + }, + }); } - assert(turn: Turn, assertion: Assertion, handle: Handle): void { - const filtered = this.filter(assertion); - if (filtered !== null) this.target.assert?.(turn, filtered, handle); - } - - retract(turn: Turn, handle: Handle): void { - // TODO: consider whether we want targets to even see blocked handles - this.target.retract?.(turn, handle); - } - - message(turn: Turn, body: Assertion): void { - const filtered = this.filter(body); - if (filtered !== null) this.target.message?.(turn, filtered); - } - - sync(turn: Turn, peer: Ref): void { - _sync(turn, this.target, peer); - } + return walk(p, v) ? bindings : null; } -export function attenuate(ref: Ref, filter: AttenuationFilter): Ref { - return { relay: ref.relay, target: new Attenuation(ref.target, filter) }; +export function instantiate(t: Template, b: Bindings): Assertion { + return mapPointers(t, p => ('type' in p) ? b[p.name] : p); +} + +export function rewrite(r: Rewrite, v: Assertion): Assertion | null { + const bindings = match(r.pattern, v); + if (bindings === null) return null; + return instantiate(r.template, bindings); +} + +export function runRewrites(a: Attenuation, v: Assertion): Assertion | null { + for (const r of a) { + const w = rewrite(r, v); + if (w === null) return null; + v = w; + } + return v; +} + +export function rfilter(... patterns: Value>[]): Rewrite { + return { pattern: pbind('a', por(... (patterns as Pattern[]))), template: tref('a') }; +} + +export function attenuate(ref: Ref, ... a: Attenuation): Ref { + return { ... ref, attenuation: [... a, ... (ref.attenuation ?? [])] }; } diff --git a/main.ts b/main.ts index 699818a..9acd0aa 100644 --- a/main.ts +++ b/main.ts @@ -1,8 +1,18 @@ -import { Actor, Assertion, attenuate, Entity, Handle, Ref, Turn } from './actor.js'; -import { Dictionary, IdentityMap, is, Record, RecordConstructor } from 'preserves'; +import { + Actor, + Assertion, + Entity, + Handle, + Ref, + Turn, + attenuate, + pdiscard, + rfilter, +} from './actor.js'; +import { Dictionary, IdentityMap, is, Record } from 'preserves'; import { Bag, ChangeDescription } from './bag'; -const Observe = Record.makeConstructor('Observe', ['label', 'observer']); +const Observe = Record.makeConstructor('Observe', ['label', 'observer']); class Dataspace implements Partial { readonly handleMap: IdentityMap> = new IdentityMap(); @@ -54,8 +64,8 @@ class Dataspace implements Partial { //--------------------------------------------------------------------------- -const BoxState = Record.makeConstructor('BoxState', ['value']); -const SetBox = Record.makeConstructor('SetBox', ['newValue']); +const BoxState = Record.makeConstructor('BoxState', ['value']); +const SetBox = Record.makeConstructor('SetBox', ['newValue']); let startTime = Date.now(); let prevValue = 0; @@ -114,20 +124,16 @@ function spawnClient(t: Turn, ds: Ref) { }); } -function isObserverOf(a: Assertion, ctor: RecordConstructor): boolean { - return Observe.isClassOf(a) && is(Observe._.label(a), ctor.constructorInfo.label); -} - Turn.for(new Actor(), async (t: Turn) => { const ds = t.ref(new Dataspace()); - spawnBox(t, attenuate(ds, a => { - if (BoxState.isClassOf(a)) return a; - if (isObserverOf(a, SetBox)) return a; - return null; - })); - spawnClient(t, attenuate(ds, a => { - if (SetBox.isClassOf(a)) return a; - if (isObserverOf(a, BoxState)) return a; - return null; - })); + + spawnBox(t, attenuate( + ds, + rfilter(BoxState(pdiscard), + Observe(SetBox.constructorInfo.label, pdiscard)))); + + spawnClient(t, attenuate( + ds, + rfilter(SetBox(pdiscard), + Observe(BoxState.constructorInfo.label, pdiscard)))); });