novy-syndicate/src/rewrite.ts

191 lines
6.9 KiB
TypeScript

import type { Assertion, Handle, Ref, Turn } from "./actor.js";
import { Dictionary, IdentityMap, is, Record, Tuple } from "@preserves/core";
import { Alts, Attenuation, Caveat, PBind, Pattern, Rewrite, TRef, Template } from './gen/sturdy.js';
export * from './gen/sturdy.js';
export type Bindings = { [name: string]: Assertion };
export function match(p: Pattern, v: Assertion): Bindings | null {
let bindings: Bindings = {};
function walk(p: Pattern, v: Assertion): boolean {
switch (p._variant) {
case 'PDiscard':
return true;
case 'PBind':
if (walk(p.value.pattern, v)) {
bindings[p.value.name.asPreservesText()] = v;
return true;
}
return false;
case 'PAnd':
for (const pp of p.value.patterns) {
if (!walk(pp, v)) return false;
}
return true;
case 'PNot': {
const savedBindings = bindings;
bindings = {};
const result = !walk(p.value.pattern, v)
bindings = savedBindings;
return result;
}
case 'Lit':
return is(p.value.value, v);
case 'PCompound': {
const ctor = p.value.ctor;
const members = p.value.members;
switch (ctor._variant) {
case 'CRec':
if (!Record.isRecord<Assertion, Tuple<Assertion>, Ref>(v)) return false;
if (!is(ctor.value.label, v.label)) return false;
if (ctor.value.arity !== 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 'CArr':
if (!Array.isArray(v)) return false;
if ('label' in v) return false;
if (ctor.value.arity !== 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 as Assertion);
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 {
function walk(t: Template): Assertion {
switch (t._variant) {
case 'TRef': {
const n = t.value.name.asPreservesText()
const v = b[n];
if (v === void 0) throw new Error(`Unbound reference: ${n}`);
return v;
}
case 'Lit':
return t.value.value as Assertion;
case 'TCompound': {
const ctor = t.value.ctor;
const members = t.value.members;
switch (ctor._variant) {
case 'CRec': {
const v = [] as unknown as Record<Assertion, Assertion[], Ref>;
v.length = ctor.value.arity;
v.label = ctor.value.label;
for (const [key, tt] of members) {
v[key as number] = walk(tt);
}
return v;
}
case 'CArr': {
const v = [];
v.length = ctor.value.arity;
for (const [key, tt] of members) {
v[key as number] = walk(tt);
}
return v;
}
case 'CDict': {
const v = new Dictionary<Ref, Assertion>();
for (const [key, tt] of members) {
v.set(key as Assertion, walk(tt));
}
return v;
}
}
}
}
}
return walk(t);
}
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 examineAlternatives(cav: Caveat, v: Assertion): Assertion | null {
if (cav._variant === 'Alts') {
for (const r of cav.value.alternatives) {
const w = rewrite(r, v);
if (w !== null) return w;
}
return null;
} else {
return rewrite(cav.value, v);
}
}
export function runRewrites(a: Attenuation | undefined, v: Assertion): Assertion | null {
if (a !== void 0) {
for (const stage of a) {
const w = examineAlternatives(stage, v);
if (w === null) return null;
v = w;
}
}
return v;
}
const _a = Symbol.for('a');
export function rfilter(... patterns: Pattern[]): Caveat {
const ps = patterns.map(p => Rewrite(Pattern.PBind(PBind(_a, p)), Template.TRef(TRef(_a))));
return ps.length === 1 ? Caveat.Rewrite(ps[0]) : Caveat.Alts(Alts(ps));
}
export function attenuate(ref: Ref, ... a: Attenuation): Ref {
return { ... ref, attenuation: [... a, ... (ref.attenuation ?? [])] };
}
export function forwarder(t: Turn, ref: Ref): { proxy: Ref, revoker: Ref } {
let underlying: Ref | null = ref;
let handleMap = new IdentityMap<Handle, Handle>();
let proxy = t.ref({
assert(turn: Turn, assertion: Assertion, handle: Handle): void {
if (underlying === null) return;
handleMap.set(handle, turn.assert(underlying, assertion));
},
retract(turn: Turn, handle: Handle): void {
if (underlying === null) return;
turn.retract(handleMap.get(handle));
handleMap.delete(handle);
},
message(turn: Turn, body: Assertion): void {
if (underlying === null) return;
turn.message(underlying, body);
},
sync(turn: Turn, peer: Ref): void {
if (underlying === null) return;
turn._sync(underlying, peer);
},
});
let revoker = t.ref({
message(turn: Turn, _body: Assertion): void {
underlying = null;
handleMap.forEach(h => turn.retract(h));
},
});
return { proxy, revoker };
}