Much progress
This commit is contained in:
parent
d2f5c947ac
commit
d51af436f5
31
package.json
31
package.json
|
@ -1,20 +1,23 @@
|
||||||
{
|
{
|
||||||
"name": "@syndicate-lang/root",
|
"name": "@syndicate-lang/root",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": ["packages/*", "packages/*/examples/*/"],
|
"workspaces": [
|
||||||
|
"packages/*",
|
||||||
|
"packages/*/examples/*/"
|
||||||
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
"@rollup/plugin-node-resolve": "^13.0",
|
||||||
"@types/jest": "^26.0.19",
|
"@types/jest": "^27.0",
|
||||||
"@types/node": "^14.14.20",
|
"@types/node": "^14",
|
||||||
"esm": "^3.2.25",
|
"esm": "^3.2",
|
||||||
"jest": "^26.6.3",
|
"jest": "^27.4",
|
||||||
"lerna": "^3.22.1",
|
"lerna": "^4.0",
|
||||||
"rollup": "^2.36.1",
|
"rollup": "^2.60",
|
||||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
"rollup-plugin-sourcemaps": "^0.6",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0",
|
||||||
"ts-jest": "^26.4.4",
|
"ts-jest": "^27.0",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^10.4",
|
||||||
"ts-node-dev": "^1.1.1",
|
"ts-node-dev": "^1.1",
|
||||||
"typescript": "^4.1.3"
|
"typescript": "^4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,4 +7,3 @@ export const BootProc = '__SYNDICATE__bootProc';
|
||||||
//
|
//
|
||||||
export type NonEmptySkeleton<Shape> = { shape: Shape, members: Skeleton<Shape>[] };
|
export type NonEmptySkeleton<Shape> = { shape: Shape, members: Skeleton<Shape>[] };
|
||||||
export type Skeleton<Shape> = null | NonEmptySkeleton<Shape>;
|
export type Skeleton<Shape> = null | NonEmptySkeleton<Shape>;
|
||||||
export type Path = Array<number>;
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||||
|
|
||||||
import { IdentitySet, Value } from '@preserves/core';
|
import { IdentitySet, Value, embeddedId, is } from '@preserves/core';
|
||||||
|
import { Cell, Field, Graph } from './dataflow.js';
|
||||||
import { Attenuation, runRewrites } from './rewrite.js';
|
import { Attenuation, runRewrites } from './rewrite.js';
|
||||||
import { queueTask } from './task.js';
|
import { queueTask } from './task.js';
|
||||||
|
|
||||||
|
@ -49,9 +50,13 @@ type OutboundMap = Map<Handle, OutboundAssertion>;
|
||||||
let nextActorId = 0;
|
let nextActorId = 0;
|
||||||
export const __setNextActorId = (v: number) => nextActorId = v;
|
export const __setNextActorId = (v: number) => nextActorId = v;
|
||||||
|
|
||||||
|
export type DataflowGraph = Graph<DataflowBlock, Cell>;
|
||||||
|
export type DataflowBlock = (t: Turn) => void;
|
||||||
|
|
||||||
export class Actor {
|
export class Actor {
|
||||||
readonly id = nextActorId++;
|
readonly id = nextActorId++;
|
||||||
readonly root: Facet;
|
readonly root: Facet;
|
||||||
|
_dataflowGraph: DataflowGraph | null = null;
|
||||||
exitReason: ExitReason = null;
|
exitReason: ExitReason = null;
|
||||||
readonly exitHooks: Array<LocalAction> = [];
|
readonly exitHooks: Array<LocalAction> = [];
|
||||||
|
|
||||||
|
@ -60,6 +65,14 @@ export class Actor {
|
||||||
Turn.for(new Facet(this, this.root), stopIfInertAfter(bootProc));
|
Turn.for(new Facet(this, this.root), stopIfInertAfter(bootProc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get dataflowGraph(): DataflowGraph {
|
||||||
|
if (this._dataflowGraph === null) {
|
||||||
|
this._dataflowGraph =
|
||||||
|
new Graph((b: DataflowBlock) => '' + embeddedId(b), Cell.canonicalizer);
|
||||||
|
}
|
||||||
|
return this._dataflowGraph;
|
||||||
|
}
|
||||||
|
|
||||||
atExit(a: LocalAction): void {
|
atExit(a: LocalAction): void {
|
||||||
this.exitHooks.push(a);
|
this.exitHooks.push(a);
|
||||||
}
|
}
|
||||||
|
@ -73,6 +86,11 @@ export class Actor {
|
||||||
this.exitHooks.forEach(hook => hook(t));
|
this.exitHooks.forEach(hook => hook(t));
|
||||||
queueTask(() => Turn.for(this.root, t => this.root._terminate(t, reason.ok), true));
|
queueTask(() => Turn.for(this.root, t => this.root._terminate(t, reason.ok), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repairDataflowGraph(t: Turn) {
|
||||||
|
if (this._dataflowGraph === null) return;
|
||||||
|
this._dataflowGraph.repairDamage(block => block(t));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Facet {
|
export class Facet {
|
||||||
|
@ -147,8 +165,8 @@ let nextTurnId = 0;
|
||||||
|
|
||||||
export class Turn {
|
export class Turn {
|
||||||
readonly id = nextTurnId++;
|
readonly id = nextTurnId++;
|
||||||
readonly activeFacet: Facet;
|
_activeFacet: Facet;
|
||||||
queues: Map<Facet, LocalAction[]> | null;
|
queues: Map<Actor, LocalAction[]> | null;
|
||||||
|
|
||||||
static for(facet: Facet, f: LocalAction, zombieTurn = false): void {
|
static for(facet: Facet, f: LocalAction, zombieTurn = false): void {
|
||||||
if (!zombieTurn) {
|
if (!zombieTurn) {
|
||||||
|
@ -158,22 +176,27 @@ export class Turn {
|
||||||
const t = new Turn(facet);
|
const t = new Turn(facet);
|
||||||
try {
|
try {
|
||||||
f(t);
|
f(t);
|
||||||
t.queues!.forEach((q, facet) => queueTask(() => Turn.for(facet, t=> q.forEach(f => f(t)))));
|
facet.actor.repairDataflowGraph(t);
|
||||||
t.queues = null;
|
t.deliver();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Turn.for(facet.actor.root, t => facet.actor.terminateWith(t, { ok: false, err }));
|
Turn.for(facet.actor.root, t => facet.actor.terminateWith(t, { ok: false, err }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor(facet: Facet, queues = new Map<Facet, LocalAction[]>()) {
|
private constructor(facet: Facet, queues = new Map<Actor, LocalAction[]>()) {
|
||||||
this.activeFacet = facet;
|
this._activeFacet = facet;
|
||||||
this.queues = queues;
|
this.queues = queues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get activeFacet(): Facet {
|
||||||
|
return this._activeFacet;
|
||||||
|
}
|
||||||
|
|
||||||
_inFacet(facet: Facet, f: LocalAction): void {
|
_inFacet(facet: Facet, f: LocalAction): void {
|
||||||
const t = new Turn(facet, this.queues!);
|
const saved = this._activeFacet;
|
||||||
f(t);
|
this._activeFacet = facet;
|
||||||
t.queues = null;
|
f(this);
|
||||||
|
this._activeFacet = saved;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref<T extends Partial<Entity>>(e: T): Ref {
|
ref<T extends Partial<Entity>>(e: T): Ref {
|
||||||
|
@ -212,6 +235,31 @@ export class Turn {
|
||||||
this.enqueue(this.activeFacet.actor.root, t => this.activeFacet.actor.terminateWith(t, { ok: false, err }));
|
this.enqueue(this.activeFacet.actor.root, t => this.activeFacet.actor.terminateWith(t, { ok: false, err }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
field<V extends Value<T>, T = Ref>(initial: V, name?: string): Field<V, T> {
|
||||||
|
return new Field(this.activeFacet.actor.dataflowGraph, initial, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
dataflow(a: LocalAction) {
|
||||||
|
const f = this.activeFacet;
|
||||||
|
const b = (t: Turn) => f.isLive && t._inFacet(f, a);
|
||||||
|
f.onStop(_t => f.actor.dataflowGraph.forgetSubject(b));
|
||||||
|
f.actor.dataflowGraph.withSubject(b, () => b(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
assertDataflow(assertionFunction: (t: Turn) => {target: Ref, assertion: Assertion}) {
|
||||||
|
let handle: Handle | undefined = void 0;
|
||||||
|
let target: Ref | undefined = void 0;
|
||||||
|
let assertion: Assertion | undefined = void 0;
|
||||||
|
this.dataflow(t => {
|
||||||
|
let {target: nextTarget, assertion: nextAssertion} = assertionFunction(t);
|
||||||
|
if (target !== nextTarget || !is(assertion, nextAssertion)) {
|
||||||
|
target = nextTarget;
|
||||||
|
assertion = nextAssertion;
|
||||||
|
handle = t.replace(target, handle, assertion);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
assert(ref: Ref, assertion: Assertion): Handle {
|
assert(ref: Ref, assertion: Assertion): Handle {
|
||||||
const h = nextHandle++;
|
const h = nextHandle++;
|
||||||
this._assert(ref, assertion, h);
|
this._assert(ref, assertion, h);
|
||||||
|
@ -238,8 +286,10 @@ export class Turn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
replace(ref: Ref, h: Handle | undefined, assertion: Assertion | undefined): Handle | undefined {
|
replace(ref: Ref | undefined, h: Handle | undefined, assertion: Assertion | undefined): Handle | undefined {
|
||||||
const newHandle = assertion === void 0 ? void 0 : this.assert(ref, assertion);
|
const newHandle = (assertion === void 0 || ref === void 0)
|
||||||
|
? void 0
|
||||||
|
: this.assert(ref, assertion);
|
||||||
this.retract(h);
|
this.retract(h);
|
||||||
return newHandle;
|
return newHandle;
|
||||||
}
|
}
|
||||||
|
@ -271,14 +321,14 @@ export class Turn {
|
||||||
if (this.queues === null) {
|
if (this.queues === null) {
|
||||||
throw new Error("Attempt to reuse a committed Turn");
|
throw new Error("Attempt to reuse a committed Turn");
|
||||||
}
|
}
|
||||||
this.queues.get(relay)?.push(a) ?? this.queues.set(relay, [a]);
|
a = t => t._inFacet(relay, a);
|
||||||
|
this.queues.get(relay.actor)?.push(a) ?? this.queues.set(relay.actor, [a]);
|
||||||
}
|
}
|
||||||
|
|
||||||
freshen(a: LocalAction): void {
|
deliver() {
|
||||||
if (this.queues !== null) {
|
this.queues!.forEach((q, actor) =>
|
||||||
throw new Error("Attempt to freshen a non-stale Turn");
|
queueTask(() => Turn.for(actor.root, t => q.forEach(f => f(t)))));
|
||||||
}
|
this.queues = null;
|
||||||
Turn.for(this.activeFacet, a);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,4 +5,3 @@
|
||||||
|
|
||||||
export type NonEmptySkeleton<Shape> = { shape: Shape, members: Skeleton<Shape>[] };
|
export type NonEmptySkeleton<Shape> = { shape: Shape, members: Skeleton<Shape>[] };
|
||||||
export type Skeleton<Shape> = null | NonEmptySkeleton<Shape>;
|
export type Skeleton<Shape> = null | NonEmptySkeleton<Shape>;
|
||||||
export type Path = Array<number>;
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
// Bags and Deltas (which are Bags where item-counts can be negative).
|
// Bags and Deltas (which are Bags where item-counts can be negative).
|
||||||
|
|
||||||
import { Value, Set, Dictionary, GenericEmbedded } from '@preserves/core';
|
import { Value, Set, Dictionary, KeyedDictionary, KeyedSet } from '@preserves/core';
|
||||||
|
|
||||||
export enum ChangeDescription {
|
export enum ChangeDescription {
|
||||||
PRESENT_TO_ABSENT = -1,
|
PRESENT_TO_ABSENT = -1,
|
||||||
|
@ -12,19 +12,19 @@ export enum ChangeDescription {
|
||||||
PRESENT_TO_PRESENT = 2,
|
PRESENT_TO_PRESENT = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Bag<T extends object = GenericEmbedded> {
|
export class Bag<T, V extends Value<T> = Value<T>> {
|
||||||
_items: Dictionary<T, number>;
|
_items: KeyedDictionary<V, number, T>;
|
||||||
|
|
||||||
constructor(s?: Set<T>) {
|
constructor(s?: KeyedSet<V, T>) {
|
||||||
this._items = new Dictionary();
|
this._items = new KeyedDictionary();
|
||||||
if (s) s.forEach((v) => this._items.set(v, 1));
|
if (s) s.forEach((v) => this._items.set(v, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
get(key: Value<T>): number {
|
get(key: V): number {
|
||||||
return this._items.get(key, 0) as number;
|
return this._items.get(key, 0) as number;
|
||||||
}
|
}
|
||||||
|
|
||||||
change(key: Value<T>, delta: number, clamp: boolean = false): ChangeDescription {
|
change(key: V, delta: number, clamp: boolean = false): ChangeDescription {
|
||||||
let oldCount = this.get(key);
|
let oldCount = this.get(key);
|
||||||
let newCount = oldCount + delta;
|
let newCount = oldCount + delta;
|
||||||
if (clamp) {
|
if (clamp) {
|
||||||
|
@ -45,10 +45,10 @@ export class Bag<T extends object = GenericEmbedded> {
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this._items = new Dictionary();
|
this._items = new KeyedDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
includes(key: Value<T>): boolean {
|
includes(key: V): boolean {
|
||||||
return this._items.has(key);
|
return this._items.has(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,24 +56,24 @@ export class Bag<T extends object = GenericEmbedded> {
|
||||||
return this._items.size;
|
return this._items.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
keys(): IterableIterator<Value<T>> {
|
keys(): IterableIterator<V> {
|
||||||
return this._items.keys();
|
return this._items.keys();
|
||||||
}
|
}
|
||||||
|
|
||||||
entries(): IterableIterator<[Value<T>, number]> {
|
entries(): IterableIterator<[V, number]> {
|
||||||
return this._items.entries();
|
return this._items.entries();
|
||||||
}
|
}
|
||||||
|
|
||||||
forEach(f: (count: number, value: Value<T>) => void) {
|
forEach(f: (count: number, value: V) => void) {
|
||||||
this._items.forEach(f);
|
this._items.forEach((c, v) => f(c, v));
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot(): Dictionary<T, number> {
|
snapshot(): KeyedDictionary<V, number, T> {
|
||||||
return this._items.clone();
|
return this._items.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
clone(): Bag<T> {
|
clone(): Bag<T, V> {
|
||||||
const b = new Bag<T>();
|
const b = new Bag<T, V>();
|
||||||
b._items = this._items.clone();
|
b._items = this._items.clone();
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,16 @@
|
||||||
|
|
||||||
// Property-based "dataflow"
|
// Property-based "dataflow"
|
||||||
|
|
||||||
import { FlexSet, FlexMap, Canonicalizer } from '@preserves/core';
|
import { FlexSet, FlexMap, Canonicalizer, Value, is } from '@preserves/core';
|
||||||
|
import { Ref } from 'index.js';
|
||||||
import * as MapSet from './mapset.js';
|
import * as MapSet from './mapset.js';
|
||||||
|
|
||||||
export interface PropertyOptions<ObjectId> {
|
export interface ObservingGraph<ObjectId> {
|
||||||
objectId: ObjectId;
|
recordObservation(objectId: ObjectId): void;
|
||||||
noopGuard?: (oldValue: any, newValue: any) => boolean;
|
recordDamage(objectId: ObjectId): void;
|
||||||
};
|
}
|
||||||
|
|
||||||
export class Graph<SubjectId, ObjectId> {
|
export class Graph<SubjectId, ObjectId> implements ObservingGraph<ObjectId> {
|
||||||
readonly edgesForward: FlexMap<ObjectId, FlexSet<SubjectId>>;
|
readonly edgesForward: FlexMap<ObjectId, FlexSet<SubjectId>>;
|
||||||
readonly edgesReverse: FlexMap<SubjectId, FlexSet<ObjectId>>;
|
readonly edgesReverse: FlexMap<SubjectId, FlexSet<ObjectId>>;
|
||||||
readonly subjectIdCanonicalizer: Canonicalizer<SubjectId>;
|
readonly subjectIdCanonicalizer: Canonicalizer<SubjectId>;
|
||||||
|
@ -90,36 +91,70 @@ export class Graph<SubjectId, ObjectId> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defineObservableProperty<T, K extends keyof T>(
|
let __nextCellId = 0;
|
||||||
obj: T,
|
|
||||||
prop: K,
|
export abstract class Cell {
|
||||||
value: T[K],
|
readonly id: string = '' + (__nextCellId++);
|
||||||
options: PropertyOptions<ObjectId>)
|
readonly graph: ObservingGraph<Cell>;
|
||||||
{
|
__value: unknown;
|
||||||
const { objectId, noopGuard } = options;
|
|
||||||
Object.defineProperty(obj, prop, {
|
static readonly canonicalizer: Canonicalizer<Cell> = v => v.id;
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
constructor(graph: ObservingGraph<Cell>, initial: unknown) {
|
||||||
get: () => {
|
this.graph = graph;
|
||||||
this.recordObservation(objectId);
|
this.__value = initial;
|
||||||
return value;
|
|
||||||
},
|
|
||||||
set: (newValue) => {
|
|
||||||
if (!noopGuard || !noopGuard(value, newValue)) {
|
|
||||||
this.recordDamage(objectId);
|
|
||||||
value = newValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.recordDamage(objectId);
|
|
||||||
return objectId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static newScope<T, R extends T>(o: T): R {
|
observe(): unknown {
|
||||||
const Scope: { new (): R, prototype: T } =
|
this.graph.recordObservation(this);
|
||||||
(function Scope () {}) as unknown as ({ new (): R, prototype: T });
|
return this.__value;
|
||||||
Scope.prototype = o;
|
}
|
||||||
return new Scope();
|
|
||||||
|
update(newValue: unknown) {
|
||||||
|
if (!this.valuesEqual(this.__value, newValue)) {
|
||||||
|
this.graph.recordDamage(this);
|
||||||
|
this.__value = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract valuesEqual(v1: unknown, v2: unknown): boolean;
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `𝐶<${this.__value}>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class TypedCell<V> extends Cell {
|
||||||
|
constructor(graph: ObservingGraph<Cell>, initial: V) {
|
||||||
|
super(graph, initial);
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): V {
|
||||||
|
return this.observe() as V;
|
||||||
|
}
|
||||||
|
|
||||||
|
set value(v: V) {
|
||||||
|
this.update(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract valuesEqual(v1: V, v2: V): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Field<V extends Value<T>, T = Ref> extends TypedCell<V> {
|
||||||
|
readonly name: string | undefined;
|
||||||
|
|
||||||
|
constructor(graph: ObservingGraph<Cell>, initial: V, name?: string) {
|
||||||
|
super(graph, initial);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
valuesEqual(v1: V, v2: V): boolean {
|
||||||
|
return is(v1, v2);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return `𝐹<${this.name}=${this.__value}>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,35 @@
|
||||||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||||
|
|
||||||
import { Value, is, Set } from '@preserves/core';
|
import { IdentityMap } from '@preserves/core';
|
||||||
|
import { Index } from './skeleton.js';
|
||||||
import * as Skeleton from './skeleton.js';
|
import { Assertion, Entity, Handle, Turn } from './actor.js';
|
||||||
import { Bag, ChangeDescription } from './bag.js';
|
import { Observe, toObserve } from '../gen/dataspace.js';
|
||||||
import * as Dataflow from './dataflow.js';
|
|
||||||
import { IdentitySet, IdentityMap } from './idcoll.js';
|
|
||||||
|
|
||||||
export class Dataspace implements Partial<Entity> {
|
export class Dataspace implements Partial<Entity> {
|
||||||
index = new Skeleton.Index();
|
readonly index = new Index();
|
||||||
|
readonly handleMap = new IdentityMap<Handle, [Assertion, Observe | undefined]>();
|
||||||
|
|
||||||
// adjustIndex(a: Value<any>, count: number) {
|
assert(turn: Turn, v: Assertion, handle: Handle): void {
|
||||||
// return this.index.adjustAssertion(a, count);
|
this.index.addAssertion(turn, v);
|
||||||
// }
|
const o = toObserve(v);
|
||||||
|
if (o !== void 0) {
|
||||||
// subscribe(handler: Skeleton.Analysis) {
|
this.index.addObserver(turn, o.pattern, o.observer);
|
||||||
// this.index.addHandler(handler, handler.callback!);
|
}
|
||||||
// }
|
this.handleMap.set(handle, [v, o]);
|
||||||
|
|
||||||
// unsubscribe(handler: Skeleton.Analysis) {
|
|
||||||
// this.index.removeHandler(handler, handler.callback!);
|
|
||||||
// }
|
|
||||||
|
|
||||||
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 {
|
retract(turn: Turn, handle: Handle): void {
|
||||||
console.log(preserves`ds ${turn.activeFacet.id} retract ${upstreamHandle}`);
|
const entry = this.handleMap.get(handle);
|
||||||
throw new Error("Full dataspaces not implemented");
|
if (entry === void 0) return;
|
||||||
|
const [v, o] = entry;
|
||||||
|
if (o !== void 0) {
|
||||||
|
this.index.removeObserver(turn, o.pattern, o.observer);
|
||||||
|
}
|
||||||
|
this.index.removeAssertion(turn, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
message(turn: Turn, rec: Assertion): void {
|
message(turn: Turn, v: Assertion): void {
|
||||||
console.log(preserves`ds ${turn.activeFacet.id} message ${rec}`);
|
this.index.deliverMessage(turn, v);
|
||||||
throw new Error("Full dataspaces not implemented");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
||||||
|
|
||||||
// Convenient alias for the JS-native Set and Map types.
|
|
||||||
|
|
||||||
export type IdentitySet<T> = Set<T>;
|
|
||||||
export const IdentitySet = Set;
|
|
||||||
|
|
||||||
export type IdentityMap<K,V> = Map<K,V>;
|
|
||||||
export const IdentityMap = Map;
|
|
|
@ -1,166 +1,148 @@
|
||||||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||||
|
|
||||||
use crate::schemas::dataspace_patterns::*;
|
import { canonicalString, is, Record, RecordConstructorInfo, Value } from '@preserves/core';
|
||||||
|
import { AnyValue } from './actor.js';
|
||||||
|
import * as P from '../gen/dataspacePatterns.js';
|
||||||
|
|
||||||
use preserves::value::NestedValue;
|
export type Path = Array<AnyValue>;
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
export type Shape = string;
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
|
export function classOfValue(v: any): Shape | null {
|
||||||
pub enum PathStep {
|
if (Record.isRecord(v)) {
|
||||||
Index(usize),
|
return constructorInfoSignature(Record.constructorInfo(v));
|
||||||
Key(_Any),
|
} else if (Array.isArray(v)) {
|
||||||
}
|
return '' + v.length;
|
||||||
|
} else if (Map.isMap(v)) {
|
||||||
pub type Path = Vec<PathStep>;
|
return '{}';
|
||||||
pub type Paths = Vec<Path>;
|
} else {
|
||||||
|
return null;
|
||||||
struct Analyzer {
|
|
||||||
pub const_paths: Paths,
|
|
||||||
pub const_values: Vec<_Any>,
|
|
||||||
pub capture_paths: Paths,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PatternAnalysis {
|
|
||||||
pub const_paths: Paths,
|
|
||||||
pub const_values: _Any,
|
|
||||||
pub capture_paths: Paths,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PatternMatcher<N: NestedValue> {
|
|
||||||
captures: Vec<N>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PatternAnalysis {
|
|
||||||
pub fn new(p: &Pattern) -> Self {
|
|
||||||
let mut analyzer = Analyzer {
|
|
||||||
const_paths: Vec::new(),
|
|
||||||
const_values: Vec::new(),
|
|
||||||
capture_paths: Vec::new(),
|
|
||||||
};
|
|
||||||
analyzer.walk(&mut Vec::new(), p);
|
|
||||||
PatternAnalysis {
|
|
||||||
const_paths: analyzer.const_paths,
|
|
||||||
const_values: _Any::new(analyzer.const_values),
|
|
||||||
capture_paths: analyzer.capture_paths,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Analyzer {
|
export function classOfCtor(v: P.DCompound): Shape {
|
||||||
fn walk_step(&mut self, path: &mut Path, s: PathStep, p: &Pattern) {
|
switch (v._variant) {
|
||||||
path.push(s);
|
case 'rec':
|
||||||
self.walk(path, p);
|
return canonicalString(v.ctor.label) + '/' + v.ctor.arity;
|
||||||
path.pop();
|
case 'arr':
|
||||||
}
|
return '' + v.ctor.arity;
|
||||||
|
case 'dict':
|
||||||
fn walk(&mut self, path: &mut Path, p: &Pattern) {
|
return '{}';
|
||||||
match p {
|
|
||||||
Pattern::DCompound(b) => match &**b {
|
|
||||||
DCompound::Rec { members, .. } |
|
|
||||||
DCompound::Arr { members, .. } => {
|
|
||||||
for (i, p) in members {
|
|
||||||
self.walk_step(path, PathStep::Index(usize::try_from(i).unwrap_or(0)), p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DCompound::Dict { members, .. } => {
|
|
||||||
for (k, p) in members {
|
|
||||||
self.walk_step(path, PathStep::Key(k.clone()), p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Pattern::DBind(b) => {
|
|
||||||
let DBind { pattern, .. } = &**b;
|
|
||||||
self.capture_paths.push(path.clone());
|
|
||||||
self.walk(path, pattern)
|
|
||||||
}
|
|
||||||
Pattern::DDiscard(_) =>
|
|
||||||
(),
|
|
||||||
Pattern::DLit(b) => {
|
|
||||||
let DLit { value } = &**b;
|
|
||||||
self.const_paths.push(path.clone());
|
|
||||||
self.const_values.push(value.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: NestedValue> Pattern<N> {
|
// Called by generated code in addition to functions in this module
|
||||||
pub fn match_value(&self, value: &N) -> Option<Vec<N>> {
|
export function constructorInfoSignature(ci: RecordConstructorInfo<Value>): string {
|
||||||
let mut matcher = PatternMatcher::new();
|
return canonicalString(ci.label) + '/' + ci.arity;
|
||||||
if matcher.run(self, value) {
|
}
|
||||||
Some(matcher.captures)
|
|
||||||
} else {
|
export function step(v: AnyValue, index: AnyValue): AnyValue | undefined {
|
||||||
None
|
if (Map.isMap(v)) {
|
||||||
}
|
return v.get(index);
|
||||||
|
} else {
|
||||||
|
return (v as Array<AnyValue> /* includes Record! */)[index as number];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: NestedValue> PatternMatcher<N> {
|
export type PatternAnalysis = {
|
||||||
fn new() -> Self {
|
constPaths: Array<Path>,
|
||||||
PatternMatcher {
|
constValues: Array<AnyValue>,
|
||||||
captures: Vec::new(),
|
capturePaths: Array<Path>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function analysePattern(p: P.Pattern): PatternAnalysis {
|
||||||
|
const result: PatternAnalysis = {
|
||||||
|
constPaths: [],
|
||||||
|
constValues: [],
|
||||||
|
capturePaths: [],
|
||||||
|
};
|
||||||
|
const path: Path = [];
|
||||||
|
|
||||||
|
function walk(p: P.Pattern) {
|
||||||
|
switch (p._variant) {
|
||||||
|
case 'DCompound':
|
||||||
|
p.value.members.forEach((v, k) => {
|
||||||
|
path.push(k);
|
||||||
|
walk(v);
|
||||||
|
path.pop();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'DBind':
|
||||||
|
result.capturePaths.push(path.slice());
|
||||||
|
walk(p.value.pattern);
|
||||||
|
break;
|
||||||
|
case 'DDiscard':
|
||||||
|
break;
|
||||||
|
case 'DLit':
|
||||||
|
result.constPaths.push(path.slice());
|
||||||
|
result.constValues.push(p.value.value);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&mut self, pattern: &Pattern<N>, value: &N) -> bool {
|
walk(p);
|
||||||
match pattern {
|
return result;
|
||||||
Pattern::DDiscard(_) => true,
|
}
|
||||||
Pattern::DBind(b) => {
|
|
||||||
self.captures.push(value.clone());
|
export function match(p: P.Pattern, v: AnyValue): Array<AnyValue> | false {
|
||||||
self.run(&b.pattern, value)
|
const captures: Array<AnyValue> = [];
|
||||||
|
|
||||||
|
function walk(p: P.Pattern, v: AnyValue): boolean {
|
||||||
|
switch (p._variant) {
|
||||||
|
case 'DBind': {
|
||||||
|
captures.push(v);
|
||||||
|
return walk(p.value.pattern, v);
|
||||||
}
|
}
|
||||||
Pattern::DLit(b) => value == &b.value,
|
case 'DDiscard':
|
||||||
Pattern::DCompound(b) => match &**b {
|
return true;
|
||||||
DCompound::Rec { ctor, members } => {
|
case 'DLit':
|
||||||
let arity = (&ctor.arity).try_into().expect("reasonable arity");
|
return is(p.value.value, v);
|
||||||
match value.value().as_record(Some(arity)) {
|
case 'DCompound': {
|
||||||
None => false,
|
const pcls = classOfCtor(p.value);
|
||||||
Some(r) => {
|
const vcls = classOfValue(v);
|
||||||
for (i, p) in members.iter() {
|
if (pcls !== vcls) return false;
|
||||||
let i: usize = i.try_into().expect("reasonable index");
|
for (const [stepIndex, pp] of p.value.members.entries()) {
|
||||||
if !self.run(p, &r.fields()[i]) {
|
const vv = step(v, stepIndex);
|
||||||
return false;
|
if (vv === void 0) return false;
|
||||||
}
|
if (!walk(pp, vv)) return false;
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DCompound::Arr { ctor, members } => {
|
|
||||||
let arity: usize = (&ctor.arity).try_into().expect("reasonable arity");
|
|
||||||
match value.value().as_sequence() {
|
|
||||||
None => false,
|
|
||||||
Some(vs) => {
|
|
||||||
if vs.len() != arity {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
for (i, p) in members.iter() {
|
|
||||||
let i: usize = i.try_into().expect("reasonable index");
|
|
||||||
if !self.run(p, &vs[i]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DCompound::Dict { ctor: _, members } => {
|
|
||||||
match value.value().as_dictionary() {
|
|
||||||
None => false,
|
|
||||||
Some(entries) => {
|
|
||||||
for (k, p) in members.iter() {
|
|
||||||
if !entries.get(k).map(|v| self.run(p, v)).unwrap_or(false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return walk(p, v) ? captures : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isCompletelyConcrete(p: P.Pattern): boolean {
|
||||||
|
function walk(p: P.Pattern): boolean {
|
||||||
|
switch (p._variant) {
|
||||||
|
case 'DBind': return false;
|
||||||
|
case 'DDiscard': return false;
|
||||||
|
case 'DLit': return true;
|
||||||
|
case 'DCompound':
|
||||||
|
for (const pp of p.value.members.values()) {
|
||||||
|
if (!walk(pp)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return walk(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withoutCaptures(p: P.Pattern): P.Pattern {
|
||||||
|
function walk(p: P.Pattern): P.Pattern {
|
||||||
|
switch (p._variant) {
|
||||||
|
case 'DBind': return walk(p.value.pattern);
|
||||||
|
case 'DDiscard': return p;
|
||||||
|
case 'DLit': return p;
|
||||||
|
case 'DCompound': return P.Pattern.DCompound({
|
||||||
|
_variant: p.value._variant,
|
||||||
|
ctor: p.value.ctor,
|
||||||
|
members: p.value.members.mapEntries(e => [e[0], walk(e[1])]),
|
||||||
|
} as P.DCompound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return walk(p);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,28 @@
|
||||||
/// SPDX-License-Identifier: GPL-3.0-or-later
|
/// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
/// SPDX-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||||
|
|
||||||
import { IdentitySet } from './idcoll.js';
|
import { Set, Dictionary, KeyedDictionary, IdentityMap } from '@preserves/core';
|
||||||
import { is, Value, Record, Set, Dictionary, canonicalString, RecordConstructorInfo, GenericEmbedded } from '@preserves/core';
|
import { AnyValue, Assertion, Handle, Ref, Turn } from './actor.js';
|
||||||
|
|
||||||
import { Bag, ChangeDescription } from './bag.js';
|
import { Bag, ChangeDescription } from './bag.js';
|
||||||
|
|
||||||
import * as Stack from './stack.js';
|
import * as Stack from './stack.js';
|
||||||
|
import * as P from '../gen/dataspacePatterns.js';
|
||||||
|
import { Path, analysePattern, classOfCtor, classOfValue, step } from './pattern.js';
|
||||||
|
|
||||||
import { Path, NonEmptySkeleton, Skeleton } from './api.js';
|
enum EventType {
|
||||||
|
|
||||||
export enum EventType {
|
|
||||||
ADDED = +1,
|
ADDED = +1,
|
||||||
REMOVED = -1,
|
REMOVED = -1,
|
||||||
MESSAGE = 0,
|
MESSAGE = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HandlerCallback = (eventType: EventType, bindings: Array<Value>) => void;
|
const _nop = function() {};
|
||||||
|
|
||||||
export type Shape = string;
|
|
||||||
|
|
||||||
export interface Analysis {
|
|
||||||
skeleton: Skeleton<Shape>;
|
|
||||||
constPaths: Array<Path>;
|
|
||||||
constVals: Array<Value>;
|
|
||||||
capturePaths: Array<Path>;
|
|
||||||
callback?: HandlerCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _nop = () => {};
|
|
||||||
|
|
||||||
export class Index {
|
export class Index {
|
||||||
readonly allAssertions: Bag = new Bag();
|
readonly allAssertions: Bag<Ref> = new Bag();
|
||||||
readonly root: Node = new Node(new Continuation(new Set()));
|
readonly root: Node = new Node(new Continuation(new Set()));
|
||||||
|
|
||||||
addHandler(analysisResults: Analysis, callback: HandlerCallback) {
|
addObserver(t: Turn, pattern: P.Pattern, observer: Ref) {
|
||||||
let {skeleton, constPaths, constVals, capturePaths} = analysisResults;
|
let {constPaths, constValues, capturePaths} = analysePattern(pattern);
|
||||||
const continuation = this.root.extend(skeleton);
|
const continuation = this.root.extend(pattern);
|
||||||
let constValMap = continuation.leafMap.get(constPaths);
|
let constValMap = continuation.leafMap.get(constPaths);
|
||||||
if (!constValMap) {
|
if (!constValMap) {
|
||||||
constValMap = new Dictionary();
|
constValMap = new Dictionary();
|
||||||
|
@ -51,46 +37,51 @@ export class Index {
|
||||||
});
|
});
|
||||||
continuation.leafMap.set(constPaths, constValMap);
|
continuation.leafMap.set(constPaths, constValMap);
|
||||||
}
|
}
|
||||||
let leaf = constValMap.get(constVals);
|
let leaf = constValMap.get(constValues);
|
||||||
if (!leaf) {
|
if (!leaf) {
|
||||||
leaf = new Leaf();
|
leaf = new Leaf();
|
||||||
constValMap.set(constVals, leaf);
|
constValMap.set(constValues, leaf);
|
||||||
}
|
}
|
||||||
let handler = leaf.handlerMap.get(capturePaths);
|
let observerGroup = leaf.observerGroups.get(capturePaths);
|
||||||
if (!handler) {
|
if (!observerGroup) {
|
||||||
const cachedCaptures = new Bag();
|
const cachedCaptures = new Bag<Ref, Array<AnyValue>>();
|
||||||
leaf.cachedAssertions.forEach((a) =>
|
leaf.cachedAssertions.forEach((a) =>
|
||||||
cachedCaptures._items.update(projectPaths(a, capturePaths), n => n! + 1, 0));
|
cachedCaptures._items.update(projectPaths(a, capturePaths), n => n! + 1, 0));
|
||||||
handler = new Handler(cachedCaptures);
|
observerGroup = new ObserverGroup(cachedCaptures);
|
||||||
leaf.handlerMap.set(capturePaths, handler);
|
leaf.observerGroups.set(capturePaths, observerGroup);
|
||||||
}
|
}
|
||||||
handler.callbacks.add(callback);
|
const captureMap: KeyedDictionary<Array<AnyValue>, Handle, Ref> = new KeyedDictionary();
|
||||||
handler.cachedCaptures.forEach((_count, captures) =>
|
observerGroup.observers.set(observer, captureMap);
|
||||||
callback(EventType.ADDED, captures as Array<Value>));
|
observerGroup.cachedCaptures.forEach((_count, captures) =>
|
||||||
|
captureMap.set(captures, t.assert(observer, captures)));
|
||||||
}
|
}
|
||||||
|
|
||||||
removeHandler(analysisResults: Analysis, callback: HandlerCallback) {
|
removeObserver(t: Turn, pattern: P.Pattern, observer: Ref) {
|
||||||
let {skeleton, constPaths, constVals, capturePaths} = analysisResults;
|
let {constPaths, constValues, capturePaths} = analysePattern(pattern);
|
||||||
const continuation = this.root.extend(skeleton);
|
const continuation = this.root.extend(pattern);
|
||||||
let constValMap = continuation.leafMap.get(constPaths);
|
let constValMap = continuation.leafMap.get(constPaths);
|
||||||
if (!constValMap) return;
|
if (!constValMap) return;
|
||||||
let leaf = constValMap.get(constVals);
|
let leaf = constValMap.get(constValues);
|
||||||
if (!leaf) return;
|
if (!leaf) return;
|
||||||
let handler = leaf.handlerMap.get(capturePaths);
|
let observerGroup = leaf.observerGroups.get(capturePaths);
|
||||||
if (!handler) return;
|
if (!observerGroup) return;
|
||||||
handler.callbacks.delete(callback);
|
const captureMap = observerGroup.observers.get(observer);
|
||||||
if (handler.callbacks.size === 0) {
|
if (captureMap) {
|
||||||
leaf.handlerMap.delete(capturePaths);
|
captureMap.forEach((handle, _captures) => t.retract(handle));
|
||||||
|
observerGroup.observers.delete(observer);
|
||||||
|
}
|
||||||
|
if (observerGroup.observers.size === 0) {
|
||||||
|
leaf.observerGroups.delete(capturePaths);
|
||||||
}
|
}
|
||||||
if (leaf.isEmpty()) {
|
if (leaf.isEmpty()) {
|
||||||
constValMap.delete(constVals);
|
constValMap.delete(constValues);
|
||||||
}
|
}
|
||||||
if (constValMap.size === 0) {
|
if (constValMap.size === 0) {
|
||||||
continuation.leafMap.delete(constPaths);
|
continuation.leafMap.delete(constPaths);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
adjustAssertion(outerValue: Value, delta: number): ChangeDescription {
|
adjustAssertion(t: Turn, outerValue: Assertion, delta: number): ChangeDescription {
|
||||||
let net = this.allAssertions.change(outerValue, delta);
|
let net = this.allAssertions.change(outerValue, delta);
|
||||||
switch (net) {
|
switch (net) {
|
||||||
case ChangeDescription.ABSENT_TO_PRESENT:
|
case ChangeDescription.ABSENT_TO_PRESENT:
|
||||||
|
@ -101,7 +92,8 @@ export class Index {
|
||||||
(l, v) => l.cachedAssertions.add(v),
|
(l, v) => l.cachedAssertions.add(v),
|
||||||
(h, vs) => {
|
(h, vs) => {
|
||||||
if (h.cachedCaptures.change(vs, +1) === ChangeDescription.ABSENT_TO_PRESENT)
|
if (h.cachedCaptures.change(vs, +1) === ChangeDescription.ABSENT_TO_PRESENT)
|
||||||
h.callbacks.forEach(cb => cb(EventType.ADDED, vs));
|
h.observers.forEach((captureMap, observer) =>
|
||||||
|
captureMap.set(vs, t.assert(observer, vs)));
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -113,111 +105,114 @@ export class Index {
|
||||||
(l, v) => l.cachedAssertions.delete(v),
|
(l, v) => l.cachedAssertions.delete(v),
|
||||||
(h, vs) => {
|
(h, vs) => {
|
||||||
if (h.cachedCaptures.change(vs, -1) === ChangeDescription.PRESENT_TO_ABSENT)
|
if (h.cachedCaptures.change(vs, -1) === ChangeDescription.PRESENT_TO_ABSENT)
|
||||||
h.callbacks.forEach(cb => cb(EventType.REMOVED, vs));
|
h.observers.forEach((captureMap, _observer) => {
|
||||||
|
t.retract(captureMap.get(vs));
|
||||||
|
captureMap.delete(vs);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return net;
|
return net;
|
||||||
}
|
}
|
||||||
|
|
||||||
addAssertion(v: Value) {
|
addAssertion(t: Turn, v: Assertion) {
|
||||||
this.adjustAssertion(v, +1);
|
this.adjustAssertion(t, v, +1);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeAssertion(v: Value) {
|
removeAssertion(t: Turn, v: Assertion) {
|
||||||
this.adjustAssertion(v, -1);
|
this.adjustAssertion(t, v, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
deliverMessage(v: Value, leafCallback: (l: Leaf, v: Value) => void = _nop) {
|
deliverMessage(t: Turn, v: Assertion, leafCallback: (l: Leaf, v: Assertion) => void = _nop) {
|
||||||
this.root.modify(EventType.MESSAGE, v, _nop, leafCallback, (h, vs) =>
|
this.root.modify(EventType.MESSAGE, v, _nop, leafCallback, (h, vs) =>
|
||||||
h.callbacks.forEach(cb => cb(EventType.MESSAGE, vs)));
|
h.observers.forEach((_captureMap, observer) => t.message(observer, vs)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Selector = [number, AnyValue];
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
readonly continuation: Continuation;
|
readonly continuation: Continuation;
|
||||||
readonly edges: { [selector: string]: { [shape: string]: Node } } = {};
|
readonly edges: KeyedDictionary<Selector, { [shape: string]: Node }, Ref> = new KeyedDictionary();
|
||||||
|
|
||||||
constructor(continuation: Continuation) {
|
constructor(continuation: Continuation) {
|
||||||
this.continuation = continuation;
|
this.continuation = continuation;
|
||||||
}
|
}
|
||||||
|
|
||||||
extend(skeleton: Skeleton<Shape>): Continuation {
|
extend(p: P.Pattern): Continuation {
|
||||||
const path: Path = [];
|
const path: Path = [];
|
||||||
|
|
||||||
function walkNode(node: Node,
|
function walkNode(node: Node, popCount: number, stepIndex: AnyValue, p: P.Pattern): [number, Node]
|
||||||
popCount: number,
|
|
||||||
index: number,
|
|
||||||
skeleton: Skeleton<Shape>): [number, Node]
|
|
||||||
{
|
{
|
||||||
if (skeleton === null) {
|
switch (p._variant) {
|
||||||
return [popCount, node];
|
case 'DDiscard':
|
||||||
} else {
|
case 'DLit':
|
||||||
const selector = '' + popCount + ',' + index;
|
return [popCount, node];
|
||||||
const cls = skeleton.shape;
|
case 'DBind':
|
||||||
let table = node.edges[selector];
|
return walkNode(node, popCount, stepIndex, p.value.pattern);
|
||||||
if (!table) {
|
case 'DCompound': {
|
||||||
table = {};
|
const selector: Selector = [popCount, stepIndex];
|
||||||
node.edges[selector] = table;
|
let table = node.edges.get(selector);
|
||||||
}
|
if (!table) {
|
||||||
let nextNode = table[cls];
|
table = {};
|
||||||
if (!nextNode) {
|
node.edges.set(selector, table);
|
||||||
nextNode = new Node(new Continuation(
|
}
|
||||||
node.continuation.cachedAssertions.filter(
|
let cls = classOfCtor(p.value);
|
||||||
(a) => classOf(projectPath(a, path)) === cls)));
|
let nextNode = table[cls];
|
||||||
table[cls] = nextNode;
|
if (!nextNode) {
|
||||||
}
|
nextNode = new Node(new Continuation(
|
||||||
popCount = 0;
|
node.continuation.cachedAssertions.filter(
|
||||||
index = 0;
|
(a) => classOfValue(projectPath(a, path)) === cls)));
|
||||||
path.push(index);
|
table[cls] = nextNode;
|
||||||
skeleton.members.forEach((member) => {
|
}
|
||||||
[popCount, nextNode] = walkNode(nextNode, popCount, index, member);
|
popCount = 0;
|
||||||
index++;
|
p.value.members.forEach((member, stepIndex) => {
|
||||||
|
path.push(stepIndex);
|
||||||
|
[popCount, nextNode] = walkNode(nextNode, popCount, stepIndex, member);
|
||||||
|
path.pop();
|
||||||
|
});
|
||||||
path.pop();
|
path.pop();
|
||||||
path.push(index);
|
return [popCount + 1, nextNode];
|
||||||
});
|
}
|
||||||
path.pop();
|
|
||||||
return [popCount + 1, nextNode];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return walkNode(this, 0, 0, skeleton)[1].continuation;
|
return walkNode(this, 0, 0, p)[1].continuation;
|
||||||
}
|
}
|
||||||
|
|
||||||
modify(operation: EventType,
|
modify(operation: EventType,
|
||||||
outerValue: Value,
|
outerValue: Assertion,
|
||||||
m_cont: (c: Continuation, v: Value) => void,
|
m_cont: (c: Continuation, v: Assertion) => void,
|
||||||
m_leaf: (l: Leaf, v: Value) => void,
|
m_leaf: (l: Leaf, v: Assertion) => void,
|
||||||
m_handler: (h: Handler, vs: Array<Value>) => void)
|
m_observerGroup: (h: ObserverGroup, vs: Array<Assertion>) => void)
|
||||||
{
|
{
|
||||||
function walkNode(node: Node, termStack: Stack.NonEmptyStack<Array<Value>>) {
|
function walkNode(node: Node, termStack: Stack.NonEmptyStack<Assertion>) {
|
||||||
walkContinuation(node.continuation);
|
walkContinuation(node.continuation);
|
||||||
Object.entries(node.edges).forEach(([selectorStr, table]) => {
|
node.edges.forEach((table, [popCount, stepIndex]) => {
|
||||||
const selector = parseSelector(selectorStr);
|
const nextStack = Stack.dropNonEmpty(termStack, popCount);
|
||||||
const nextStack = Stack.dropNonEmpty(termStack, selector.popCount);
|
const nextValue = step(nextStack.item, stepIndex);
|
||||||
const nextValue = step(nextStack.item, selector.index);
|
const nextClass = classOfValue(nextValue);
|
||||||
const nextClass = classOf(nextValue);
|
|
||||||
const nextNode = nextClass && table[nextClass];
|
const nextNode = nextClass && table[nextClass];
|
||||||
if (nextNode) walkNode(nextNode, Stack.push(nextValue as Array<Value>, nextStack));
|
if (nextNode) walkNode(nextNode, Stack.push(nextValue!, nextStack));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function walkContinuation(continuation: Continuation) {
|
function walkContinuation(continuation: Continuation) {
|
||||||
m_cont(continuation, outerValue);
|
m_cont(continuation, outerValue);
|
||||||
continuation.leafMap.forEach((constValMap, constPaths) => {
|
continuation.leafMap.forEach((constValMap, constPaths) => {
|
||||||
let constVals = projectPaths(outerValue, constPaths as Array<Path>);
|
let constValues = projectPaths(outerValue, constPaths);
|
||||||
let leaf = constValMap.get(constVals);
|
let leaf = constValMap.get(constValues);
|
||||||
if (!leaf && operation === EventType.ADDED) {
|
if (!leaf && operation === EventType.ADDED) {
|
||||||
leaf = new Leaf();
|
leaf = new Leaf();
|
||||||
constValMap.set(constVals, leaf);
|
constValMap.set(constValues, leaf);
|
||||||
}
|
}
|
||||||
if (leaf) {
|
if (leaf) {
|
||||||
m_leaf(leaf, outerValue);
|
m_leaf(leaf, outerValue);
|
||||||
leaf.handlerMap.forEach((handler, capturePaths) => {
|
leaf.observerGroups.forEach((observerGroup, capturePaths) => {
|
||||||
m_handler(handler, projectPaths(outerValue, capturePaths as Array<Path>));
|
m_observerGroup(observerGroup, projectPaths(outerValue, capturePaths));
|
||||||
});
|
});
|
||||||
if (operation === EventType.REMOVED && leaf.isEmpty()) {
|
if (operation === EventType.REMOVED && leaf.isEmpty()) {
|
||||||
constValMap.delete(constVals);
|
constValMap.delete(constValues);
|
||||||
if (constValMap.size === 0) {
|
if (constValMap.size === 0) {
|
||||||
continuation.leafMap.delete(constPaths);
|
continuation.leafMap.delete(constPaths);
|
||||||
}
|
}
|
||||||
|
@ -231,160 +226,42 @@ class Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSelector(s: string): { popCount: number, index: number } {
|
|
||||||
const pos = s.indexOf(',');
|
|
||||||
return { popCount: parseInt(s.substr(0, pos)),
|
|
||||||
index: parseInt(s.substr(pos + 1)) };
|
|
||||||
}
|
|
||||||
|
|
||||||
class Continuation {
|
class Continuation {
|
||||||
readonly cachedAssertions: Set;
|
readonly cachedAssertions: Set<Ref>;
|
||||||
readonly leafMap: Dictionary<GenericEmbedded, Dictionary<GenericEmbedded, Leaf>> = new Dictionary();
|
readonly leafMap: KeyedDictionary<Array<Path>, Dictionary<Ref, Leaf>, Ref> = new KeyedDictionary();
|
||||||
|
|
||||||
constructor(cachedAssertions: Set) {
|
constructor(cachedAssertions: Set<Ref>) {
|
||||||
this.cachedAssertions = cachedAssertions;
|
this.cachedAssertions = cachedAssertions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Leaf {
|
class Leaf {
|
||||||
readonly cachedAssertions: Set = new Set();
|
readonly cachedAssertions: Set<Ref> = new Set();
|
||||||
readonly handlerMap: Dictionary<GenericEmbedded, Handler> = new Dictionary();
|
readonly observerGroups: KeyedDictionary<Array<Path>, ObserverGroup, Ref> = new KeyedDictionary();
|
||||||
|
|
||||||
isEmpty(): boolean {
|
isEmpty(): boolean {
|
||||||
return this.cachedAssertions.size === 0 && this.handlerMap.size === 0;
|
return this.cachedAssertions.size === 0 && this.observerGroups.size === 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Handler {
|
class ObserverGroup {
|
||||||
readonly cachedCaptures: Bag;
|
readonly cachedCaptures: Bag<Ref, Array<AnyValue>>;
|
||||||
readonly callbacks: IdentitySet<HandlerCallback> = new IdentitySet();
|
readonly observers: IdentityMap<Ref, KeyedDictionary<Array<AnyValue>, Handle, Ref>> = new IdentityMap();
|
||||||
|
|
||||||
constructor(cachedCaptures: Bag) {
|
constructor(cachedCaptures: Bag<Ref, Array<AnyValue>>) {
|
||||||
this.cachedCaptures = cachedCaptures;
|
this.cachedCaptures = cachedCaptures;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function constructorInfoSignature(ci: RecordConstructorInfo<Value>): string {
|
// Total by assumption that path is valid for v
|
||||||
return canonicalString(ci.label) + '/' + ci.arity;
|
function projectPath(v: AnyValue, path: Path): AnyValue {
|
||||||
}
|
|
||||||
|
|
||||||
function classOf(v: any): Shape | null {
|
|
||||||
if (Record.isRecord(v)) {
|
|
||||||
return constructorInfoSignature(Record.constructorInfo(v));
|
|
||||||
} else if (Array.isArray(v)) {
|
|
||||||
return '' + v.length;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function step(v: Array<Value> /* includes Record! */, index: number) {
|
|
||||||
return v[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
function projectPath(v: Value, path: Path) {
|
|
||||||
for (let index of path) {
|
for (let index of path) {
|
||||||
v = step(v as Array<Value>, index);
|
v = step(v, index)!;
|
||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
function projectPaths(v: Value, paths: Array<Path>) {
|
// Total by assumption that paths are valid for v
|
||||||
|
function projectPaths(v: AnyValue, paths: Array<Path>): AnyValue[] {
|
||||||
return paths.map((path) => projectPath(v, path));
|
return paths.map((path) => projectPath(v, path));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function analyzeAssertion(a: Value): Analysis {
|
|
||||||
const constPaths: Path[] = [];
|
|
||||||
const constVals: Value[] = [];
|
|
||||||
const capturePaths: Path[] = [];
|
|
||||||
const path: Path = [];
|
|
||||||
|
|
||||||
function walk(a: Value): Skeleton<Shape> {
|
|
||||||
if (Capture.isClassOf(a)) {
|
|
||||||
// NB. isUnrestricted relies on the specific order that
|
|
||||||
// capturePaths is computed here.
|
|
||||||
capturePaths.push(path.slice());
|
|
||||||
return walk(a[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Discard.isClassOf(a)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cls = classOf(a);
|
|
||||||
if (cls !== null) {
|
|
||||||
let aa = a as Array<Value>;
|
|
||||||
// ^ We know this is safe because it's either Record or Array
|
|
||||||
let arity = aa.length;
|
|
||||||
let result: NonEmptySkeleton<Shape> = { shape: cls, members: [] };
|
|
||||||
path.push(0);
|
|
||||||
for (let i = 0; i < arity; i++) {
|
|
||||||
path[path.length - 1] = i;
|
|
||||||
result.members.push(walk(step(aa, i)));
|
|
||||||
}
|
|
||||||
path.pop();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
constPaths.push(path);
|
|
||||||
constVals.push(a);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let skeleton = walk(a);
|
|
||||||
|
|
||||||
return { skeleton, constPaths, constVals, capturePaths };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function match(p: Value, v: Value): Array<Value> | false {
|
|
||||||
const captures: Array<Value> = [];
|
|
||||||
|
|
||||||
function walk(p: Value, v: Value): boolean {
|
|
||||||
if (Capture.isClassOf(p)) {
|
|
||||||
if (!walk(p[0], v)) return false;
|
|
||||||
captures.push(v);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Discard.isClassOf(p)) return true;
|
|
||||||
|
|
||||||
const pcls = classOf(p);
|
|
||||||
const vcls = classOf(v);
|
|
||||||
if (pcls !== vcls) return false;
|
|
||||||
|
|
||||||
if (pcls === null) return is(p, v);
|
|
||||||
|
|
||||||
const pp = p as Array<Value>;
|
|
||||||
const vv = v as Array<Value>;
|
|
||||||
// ^ These are safe because classOf yielded nonnull for both
|
|
||||||
|
|
||||||
return pp.every((pv, i) => walk(pv, vv[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return walk(p, v) ? captures : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isCompletelyConcrete(p: Value): boolean {
|
|
||||||
function walk(p: Value): boolean {
|
|
||||||
if (Capture.isClassOf(p)) return false;
|
|
||||||
if (Discard.isClassOf(p)) return false;
|
|
||||||
|
|
||||||
const cls = classOf(p);
|
|
||||||
if (cls === null) return true;
|
|
||||||
return (p as Array<Value>).every(walk);
|
|
||||||
}
|
|
||||||
return walk(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function withoutCaptures(p: Value): Value {
|
|
||||||
function walk(p: Value): Value {
|
|
||||||
if (Capture.isClassOf(p)) return walk(p[0]);
|
|
||||||
if (Discard.isClassOf(p)) return p;
|
|
||||||
|
|
||||||
const cls = classOf(p);
|
|
||||||
if (cls === null) return p;
|
|
||||||
if (Record.isRecord(p)) return Record(p.label, p.map(walk));
|
|
||||||
return (p as Array<Value>).map(walk);
|
|
||||||
}
|
|
||||||
return walk(p);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue