2021-02-24 20:48:55 +00:00
|
|
|
import {
|
|
|
|
Dictionary,
|
|
|
|
IdentitySet,
|
|
|
|
Record,
|
|
|
|
Value,
|
|
|
|
fold,
|
|
|
|
is,
|
|
|
|
mapPointers,
|
|
|
|
unannotate,
|
|
|
|
} from 'preserves';
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
2021-02-17 19:57:15 +00:00
|
|
|
|
2021-02-23 13:35:23 +00:00
|
|
|
export type Assertion = Value<Ref>;
|
2021-02-22 18:37:47 +00:00
|
|
|
export type Handle = number;
|
2021-02-22 09:12:10 +00:00
|
|
|
export type ExitReason = null | { ok: true } | { ok: false, err: Error };
|
2021-02-23 14:41:54 +00:00
|
|
|
export type LocalAction = (t: Turn) => void;
|
2021-02-17 19:57:15 +00:00
|
|
|
|
2021-02-22 09:12:10 +00:00
|
|
|
export interface Entity {
|
2021-02-23 15:16:15 +00:00
|
|
|
assert(turn: Turn, assertion: Assertion, handle: Handle): void;
|
|
|
|
retract(turn: Turn, handle: Handle): void;
|
|
|
|
message(turn: Turn, body: Assertion): void;
|
|
|
|
sync(turn: Turn, peer: Ref): void;
|
2021-02-17 19:57:15 +00:00
|
|
|
}
|
|
|
|
|
2021-02-24 20:48:55 +00:00
|
|
|
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 });
|
2021-02-17 19:57:15 +00:00
|
|
|
|
2021-02-22 18:51:19 +00:00
|
|
|
export class Actor {
|
2021-02-23 14:53:42 +00:00
|
|
|
readonly outbound: Map<Handle, Ref>;
|
2021-02-17 19:57:15 +00:00
|
|
|
exitReason: ExitReason = null;
|
|
|
|
|
2021-02-23 14:53:42 +00:00
|
|
|
constructor(initialAssertions = new Map<Handle, Ref>()) {
|
2021-02-22 09:12:10 +00:00
|
|
|
this.outbound = initialAssertions;
|
2021-02-17 19:57:15 +00:00
|
|
|
}
|
|
|
|
|
2021-02-22 18:37:47 +00:00
|
|
|
terminateWith(t: Turn, reason: Exclude<ExitReason, null>) {
|
2021-02-23 10:09:41 +00:00
|
|
|
if (this.exitReason !== null) return;
|
2021-02-22 21:30:36 +00:00
|
|
|
this.exitReason = reason;
|
|
|
|
this.outbound.forEach((peer, h) => t._retract(peer, h));
|
2021-02-17 19:57:15 +00:00
|
|
|
}
|
2021-02-22 09:12:10 +00:00
|
|
|
|
|
|
|
execute(proc: () => void): void {
|
|
|
|
queueMicrotask(() => {
|
2021-02-23 10:09:41 +00:00
|
|
|
if (this.exitReason !== null) return;
|
2021-02-22 21:30:36 +00:00
|
|
|
try {
|
|
|
|
proc();
|
|
|
|
} catch (err) {
|
|
|
|
console.error(Actor, err);
|
|
|
|
Turn.for(this, t => this.terminateWith(t, { ok: false, err }));
|
2021-02-22 09:12:10 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2021-02-17 19:57:15 +00:00
|
|
|
}
|
|
|
|
|
2021-02-22 09:12:10 +00:00
|
|
|
let nextHandle = 0;
|
2021-02-17 19:57:15 +00:00
|
|
|
|
2021-02-23 15:16:15 +00:00
|
|
|
export function _sync(turn: Turn, e: Partial<Entity>, peer: Ref): void {
|
|
|
|
e.sync ? e.sync!(turn, peer) : turn.message(peer, true);
|
|
|
|
}
|
|
|
|
|
2021-02-22 09:12:10 +00:00
|
|
|
export class Turn {
|
2021-02-23 09:56:12 +00:00
|
|
|
readonly actor: Actor;
|
2021-02-23 14:53:42 +00:00
|
|
|
readonly queues = new Map<Actor, LocalAction[]>();
|
2021-02-22 09:12:10 +00:00
|
|
|
|
2021-02-23 10:17:38 +00:00
|
|
|
static for(actor: Actor, f: LocalAction): void {
|
2021-02-22 09:12:10 +00:00
|
|
|
const t = new Turn(actor);
|
|
|
|
f(t);
|
2021-02-23 13:08:29 +00:00
|
|
|
t.queues.forEach((q, a) => a.execute(() => q.forEach(f => Turn.for(a, f))));
|
2021-02-22 09:12:10 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 09:56:12 +00:00
|
|
|
private constructor(actor: Actor) {
|
2021-02-22 09:12:10 +00:00
|
|
|
this.actor = actor;
|
2021-02-17 19:57:15 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 15:16:15 +00:00
|
|
|
ref(e: Partial<Entity>): Ref {
|
2021-02-23 14:53:42 +00:00
|
|
|
return { relay: this.actor, target: e };
|
2021-02-22 18:37:47 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 10:17:38 +00:00
|
|
|
spawn(bootProc: LocalAction, initialAssertions = new IdentitySet<Handle>()): void {
|
2021-02-23 14:40:43 +00:00
|
|
|
this.enqueue(this.actor, () => {
|
2021-02-23 14:53:42 +00:00
|
|
|
const newOutbound = new Map<Handle, Ref>();
|
2021-02-23 10:13:41 +00:00
|
|
|
initialAssertions.forEach(key => {
|
|
|
|
newOutbound.set(key, this.actor.outbound.get(key)!); // we trust initialAssertions
|
|
|
|
this.actor.outbound.delete(key);
|
|
|
|
});
|
|
|
|
const child = new Actor(newOutbound);
|
2021-02-22 09:12:10 +00:00
|
|
|
child.execute(() => Turn.for(child, bootProc));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-02-22 18:37:47 +00:00
|
|
|
quit(): void {
|
2021-02-23 14:40:43 +00:00
|
|
|
this.enqueue(this.actor, t => this.actor.terminateWith(t, { ok: true }));
|
2021-02-22 18:37:47 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 14:38:57 +00:00
|
|
|
assert(ref: Ref, assertion: Assertion): Handle {
|
2021-02-22 09:12:10 +00:00
|
|
|
const h = nextHandle++;
|
2021-02-23 14:38:57 +00:00
|
|
|
this.enqueue(ref.relay, t => {
|
2021-02-24 20:48:55 +00:00
|
|
|
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);
|
|
|
|
}
|
2021-02-22 09:12:10 +00:00
|
|
|
});
|
|
|
|
return h;
|
|
|
|
}
|
|
|
|
|
|
|
|
retract(h: Handle): void {
|
2021-02-24 20:48:55 +00:00
|
|
|
const peer = this.actor.outbound.get(h);
|
|
|
|
if (peer === void 0) return;
|
|
|
|
this._retract(peer, h);
|
2021-02-22 09:12:10 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 14:38:57 +00:00
|
|
|
replace(ref: Ref, h: Handle | undefined, assertion: Assertion): Handle {
|
|
|
|
const newHandle = this.assert(ref, assertion);
|
2021-02-22 18:37:47 +00:00
|
|
|
if (h !== void 0) this.retract(h);
|
|
|
|
return newHandle;
|
|
|
|
}
|
|
|
|
|
2021-02-23 14:38:57 +00:00
|
|
|
_retract(ref: Ref, handle: Handle): void {
|
|
|
|
this.enqueue(ref.relay, t => {
|
2021-02-23 09:56:12 +00:00
|
|
|
this.actor.outbound.delete(handle);
|
2021-02-23 14:53:42 +00:00
|
|
|
ref.target.retract?.(t, handle);
|
2021-02-22 09:12:10 +00:00
|
|
|
});
|
2021-02-17 19:57:15 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 14:38:57 +00:00
|
|
|
sync(ref: Ref): Promise<Turn> {
|
2021-02-23 15:16:15 +00:00
|
|
|
return new Promise(resolve => this.enqueue(ref.relay, t =>
|
|
|
|
_sync(t, ref.target, this.ref({ message: resolve }))));
|
2021-02-22 09:12:10 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 14:38:57 +00:00
|
|
|
message(ref: Ref, assertion: Assertion): void {
|
2021-02-24 20:48:55 +00:00
|
|
|
const a = ref.attenuation === void 0
|
|
|
|
? assertion
|
|
|
|
: runRewrites(ref.attenuation, assertion);
|
|
|
|
if (a !== null) this.enqueue(ref.relay, t => ref.target.message?.(t, assertion));
|
2021-02-22 09:12:10 +00:00
|
|
|
}
|
|
|
|
|
2021-02-23 14:40:43 +00:00
|
|
|
enqueue(relay: Actor, a: LocalAction): void {
|
|
|
|
this.queues.get(relay)?.push(a) ?? this.queues.set(relay, [a]);
|
2021-02-22 09:12:10 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-23 15:16:15 +00:00
|
|
|
|
2021-02-24 20:48:55 +00:00
|
|
|
//---------------------------------------------------------------------------
|
2021-02-23 15:16:15 +00:00
|
|
|
|
2021-02-24 20:48:55 +00:00
|
|
|
export function match(p: Pattern, v: Assertion): Bindings | null {
|
|
|
|
const bindings: Bindings = {};
|
2021-02-23 15:16:15 +00:00
|
|
|
|
2021-02-24 20:48:55 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
2021-02-23 15:16:15 +00:00
|
|
|
}
|
|
|
|
|
2021-02-24 20:48:55 +00:00
|
|
|
return walk(p, v) ? bindings : null;
|
|
|
|
}
|
2021-02-23 15:16:15 +00:00
|
|
|
|
2021-02-24 20:48:55 +00:00
|
|
|
export function instantiate(t: Template, b: Bindings): Assertion {
|
|
|
|
return mapPointers(t, p => ('type' in p) ? b[p.name] : p);
|
|
|
|
}
|
2021-02-23 15:16:15 +00:00
|
|
|
|
2021-02-24 20:48:55 +00:00
|
|
|
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);
|
|
|
|
}
|
2021-02-23 15:16:15 +00:00
|
|
|
|
2021-02-24 20:48:55 +00:00
|
|
|
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;
|
2021-02-23 15:16:15 +00:00
|
|
|
}
|
2021-02-24 20:48:55 +00:00
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function rfilter(... patterns: Value<Exclude<PatternPointer, PBind>>[]): Rewrite {
|
|
|
|
return { pattern: pbind('a', por(... (patterns as Pattern[]))), template: tref('a') };
|
2021-02-23 15:16:15 +00:00
|
|
|
}
|
|
|
|
|
2021-02-24 20:48:55 +00:00
|
|
|
export function attenuate(ref: Ref, ... a: Attenuation): Ref {
|
|
|
|
return { ... ref, attenuation: [... a, ... (ref.attenuation ?? [])] };
|
2021-02-23 15:16:15 +00:00
|
|
|
}
|