Lifted patterns and templates; typed records
This commit is contained in:
parent
99e6b6ab3d
commit
3b2e544f35
239
actor.ts
239
actor.ts
|
@ -3,10 +3,7 @@ import {
|
|||
IdentitySet,
|
||||
Record,
|
||||
Value,
|
||||
fold,
|
||||
is,
|
||||
mapPointers,
|
||||
unannotate,
|
||||
} from 'preserves';
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
@ -33,16 +30,68 @@ export type Attenuation = Array<RewriteStage>; // array of stages, each a list o
|
|||
export type RewriteStage = Array<Rewrite>;
|
||||
export type Rewrite = { pattern: Pattern, template: Template };
|
||||
|
||||
export type PatternPointer =
|
||||
| Ref
|
||||
| { type: 'discard' }
|
||||
| { type: 'bind', name: string, pattern: Pattern }
|
||||
| { type: 'and', patterns: Array<Pattern> }
|
||||
| { type: 'not', pattern: Pattern };
|
||||
export type Pattern = Value<PatternPointer>;
|
||||
export const _CRec = Symbol.for('rec');
|
||||
export const CRec = Record.makeConstructor<{label: Assertion, arity: number}, Ref>()(
|
||||
_CRec, ['label', 'arity']);
|
||||
export type CRec = ReturnType<typeof CRec>;
|
||||
|
||||
export type TemplatePointer = Ref | { type: 'ref', name: string };
|
||||
export type Template = Value<TemplatePointer>;
|
||||
export const _CArr = Symbol.for('arr');
|
||||
export const CArr = Record.makeConstructor<{arity: number}, Ref>()(
|
||||
_CArr, ['arity']);
|
||||
export type CArr = ReturnType<typeof CArr>;
|
||||
|
||||
export const _CDict = Symbol.for('dict');
|
||||
export const CDict = Record.makeConstructor<{}, Ref>()(
|
||||
_CDict, []);
|
||||
export type CDict = ReturnType<typeof CDict>;
|
||||
|
||||
export type ConstructorSpec = CRec | CArr | CDict;
|
||||
|
||||
export const _PDiscard = Symbol.for('_');
|
||||
export const PDiscard = Record.makeConstructor<{}, Ref>()(
|
||||
_PDiscard, []);
|
||||
export type PDiscard = ReturnType<typeof PDiscard>;
|
||||
|
||||
export const _PBind = Symbol.for('bind');
|
||||
export const PBind = Record.makeConstructor<{name: string, pattern: Assertion}, Ref>()(
|
||||
_PBind, ['name', 'pattern']);
|
||||
export type PBind = ReturnType<typeof PBind>;
|
||||
|
||||
export const _PAnd = Symbol.for('and');
|
||||
export const PAnd = Record.makeConstructor<{patterns: Array<Assertion>}, Ref>()(
|
||||
_PAnd, ['patterns']);
|
||||
export type PAnd = ReturnType<typeof PAnd>;
|
||||
|
||||
export const _PNot = Symbol.for('not');
|
||||
export const PNot = Record.makeConstructor<{pattern: Assertion}, Ref>()(
|
||||
_PNot, ['pattern']);
|
||||
export type PNot = ReturnType<typeof PNot>;
|
||||
|
||||
export const _Lit = Symbol.for('lit');
|
||||
export const Lit = Record.makeConstructor<{value: Assertion}, Ref>()(
|
||||
_Lit, ['value']);
|
||||
export type Lit = ReturnType<typeof Lit>;
|
||||
|
||||
export const _PCompound = Symbol.for('compound');
|
||||
export const PCompound =
|
||||
Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary<Assertion>}, Ref>()(
|
||||
_PCompound, ['ctor', 'members']);
|
||||
export type PCompound = ReturnType<typeof PCompound>;
|
||||
|
||||
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<typeof TRef>;
|
||||
|
||||
export const _TCompound = Symbol.for('compound');
|
||||
export const TCompound =
|
||||
Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary<Assertion>}, Ref>()(
|
||||
_TCompound, ['ctor', 'members']);
|
||||
export type TCompound = ReturnType<typeof TCompound>;
|
||||
|
||||
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<Handle, Ref>;
|
||||
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<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;
|
||||
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<Pattern, Ref>;
|
||||
switch (ctor.label) {
|
||||
case _CRec:
|
||||
if (!Record.isRecord<Assertion, Ref>(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<Assertion, Ref>(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<Template, Ref>;
|
||||
switch (ctor.label) {
|
||||
case _CRec: {
|
||||
const v = Record<Assertion, Ref>(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<Assertion, Ref>();
|
||||
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 {
|
||||
|
|
11
dataspace.ts
11
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<Entity> {
|
||||
readonly handleMap: IdentityMap<Handle, Record<Ref>> = new IdentityMap();
|
||||
readonly handleMap: IdentityMap<Handle, Record<Assertion, Ref>> = new IdentityMap();
|
||||
readonly assertions = new Bag<Ref>();
|
||||
readonly subscriptions: Dictionary<Map<Ref, Dictionary<Handle>>> = new Dictionary();
|
||||
|
||||
assert(turn: Turn, rec: Assertion, handle: Handle): void {
|
||||
if (!Record.isRecord<Ref>(rec)) return;
|
||||
if (!Record.isRecord<Assertion, Ref>(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<Entity> {
|
|||
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<Ref>).label, label)
|
||||
is((prev as Record<Assertion, Ref>).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<Entity> {
|
|||
}
|
||||
|
||||
message(turn: Turn, rec: Assertion): void {
|
||||
if (!Record.isRecord<Ref>(rec)) return;
|
||||
if (!Record.isRecord<Assertion, Ref>(rec)) return;
|
||||
this.subscriptions.get(rec.label)?.forEach((_seen, peer) => turn.message(peer, rec));
|
||||
}
|
||||
}
|
||||
|
|
29
main.ts
29
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)]])))));
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue