novy-syndicate/src/rewrite.ts

248 lines
9.1 KiB
TypeScript
Raw Normal View History

2021-03-04 18:54:12 +00:00
import type { Assertion, Bindings, Handle, Ref, Turn } from "./actor.js";
import { Dictionary, IdentityMap, is, Record, Tuple, Value } from "preserves";
export type Attenuation = Array<RewriteStage>; // array of stages, each a list of alternatives
export type RewriteStage = Array<Rewrite>;
export type Rewrite = { pattern: Pattern, template: Template };
export const _CRec = Symbol.for('rec');
export const CRec = Record.makeConstructor<{label: Value<never>, arity: number}, never>()(
_CRec, ['label', 'arity']);
export const _CArr = Symbol.for('arr');
export const CArr = Record.makeConstructor<{arity: number}, never>()(
_CArr, ['arity']);
export const _CDict = Symbol.for('dict');
export const CDict = Record.makeConstructor<{}, never>()(
_CDict, []);
export type ConstructorSpec =
| Record<typeof _CRec, [Value<never>, number], never>
| Record<typeof _CArr, [number], never>
| Record<typeof _CDict, [], never>;
export const _PDiscard = Symbol.for('_');
export const PDiscard = Record.makeConstructor<{}, never>()(
_PDiscard, []);
export const _PBind = Symbol.for('bind');
export const PBind = Record.makeConstructor<{name: string, pattern: Pattern}, never>()(
_PBind, ['name', 'pattern']);
export const _PAnd = Symbol.for('and');
export const PAnd = Record.makeConstructor<{patterns: Array<Pattern>}, never>()(
_PAnd, ['patterns']);
export const _PNot = Symbol.for('not');
export const PNot = Record.makeConstructor<{pattern: Pattern}, never>()(
_PNot, ['pattern']);
export const _Lit = Symbol.for('lit');
export const Lit = Record.makeConstructor<{value: Value<never>}, never>()(
_Lit, ['value']);
export const _PCompound = Symbol.for('compound');
export const PCompound =
Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary<Pattern, never>}, never>()(
_PCompound, ['ctor', 'members']);
export type Pattern =
| Record<typeof _PDiscard, [], never>
| Record<typeof _PBind, [string, Pattern], never>
| Record<typeof _PAnd, [Pattern[]], never>
| Record<typeof _PNot, [Pattern], never>
| Record<typeof _Lit, [Value<never>], never>
| Record<typeof _PCompound, [ConstructorSpec, Dictionary<Pattern, never>], never>;
export const _TRef = Symbol.for('ref');
export const TRef = Record.makeConstructor<{name: string}, never>()(
_TRef, ['name']);
export const _TCompound = Symbol.for('compound');
export const TCompound =
Record.makeConstructor<{ctor: ConstructorSpec, members: Dictionary<Template, never>}, never>()(
_TCompound, ['ctor', 'members']);
export type Template =
| Record<typeof _TRef, [string], never>
| Record<typeof _Lit, [Value<never>], never>
| Record<typeof _TCompound, [ConstructorSpec, Dictionary<Template, never>], never>;
export function match(p: Pattern, v: Assertion): Bindings | null {
let bindings: Bindings = {};
function walk(p: Pattern, v: Assertion): boolean {
switch (p.label) {
case _PDiscard:
return true;
case _PBind:
if (walk(PBind._.pattern(p), v)) {
bindings[PBind._.name(p)] = v;
return true;
}
return false;
case _PAnd:
for (const pp of PAnd._.patterns(p)) {
if (!walk(pp, v)) return false;
}
return true;
case _PNot: {
const savedBindings = bindings;
bindings = {};
const result = !walk(PNot._.pattern(p), 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);
switch (ctor.label) {
case _CRec:
if (!Record.isRecord<Assertion, Tuple<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;
}
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 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.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) as Assertion;
case _TCompound: {
const ctor = TCompound._.ctor(t);
const members = TCompound._.members(t);
switch (ctor.label) {
case _CRec: {
const v = Record<Assertion, any, Ref>(
CRec._.label(ctor) as Assertion,
[],
);
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 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(alternatives: RewriteStage, v: Assertion): Assertion | null {
for (const r of alternatives) {
const w = rewrite(r, v);
if (w !== null) return w;
}
return null;
}
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;
}
export function rfilter(... patterns: Pattern[]): RewriteStage {
return patterns.map(p => ({ pattern: PBind('a', p), template: TRef('a') }));
}
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 };
}