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,
|
IdentitySet,
|
||||||
Record,
|
Record,
|
||||||
Value,
|
Value,
|
||||||
fold,
|
|
||||||
is,
|
is,
|
||||||
mapPointers,
|
|
||||||
unannotate,
|
|
||||||
} from 'preserves';
|
} 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 RewriteStage = Array<Rewrite>;
|
||||||
export type Rewrite = { pattern: Pattern, template: Template };
|
export type Rewrite = { pattern: Pattern, template: Template };
|
||||||
|
|
||||||
export type PatternPointer =
|
export const _CRec = Symbol.for('rec');
|
||||||
| Ref
|
export const CRec = Record.makeConstructor<{label: Assertion, arity: number}, Ref>()(
|
||||||
| { type: 'discard' }
|
_CRec, ['label', 'arity']);
|
||||||
| { type: 'bind', name: string, pattern: Pattern }
|
export type CRec = ReturnType<typeof CRec>;
|
||||||
| { type: 'and', patterns: Array<Pattern> }
|
|
||||||
| { type: 'not', pattern: Pattern };
|
|
||||||
export type Pattern = Value<PatternPointer>;
|
|
||||||
|
|
||||||
export type TemplatePointer = Ref | { type: 'ref', name: string };
|
export const _CArr = Symbol.for('arr');
|
||||||
export type Template = Value<TemplatePointer>;
|
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 };
|
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;
|
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 {
|
export class Actor {
|
||||||
readonly outbound: Map<Handle, Ref>;
|
readonly outbound: Map<Handle, Ref>;
|
||||||
exitReason: ExitReason = null;
|
exitReason: ExitReason = null;
|
||||||
|
@ -179,78 +220,110 @@ export function match(p: Pattern, v: Assertion): Bindings | null {
|
||||||
let bindings: Bindings = {};
|
let bindings: Bindings = {};
|
||||||
|
|
||||||
function walk(p: Pattern, v: Assertion): boolean {
|
function walk(p: Pattern, v: Assertion): boolean {
|
||||||
return fold(p, {
|
switch (p.label) {
|
||||||
boolean(_b) { return is(p, v); },
|
case _PDiscard:
|
||||||
single(_f) { return is(p, v); },
|
return true;
|
||||||
double(_f) { return is(p, v); },
|
case _PBind:
|
||||||
integer(_i) { return is(p, v); },
|
if (walk(PBind._.pattern(p) as Pattern, v)) {
|
||||||
string(_s) { return is(p, v); },
|
bindings[PBind._.name(p)] = v;
|
||||||
bytes(_b) { return is(p, v); },
|
return true;
|
||||||
symbol(_s) { return is(p, v); },
|
}
|
||||||
record(r) {
|
return false;
|
||||||
v = unannotate(v);
|
case _PAnd:
|
||||||
return Record.isRecord<Ref>(v)
|
for (const pp of PAnd._.patterns(p) as Pattern[]) {
|
||||||
&& r.length === v.length
|
if (!walk(pp, v)) return false;
|
||||||
&& 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;
|
return true;
|
||||||
},
|
case _PNot: {
|
||||||
annotated(a) { return walk(a.item, v); },
|
const savedBindings = bindings;
|
||||||
pointer(t) {
|
bindings = {};
|
||||||
if ('type' in t) {
|
const result = !walk(PNot._.pattern(p) as Pattern, v)
|
||||||
switch (t.type) {
|
bindings = savedBindings;
|
||||||
case 'bind':
|
return result;
|
||||||
if (walk(t.pattern, v)) {
|
}
|
||||||
bindings[t.name] = v;
|
case _Lit:
|
||||||
return true;
|
return is(Lit._.value(p), v);
|
||||||
}
|
case _PCompound: {
|
||||||
return false;
|
const ctor = PCompound._.ctor(p);
|
||||||
case 'discard':
|
const members = PCompound._.members(p) as Dictionary<Pattern, Ref>;
|
||||||
return true;
|
switch (ctor.label) {
|
||||||
case 'not': {
|
case _CRec:
|
||||||
const savedBindings = bindings;
|
if (!Record.isRecord<Assertion, Ref>(v)) return false;
|
||||||
bindings = {};
|
if (!is(CRec._.label(ctor), v.label)) return false;
|
||||||
const result = !walk(t.pattern, v)
|
if (CRec._.arity(ctor) !== v.length) return false;
|
||||||
bindings = savedBindings;
|
for (const [key, pp] of members) {
|
||||||
return result;
|
if (typeof key !== 'number') return false;
|
||||||
|
if (!walk(pp, v[key])) return false;
|
||||||
}
|
}
|
||||||
case 'and':
|
return true;
|
||||||
for (const p of t.patterns) {
|
case _CArr:
|
||||||
if (!walk(p, v)) return false;
|
if (!Array.isArray(v)) return false;
|
||||||
}
|
if ('label' in v) return false;
|
||||||
return true;
|
if (CArr._.arity(ctor) !== v.length) return false;
|
||||||
}
|
for (const [key, pp] of members) {
|
||||||
} else {
|
if (typeof key !== 'number') return false;
|
||||||
if (!isRef(v)) return false;
|
if (!walk(pp, v[key])) return false;
|
||||||
return (t.relay === v.relay) && (t.target === v.target);
|
}
|
||||||
|
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;
|
return walk(p, v) ? bindings : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function instantiate(t: Template, b: Bindings): Assertion {
|
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 {
|
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 {
|
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 {
|
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
|
// observer-observers and attenuator patterns don't have to deal with
|
||||||
// spurious variation.
|
// 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> {
|
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 assertions = new Bag<Ref>();
|
||||||
readonly subscriptions: Dictionary<Map<Ref, Dictionary<Handle>>> = new Dictionary();
|
readonly subscriptions: Dictionary<Map<Ref, Dictionary<Handle>>> = new Dictionary();
|
||||||
|
|
||||||
assert(turn: Turn, rec: Assertion, handle: Handle): void {
|
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);
|
this.handleMap.set(handle, rec);
|
||||||
if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return;
|
if (this.assertions.change(rec, +1) !== ChangeDescription.ABSENT_TO_PRESENT) return;
|
||||||
if (Observe.isClassOf(rec)) {
|
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());
|
if (!this.subscriptions.has(label)) this.subscriptions.set(label, new Map());
|
||||||
this.subscriptions.get(label)!.set(observer, seen);
|
this.subscriptions.get(label)!.set(observer, seen);
|
||||||
this.assertions.forEach((_count, prev) =>
|
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)));
|
&& seen.set(prev, turn.assert(observer, prev)));
|
||||||
}
|
}
|
||||||
this.subscriptions.get(rec.label)?.forEach((seen, peer) =>
|
this.subscriptions.get(rec.label)?.forEach((seen, peer) =>
|
||||||
|
@ -72,7 +73,7 @@ export class Dataspace implements Partial<Entity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
message(turn: Turn, rec: Assertion): void {
|
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));
|
this.subscriptions.get(rec.label)?.forEach((_seen, peer) => turn.message(peer, rec));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
29
main.ts
29
main.ts
|
@ -5,14 +5,19 @@ import {
|
||||||
Ref,
|
Ref,
|
||||||
Turn,
|
Turn,
|
||||||
attenuate,
|
attenuate,
|
||||||
pdiscard,
|
|
||||||
rfilter,
|
rfilter,
|
||||||
|
PDiscard,
|
||||||
|
PCompound,
|
||||||
|
CRec,
|
||||||
|
Lit,
|
||||||
} from './actor.js';
|
} from './actor.js';
|
||||||
import { Record } from 'preserves';
|
import { Dictionary, Record } from 'preserves';
|
||||||
import { Dataspace, Observe } from './dataspace.js';
|
import { Dataspace, Observe } from './dataspace.js';
|
||||||
|
|
||||||
const BoxState = Record.makeConstructor('BoxState', ['value']);
|
const BoxState = Record.makeConstructor<{value: number}, Ref>()(
|
||||||
const SetBox = Record.makeConstructor('SetBox', ['newValue']);
|
Symbol.for('BoxState'), ['value']);
|
||||||
|
const SetBox = Record.makeConstructor<{newValue: number}, Ref>()(
|
||||||
|
Symbol.for('SetBox'), ['newValue']);
|
||||||
|
|
||||||
let startTime = Date.now();
|
let startTime = Date.now();
|
||||||
let prevValue = 0;
|
let prevValue = 0;
|
||||||
|
@ -76,11 +81,19 @@ Turn.for(new Actor(), async (t: Turn) => {
|
||||||
|
|
||||||
spawnBox(t, attenuate(
|
spawnBox(t, attenuate(
|
||||||
ds,
|
ds,
|
||||||
rfilter(BoxState(pdiscard),
|
rfilter(PCompound(CRec(BoxState.constructorInfo.label,
|
||||||
Observe(SetBox.constructorInfo.label, pdiscard))));
|
BoxState.constructorInfo.arity),
|
||||||
|
new Dictionary()),
|
||||||
|
PCompound(CRec(Observe.constructorInfo.label,
|
||||||
|
Observe.constructorInfo.arity),
|
||||||
|
new Dictionary([[0, Lit(SetBox.constructorInfo.label)]])))));
|
||||||
|
|
||||||
spawnClient(t, attenuate(
|
spawnClient(t, attenuate(
|
||||||
ds,
|
ds,
|
||||||
rfilter(SetBox(pdiscard),
|
rfilter(PCompound(CRec(SetBox.constructorInfo.label,
|
||||||
Observe(BoxState.constructorInfo.label, pdiscard))));
|
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