syndicate-js/packages/core/src/runtime/rewrite.ts

295 lines
9.4 KiB
TypeScript

/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Turn } from "./actor.js";
import { Bytes, Dictionary, DoubleFloat, embed, IdentityMap, is, isEmbedded, Record, SingleFloat, Tuple, stringify } from "@preserves/core";
import type { Assertion, Handle, Ref } from "./actor.js";
import type { SturdyValue } from "../transport/sturdy.js";
import {
Alts,
Caveat,
Lit,
PAnd,
PAtom,
PBind,
PCompound,
PDiscard,
PNot,
PEmbedded,
Pattern,
Rewrite,
TRef,
Template,
_embedded,
} from '../gen/sturdy.js';
export * from '../gen/sturdy.js';
export type Bindings = Array<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 'PAtom':
switch (p.value._variant) {
case 'Boolean': return typeof v === 'boolean';
case 'ByteString': return Bytes.isBytes(v);
case 'Double': return DoubleFloat.isDouble(v);
case 'Float': return SingleFloat.isSingle(v);
case 'SignedInteger': return typeof v === 'number';
case 'String': return typeof v === 'string';
case 'Symbol': return typeof v === 'symbol';
}
case 'PEmbedded':
return isEmbedded(v);
case 'PBind':
if (walk(p.value.pattern, v)) {
bindings.push(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': switch (p.value._variant) {
case 'rec': {
if (!Record.isRecord<Assertion, Tuple<Assertion>, Ref>(v)) return false;
if (!is(p.value.label, v.label)) return false;
if (p.value.fields.length !== v.length) return false;
let index = 0;
for (const pp of p.value.fields) {
if (!walk(pp, v[index++])) return false;
}
return true;
}
case 'arr': {
if (!Array.isArray(v)) return false;
if ('label' in v) return false;
if (p.value.items.length !== v.length) return false;
let index = 0;
for (const pp of p.value.items) {
if (!walk(pp, v[index++])) return false;
}
return true;
}
case 'dict':{
if (!Dictionary.isDictionary<Ref, Assertion>(v)) return false;
for (const [key, pp] of p.value.entries.entries()) {
const vv = v.get(key);
if (vv === void 0) return false;
if (!walk(pp, vv)) return false;
}
return true;
}
}
default:
((_p : never) => {})(p);
return false;
}
}
return walk(p, v) ? bindings : null;
}
export function instantiate(t: Template, b: Bindings): Assertion {
function walk(t: Template): Assertion {
switch (t._variant) {
case 'TAttenuate': {
const v = walk(t.value.template);
if (!isEmbedded(v)) {
throw new Error(`Attempt to attenuate non-capability: ${stringify(v)}`);
}
const r = v.embeddedValue;
return embed(attenuate(r, ... t.value.attenuation));
}
case 'TRef': {
const n = t.value.binding;
const v = b[n];
if (v === void 0) throw new Error(`Unbound reference: ${n}`);
return v;
}
case 'Lit':
return t.value.value;
case 'TCompound':
switch (t.value._variant) {
case 'rec': {
const v = [] as unknown as Record<Assertion, Assertion[], Ref>;
v.label = t.value.label;
t.value.fields.forEach(tt => v.push(walk(tt)));
return v;
}
case 'arr':
return t.value.items.map(walk);
case 'dict': {
const v = new Dictionary<Ref, Assertion>();
t.value.entries.forEach((tt, key) => v.set(key, 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 {
switch (cav._variant) {
case "Alts": {
for (const r of cav.value.alternatives) {
const w = rewrite(r, v);
if (w !== null) return w;
}
return null;
}
case "Rewrite":
return rewrite(cav.value, v);
case "Reject":
return (match(cav.value.pattern, v) !== null) ? null : v;
case "unknown":
return null;
default:
((_: never) => { throw new Error("bad caveat"); })(cav);
}
}
export function runRewrites(a: Caveat[] | undefined, v: Assertion): Assertion | null {
if (a !== void 0) {
for (let i = a.length - 1; i >= 0; i--) {
const caveat = a[i];
const w = examineAlternatives(caveat, v);
if (w === null) return null;
v = w;
}
}
return v;
}
export function rfilter(... patterns: Pattern[]): Caveat {
const ps = patterns.map(p => Rewrite({
pattern: Pattern.PBind(PBind(p)),
template: Template.TRef(TRef(0))
}));
return ps.length === 1 ? Caveat.Rewrite(ps[0]) : Caveat.Alts(Alts(ps));
}
export function attenuate(ref: Ref, ... a: Caveat[]): Ref {
if (a.length === 0) return ref;
return { ... ref, attenuation: [... (ref.attenuation ?? []), ... a] };
}
export function forwarder(ref: Ref): { proxy: Ref, revoker: Ref } {
let underlying: Ref | null = ref;
let handleMap = new IdentityMap<Handle, Handle>();
let proxy = Turn.ref({
assert(assertion: Assertion, handle: Handle): void {
if (underlying === null) return;
handleMap.set(handle, Turn.active.assert(underlying, assertion));
},
retract(handle: Handle): void {
if (underlying === null) return;
Turn.active.retract(handleMap.get(handle));
handleMap.delete(handle);
},
message(body: Assertion): void {
if (underlying === null) return;
Turn.active.message(underlying, body);
},
sync(peer: Ref): void {
if (underlying === null) return;
Turn.active._sync(underlying, peer);
},
});
let revoker = Turn.ref({
message(_body: Assertion): void {
underlying = null;
handleMap.forEach(h => Turn.active.retract(h));
},
});
return { proxy, revoker };
}
export function pRec(label: SturdyValue, ... fields: Array<Pattern>): Pattern {
return Pattern.PCompound(PCompound.rec({ label, fields }));
}
export function pArr(... items: Array<Pattern>): Pattern {
return Pattern.PCompound(PCompound.arr(items));
}
export function pDict(... entries: [SturdyValue, Pattern][]): Pattern {
return Pattern.PCompound(PCompound.dict(new Dictionary<_embedded, Pattern>(entries)));
}
export function pLit(value: SturdyValue): Pattern {
return Pattern.Lit(Lit(value));
}
export function pNot(p: Pattern): Pattern {
return Pattern.PNot(PNot(p));
}
export function pAnd(... ps: Pattern[]): Pattern {
return Pattern.PAnd(PAnd(ps));
}
export function pBind(pattern: Pattern): Pattern {
return Pattern.PBind(PBind(pattern));
}
export function pEmbedded(): Pattern {
return Pattern.PEmbedded(PEmbedded());
}
export function pSymbol(): Pattern {
return Pattern.PAtom(PAtom.Symbol());
}
export function pByteString(): Pattern {
return Pattern.PAtom(PAtom.ByteString());
}
export function pString(): Pattern {
return Pattern.PAtom(PAtom.String());
}
export function pSignedInteger(): Pattern {
return Pattern.PAtom(PAtom.SignedInteger());
}
export function pDouble(): Pattern {
return Pattern.PAtom(PAtom.Double());
}
export function pFloat(): Pattern {
return Pattern.PAtom(PAtom.Float());
}
export function pBoolean(): Pattern {
return Pattern.PAtom(PAtom.Boolean());
}
export function pDiscard(): Pattern {
return Pattern.PDiscard(PDiscard());
}