diff --git a/actor.ts b/actor.ts index eadaa9c..4fb69c8 100644 --- a/actor.ts +++ b/actor.ts @@ -3,10 +3,7 @@ import { IdentitySet, Record, Value, - fold, is, - mapPointers, - unannotate, } from 'preserves'; //--------------------------------------------------------------------------- @@ -33,16 +30,68 @@ export type Attenuation = Array; // array of stages, each a list o export type RewriteStage = Array; export type Rewrite = { pattern: Pattern, template: Template }; -export type PatternPointer = - | Ref - | { type: 'discard' } - | { type: 'bind', name: string, pattern: Pattern } - | { type: 'and', patterns: Array } - | { type: 'not', pattern: Pattern }; -export type Pattern = Value; +export const _CRec = Symbol.for('rec'); +export const CRec = Record.makeConstructor<{label: Assertion, arity: number}, Ref>()( + _CRec, ['label', 'arity']); +export type CRec = ReturnType; -export type TemplatePointer = Ref | { type: 'ref', name: string }; -export type Template = Value; +export const _CArr = Symbol.for('arr'); +export const CArr = Record.makeConstructor<{arity: number}, Ref>()( + _CArr, ['arity']); +export type CArr = ReturnType; + +export const _CDict = Symbol.for('dict'); +export const CDict = Record.makeConstructor<{}, Ref>()( + _CDict, []); +export type CDict = ReturnType; + +export type ConstructorSpec = CRec | CArr | CDict; + +export const _PDiscard = Symbol.for('_'); +export const PDiscard = Record.makeConstructor<{}, Ref>()( + _PDiscard, []); +export type PDiscard = ReturnType; + +export const _PBind = Symbol.for('bind'); +export const PBind = Record.makeConstructor<{name: string, pattern: Assertion}, Ref>()( + _PBind, ['name', 'pattern']); +export type PBind = ReturnType; + +export const _PAnd = Symbol.for('and'); +export const PAnd = Record.makeConstructor<{patterns: Array}, Ref>()( + _PAnd, ['patterns']); +export type PAnd = ReturnType; + +export const _PNot = Symbol.for('not'); +export const PNot = Record.makeConstructor<{pattern: Assertion}, Ref>()( + _PNot, ['pattern']); +export type PNot = ReturnType; + +export const _Lit = Symbol.for('lit'); +export const Lit = Record.makeConstructor<{value: Assertion}, Ref>()( + _Lit, ['value']); +export type Lit = ReturnType; + +export const _PCompound = Symbol.for('compound'); +export const PCompound = + Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary}, Ref>()( + _PCompound, ['ctor', 'members']); +export type PCompound = ReturnType; + +export type Pattern = PDiscard | PBind | PAnd | PNot | Lit | PCompound; + +export const _TRef = Symbol.for('ref'); +export const TRef = Record.makeConstructor<{name: string}, Ref>()( + _TRef, ['name']); +export type TRef = ReturnType; + +export const _TCompound = Symbol.for('compound'); +export const TCompound = + Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary}, Ref>()( + _TCompound, ['ctor', 'members']); +export type TCompound = ReturnType; + +export type Template = TRef | Lit | TCompound; export type Bindings = { [name: string]: Assertion }; @@ -52,14 +101,6 @@ 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 pnot = (pattern: Pattern): Pattern => ({ type: 'not', pattern }); - -export const tref = (name: string): Template => ({ type: 'ref', name }); - export class Actor { readonly outbound: Map; exitReason: ExitReason = null; @@ -179,78 +220,110 @@ export function match(p: Pattern, v: Assertion): Bindings | null { let bindings: Bindings = {}; 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; + switch (p.label) { + case _PDiscard: + return true; + case _PBind: + if (walk(PBind._.pattern(p) as Pattern, v)) { + bindings[PBind._.name(p)] = v; + return true; + } + return false; + case _PAnd: + for (const pp of PAnd._.patterns(p) as Pattern[]) { + if (!walk(pp, v)) return false; } return true; - }, - annotated(a) { return walk(a.item, v); }, - pointer(t) { - if ('type' in t) { - switch (t.type) { - case 'bind': - if (walk(t.pattern, v)) { - bindings[t.name] = v; - return true; - } - return false; - case 'discard': - return true; - case 'not': { - const savedBindings = bindings; - bindings = {}; - const result = !walk(t.pattern, v) - bindings = savedBindings; - return result; + case _PNot: { + const savedBindings = bindings; + bindings = {}; + const result = !walk(PNot._.pattern(p) as Pattern, v) + bindings = savedBindings; + return result; + } + case _Lit: + return is(Lit._.value(p), v); + case _PCompound: { + const ctor = PCompound._.ctor(p); + const members = PCompound._.members(p) as Dictionary; + switch (ctor.label) { + case _CRec: + if (!Record.isRecord(v)) return false; + if (!is(CRec._.label(ctor), v.label)) return false; + if (CRec._.arity(ctor) !== v.length) return false; + for (const [key, pp] of members) { + if (typeof key !== 'number') return false; + if (!walk(pp, v[key])) return false; } - case 'and': - for (const p of t.patterns) { - if (!walk(p, v)) return false; - } - return true; - } - } else { - if (!isRef(v)) return false; - return (t.relay === v.relay) && (t.target === v.target); + return true; + case _CArr: + if (!Array.isArray(v)) return false; + if ('label' in v) return false; + if (CArr._.arity(ctor) !== v.length) return false; + for (const [key, pp] of members) { + if (typeof key !== 'number') return false; + if (!walk(pp, v[key])) return false; + } + return true; + case _CDict: + if (!Dictionary.isDictionary(v)) return false; + for (const [key, pp] of members) { + const vv = v.get(key); + if (vv === void 0) return false; + if (!walk(pp, vv)) return false; + } + return true; } - }, - }); + } + } } return walk(p, v) ? bindings : null; } export function instantiate(t: Template, b: Bindings): Assertion { - return mapPointers(t, p => ('type' in p) ? b[p.name] : p); + function walk(t: Template): Assertion { + switch (t.label) { + case _TRef: { + const v = b[TRef._.name(t)]; + if (v === void 0) throw new Error(`Unbound reference: ${TRef._.name(t)}`); + return v; + } + case _Lit: + return Lit._.value(t); + case _TCompound: { + const ctor = TCompound._.ctor(t); + const members = TCompound._.members(t) as Dictionary; + switch (ctor.label) { + case _CRec: { + const v = Record(CRec._.label(ctor), []); + v.length = CRec._.arity(ctor); + for (const [key, tt] of members) { + v[key as number] = walk(tt); + } + return v; + } + case _CArr: { + const v = []; + v.length = CArr._.arity(ctor); + for (const [key, tt] of members) { + v[key as number] = walk(tt); + } + return v; + } + case _CDict: { + const v = new Dictionary(); + for (const [key, tt] of members) { + v.set(key, walk(tt)); + } + return v; + } + } + } + } + } + + return walk(t); } export function rewrite(r: Rewrite, v: Assertion): Assertion | null { @@ -279,7 +352,7 @@ export function runRewrites(a: Attenuation | undefined, v: Assertion): Assertion } export function rfilter(... patterns: Pattern[]): RewriteStage { - return patterns.map(p => ({ pattern: pbind('a', p), template: tref('a') })); + return patterns.map(p => ({ pattern: PBind('a', p), template: TRef('a') })); } export function attenuate(ref: Ref, ... a: Attenuation): Ref { diff --git a/dataspace.ts b/dataspace.ts index ed62b95..cf54594 100644 --- a/dataspace.ts +++ b/dataspace.ts @@ -27,15 +27,16 @@ import { Bag, ChangeDescription } from './bag'; // observer-observers and attenuator patterns don't have to deal with // spurious variation. // -export const Observe = Record.makeConstructor('Observe', ['label', 'observer']); +export const Observe = Record.makeConstructor<{label: Assertion, observer: Ref}, Ref>()( + Symbol.for('Observe'), ['label', 'observer']); export class Dataspace implements Partial { - readonly handleMap: IdentityMap> = new IdentityMap(); + readonly handleMap: IdentityMap> = new IdentityMap(); readonly assertions = new Bag(); readonly subscriptions: Dictionary>> = new Dictionary(); assert(turn: Turn, rec: Assertion, handle: Handle): void { - if (!Record.isRecord(rec)) return; + if (!Record.isRecord(rec)) return; this.handleMap.set(handle, rec); if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return; if (Observe.isClassOf(rec)) { @@ -45,7 +46,7 @@ export class Dataspace implements Partial { if (!this.subscriptions.has(label)) this.subscriptions.set(label, new Map()); this.subscriptions.get(label)!.set(observer, seen); this.assertions.forEach((_count, prev) => - is((prev as Record).label, label) + is((prev as Record).label, label) && seen.set(prev, turn.assert(observer, prev))); } this.subscriptions.get(rec.label)?.forEach((seen, peer) => @@ -72,7 +73,7 @@ export class Dataspace implements Partial { } message(turn: Turn, rec: Assertion): void { - if (!Record.isRecord(rec)) return; + if (!Record.isRecord(rec)) return; this.subscriptions.get(rec.label)?.forEach((_seen, peer) => turn.message(peer, rec)); } } diff --git a/main.ts b/main.ts index 6f2177c..8a1bf6c 100644 --- a/main.ts +++ b/main.ts @@ -5,14 +5,19 @@ import { Ref, Turn, attenuate, - pdiscard, rfilter, + PDiscard, + PCompound, + CRec, + Lit, } from './actor.js'; -import { Record } from 'preserves'; +import { Dictionary, Record } from 'preserves'; import { Dataspace, Observe } from './dataspace.js'; -const BoxState = Record.makeConstructor('BoxState', ['value']); -const SetBox = Record.makeConstructor('SetBox', ['newValue']); +const BoxState = Record.makeConstructor<{value: number}, Ref>()( + Symbol.for('BoxState'), ['value']); +const SetBox = Record.makeConstructor<{newValue: number}, Ref>()( + Symbol.for('SetBox'), ['newValue']); let startTime = Date.now(); let prevValue = 0; @@ -76,11 +81,19 @@ Turn.for(new Actor(), async (t: Turn) => { spawnBox(t, attenuate( ds, - rfilter(BoxState(pdiscard), - Observe(SetBox.constructorInfo.label, pdiscard)))); + rfilter(PCompound(CRec(BoxState.constructorInfo.label, + BoxState.constructorInfo.arity), + new Dictionary()), + PCompound(CRec(Observe.constructorInfo.label, + Observe.constructorInfo.arity), + new Dictionary([[0, Lit(SetBox.constructorInfo.label)]]))))); spawnClient(t, attenuate( ds, - rfilter(SetBox(pdiscard), - Observe(BoxState.constructorInfo.label, pdiscard)))); + rfilter(PCompound(CRec(SetBox.constructorInfo.label, + SetBox.constructorInfo.arity), + new Dictionary()), + PCompound(CRec(Observe.constructorInfo.label, + Observe.constructorInfo.arity), + new Dictionary([[0, Lit(BoxState.constructorInfo.label)]]))))); });