/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones 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; 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, 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(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; 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(); 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(); 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 { return Pattern.PCompound(PCompound.rec({ label, fields })); } export function pArr(... items: Array): 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()); }