137 lines
5.5 KiB
TypeScript
137 lines
5.5 KiB
TypeScript
import { KeyedDictionary, preserves } from '@preserves/core';
|
|
import { AnyValue, Assertion, Entity, Handle, LocalAction, Ref, Turn } from 'runtime/actor';
|
|
|
|
import { fromObserve, Observe } from '../gen/dataspace';
|
|
import { DBind, DDiscard, DLit, Pattern, DCompound, CArr, CDict, CRec } from '../gen/dataspacePatterns';
|
|
|
|
export { asObserve, fromObserve, toObserve, Observe, $Observe } from '../gen/dataspace';
|
|
export * from '../gen/dataspacePatterns';
|
|
|
|
// Q. Why keep "Observe"? Why not do the clever trick of asserting the
|
|
// observer, and having the dataspace read the implicit pattern it's
|
|
// interested in off its attenuator?
|
|
//
|
|
// A. (1) Because we want to have the possibility of more than one
|
|
// variety of pattern language. For example, here we have a simple
|
|
// label match, but we'll quickly want something about as rich as the
|
|
// pattern language in attenuators. And later, we could easily want
|
|
// something with, perhaps, as much power as RELAX-NG or similar. (2)
|
|
// Because we want to have onlookers have some hope of seeing whether
|
|
// a pattern of interest to them is being observed, and if we used
|
|
// attenuators to match, we'd have to expose visibility into
|
|
// attenuators into the pattern language. See next question. (3)
|
|
// Because reflection on attenuators is a big, heavy hammer, and it's
|
|
// better to be explicit about patterns! Also, some attenuations
|
|
// happen behind a veil of secrecy - they're not all open for the
|
|
// world to read about. Actors may proxy communications in arbitrary,
|
|
// secret ways.
|
|
//
|
|
// Q. What kinds of constraints on the pattern language are there?
|
|
//
|
|
// A. It should be fast to evaluate; ideally, JITtable? It should
|
|
// allow patterns on patterns, to some degree. This is for two
|
|
// reasons: we occasionally want to observe observers, and we
|
|
// frequently want to use attenuators limit the kinds of patterns that
|
|
// a principal may observe. As such, it's good to choose a language
|
|
// that enforced some kind of normal forms for its patterns, so
|
|
// observer-observers and attenuator patterns don't have to deal with
|
|
// spurious variation.
|
|
|
|
export class Dataspace implements Partial<Entity> {
|
|
assert(turn: Turn, rec: Assertion, handle: Handle): void {
|
|
console.log(preserves`ds ${turn.activeFacet.id} assert ${rec} ${handle}`);
|
|
throw new Error("Full dataspaces not implemented");
|
|
}
|
|
|
|
retract(turn: Turn, upstreamHandle: Handle): void {
|
|
console.log(preserves`ds ${turn.activeFacet.id} retract ${upstreamHandle}`);
|
|
throw new Error("Full dataspaces not implemented");
|
|
}
|
|
|
|
message(turn: Turn, rec: Assertion): void {
|
|
console.log(preserves`ds ${turn.activeFacet.id} message ${rec}`);
|
|
throw new Error("Full dataspaces not implemented");
|
|
}
|
|
}
|
|
|
|
export function during(f: (t: Turn, a: Assertion) => Promise<LocalAction | null>): Partial<Entity> {
|
|
const assertionMap = new Map<Handle, LocalAction | 'dead'>();
|
|
return {
|
|
assert(t: Turn, a: Assertion, h: Handle): void {
|
|
f(t, a).then(g => {
|
|
if (g === null) g = _t => {};
|
|
switch (assertionMap.get(h)) {
|
|
case void 0:
|
|
assertionMap.set(h, g);
|
|
break;
|
|
case 'dead':
|
|
assertionMap.delete(h);
|
|
t.freshen(g);
|
|
break;
|
|
default:
|
|
console.error('during: Duplicate handle in assert: ' + h);
|
|
break;
|
|
}
|
|
});
|
|
},
|
|
retract(t: Turn, h: Handle): void {
|
|
const g = assertionMap.get(h);
|
|
switch (g) {
|
|
case void 0:
|
|
assertionMap.set(h, 'dead');
|
|
break;
|
|
case 'dead':
|
|
console.error('during: Duplicate handle in retract: ' + h);
|
|
break;
|
|
default:
|
|
assertionMap.delete(h);
|
|
g(t);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
export function observe(t: Turn, ds: Ref, pattern: Pattern, e: Partial<Entity>): Handle {
|
|
return t.assert(ds, fromObserve(Observe({ pattern, observer: t.ref(e) })));
|
|
}
|
|
|
|
export namespace P {
|
|
export function discard(): Pattern {
|
|
return Pattern.DDiscard(DDiscard());
|
|
}
|
|
export function bind(name: string, pattern: Pattern = discard()): Pattern {
|
|
return Pattern.DBind(DBind({name: Symbol.for(name), pattern}));
|
|
}
|
|
export function lit(value: AnyValue): Pattern {
|
|
return Pattern.DLit(DLit(value));
|
|
}
|
|
export function rec(label: AnyValue, ...fields: Pattern[]): Pattern {
|
|
const members = new KeyedDictionary<number, Pattern, Ref>();
|
|
fields.forEach((f, i) => {
|
|
if (f._variant !== 'DDiscard') members.set(i, f);
|
|
});
|
|
return Pattern.DCompound(DCompound.rec({
|
|
ctor: CRec({ label, arity: fields.length }),
|
|
members,
|
|
}));
|
|
}
|
|
export function arr(...entries: Pattern[]): Pattern {
|
|
const members = new KeyedDictionary<number, Pattern, Ref>();
|
|
entries.forEach((f, i) => {
|
|
if (f._variant !== 'DDiscard') members.set(i, f);
|
|
});
|
|
return Pattern.DCompound(DCompound.arr({
|
|
ctor: CArr(entries.length),
|
|
members,
|
|
}));
|
|
}
|
|
export function dict(...entries: [AnyValue, Pattern][]): Pattern {
|
|
const members = new KeyedDictionary<AnyValue, Pattern, Ref>();
|
|
entries.forEach(([k, p]) => members.set(k, p));
|
|
return Pattern.DCompound(DCompound.dict({
|
|
ctor: CDict(),
|
|
members,
|
|
}));
|
|
}
|
|
}
|