Interpreter-based attenuation
This commit is contained in:
parent
f043bc13b9
commit
cf1a3da43d
189
actor.ts
189
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<Ref>;
|
||||
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<Entity> };
|
||||
export interface Ref {
|
||||
readonly relay: Actor;
|
||||
readonly target: Partial<Entity>;
|
||||
readonly attenuation?: Attenuation;
|
||||
}
|
||||
|
||||
export type Attenuation = Array<Rewrite>;
|
||||
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<Pattern> }
|
||||
| { type: 'or', patterns: Array<Pattern> }
|
||||
| { type: 'not', pattern: Pattern };
|
||||
export type Pattern = Value<PatternPointer>;
|
||||
|
||||
export type TemplatePointer = Ref | { type: 'ref', name: string };
|
||||
export type Template = Value<TemplatePointer>;
|
||||
|
||||
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<Handle, Ref>;
|
||||
|
@ -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<Entity>;
|
||||
readonly filter: AttenuationFilter;
|
||||
export function match(p: Pattern, v: Assertion): Bindings | null {
|
||||
const bindings: Bindings = {};
|
||||
|
||||
constructor(target: Partial<Entity>, 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<Ref>(v)
|
||||
&& r.length === v.length
|
||||
&& walk(r.label, v.label)
|
||||
&& r.every((pp, i) => walk(pp, (v as Record<Ref>)[i]));
|
||||
},
|
||||
array(a) {
|
||||
v = unannotate(v);
|
||||
return Array.isArray(v)
|
||||
&& a.length === v.length
|
||||
&& a.every((pp, i) =>
|
||||
walk(pp, (v as Array<Value<Ref>>)[i]));
|
||||
},
|
||||
set(_s) { return false; }, // TODO: set patterns
|
||||
dictionary(d) {
|
||||
v = unannotate(v);
|
||||
if (!Dictionary.isDictionary<Ref>(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<Exclude<PatternPointer, PBind>>[]): 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 ?? [])] };
|
||||
}
|
||||
|
|
44
main.ts
44
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<Ref>('Observe', ['label', 'observer']);
|
||||
const Observe = Record.makeConstructor('Observe', ['label', 'observer']);
|
||||
|
||||
class Dataspace implements Partial<Entity> {
|
||||
readonly handleMap: IdentityMap<Handle, Record<Ref>> = new IdentityMap();
|
||||
|
@ -54,8 +64,8 @@ class Dataspace implements Partial<Entity> {
|
|||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
const BoxState = Record.makeConstructor<Ref>('BoxState', ['value']);
|
||||
const SetBox = Record.makeConstructor<Ref>('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<Ref>): 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))));
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue