329 lines
11 KiB
TypeScript
329 lines
11 KiB
TypeScript
|
//---------------------------------------------------------------------------
|
||
|
// @syndicate-lang/core, an implementation of Syndicate dataspaces for JS.
|
||
|
// Copyright (C) 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||
|
//
|
||
|
// This program is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// This program is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU General Public License
|
||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
//---------------------------------------------------------------------------
|
||
|
|
||
|
import type { Assertion, Handle, Ref, Turn } from "./actor.js";
|
||
|
import { Bytes, Dictionary, DoubleFloat, embed, IdentityMap, is, isEmbedded, Record, SingleFloat, Tuple } from "@preserves/core";
|
||
|
import { SturdyValue } from "../transport/sturdy.js";
|
||
|
|
||
|
import {
|
||
|
Alts,
|
||
|
Attenuation,
|
||
|
CArr,
|
||
|
CDict,
|
||
|
CRec,
|
||
|
Caveat,
|
||
|
ConstructorSpec,
|
||
|
Lit,
|
||
|
PAnd,
|
||
|
PAtom,
|
||
|
PBind,
|
||
|
PCompound,
|
||
|
PCompoundMembers,
|
||
|
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': {
|
||
|
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<Ref, Assertion>(v)) return false;
|
||
|
for (const [key, pp] of members) {
|
||
|
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: ${v.asPreservesText()}`);
|
||
|
}
|
||
|
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': {
|
||
|
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, 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;
|
||
|
}
|
||
|
|
||
|
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: Attenuation): Ref {
|
||
|
if (a.length === 0) return 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 };
|
||
|
}
|
||
|
|
||
|
export function pRec(label: SturdyValue, ... members: Array<Pattern>): Pattern {
|
||
|
return Pattern.PCompound(PCompound({
|
||
|
ctor: ConstructorSpec.CRec(CRec({ label: label, arity: members.length })),
|
||
|
members: PCompoundMembers(new Dictionary<_embedded, Pattern>(
|
||
|
members.map((p, i) => [i, p] as const).filter(e => e[1]._variant !== 'PDiscard')))}));
|
||
|
}
|
||
|
|
||
|
export function pArr(... members: Array<Pattern>): Pattern {
|
||
|
return Pattern.PCompound(PCompound({
|
||
|
ctor: ConstructorSpec.CArr(CArr(members.length)),
|
||
|
members: PCompoundMembers(new Dictionary<_embedded, Pattern>(
|
||
|
members.map((p, i) => [i, p] as const).filter(e => e[1]._variant !== 'PDiscard')))}));
|
||
|
}
|
||
|
|
||
|
export function pDict(... entries: [SturdyValue, Pattern][]): Pattern {
|
||
|
return Pattern.PCompound(PCompound({
|
||
|
ctor: ConstructorSpec.CDict(CDict()),
|
||
|
members: PCompoundMembers(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());
|
||
|
}
|