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",
|
||||
"private": true,
|
||||
"workspaces": ["packages/*", "packages/*/examples/*/"],
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"packages/*/examples/*/"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
||||
"@types/jest": "^26.0.19",
|
||||
"@types/node": "^14.14.20",
|
||||
"esm": "^3.2.25",
|
||||
"jest": "^26.6.3",
|
||||
"lerna": "^3.22.1",
|
||||
"rollup": "^2.36.1",
|
||||
"rollup-plugin-sourcemaps": "^0.6.3",
|
||||
"rollup-plugin-terser": "^7.0.2",
|
||||
"ts-jest": "^26.4.4",
|
||||
"ts-node": "^9.1.1",
|
||||
"ts-node-dev": "^1.1.1",
|
||||
"typescript": "^4.1.3"
|
||||
"@rollup/plugin-node-resolve": "^13.0",
|
||||
"@types/jest": "^27.0",
|
||||
"@types/node": "^14",
|
||||
"esm": "^3.2",
|
||||
"jest": "^27.4",
|
||||
"lerna": "^4.0",
|
||||
"rollup": "^2.60",
|
||||
"rollup-plugin-sourcemaps": "^0.6",
|
||||
"rollup-plugin-terser": "^7.0",
|
||||
"ts-jest": "^27.0",
|
||||
"ts-node": "^10.4",
|
||||
"ts-node-dev": "^1.1",
|
||||
"typescript": "^4.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,3 @@ export const BootProc = '__SYNDICATE__bootProc';
|
|||
//
|
||||
export type NonEmptySkeleton<Shape> = { shape: Shape, members: Skeleton<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-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 { queueTask } from './task.js';
|
||||
|
||||
|
@ -49,9 +50,13 @@ type OutboundMap = Map<Handle, OutboundAssertion>;
|
|||
let nextActorId = 0;
|
||||
export const __setNextActorId = (v: number) => nextActorId = v;
|
||||
|
||||
export type DataflowGraph = Graph<DataflowBlock, Cell>;
|
||||
export type DataflowBlock = (t: Turn) => void;
|
||||
|
||||
export class Actor {
|
||||
readonly id = nextActorId++;
|
||||
readonly root: Facet;
|
||||
_dataflowGraph: DataflowGraph | null = null;
|
||||
exitReason: ExitReason = null;
|
||||
readonly exitHooks: Array<LocalAction> = [];
|
||||
|
||||
|
@ -60,6 +65,14 @@ export class Actor {
|
|||
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 {
|
||||
this.exitHooks.push(a);
|
||||
}
|
||||
|
@ -73,6 +86,11 @@ export class Actor {
|
|||
this.exitHooks.forEach(hook => hook(t));
|
||||
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 {
|
||||
|
@ -147,8 +165,8 @@ let nextTurnId = 0;
|
|||
|
||||
export class Turn {
|
||||
readonly id = nextTurnId++;
|
||||
readonly activeFacet: Facet;
|
||||
queues: Map<Facet, LocalAction[]> | null;
|
||||
_activeFacet: Facet;
|
||||
queues: Map<Actor, LocalAction[]> | null;
|
||||
|
||||
static for(facet: Facet, f: LocalAction, zombieTurn = false): void {
|
||||
if (!zombieTurn) {
|
||||
|
@ -158,22 +176,27 @@ export class Turn {
|
|||
const t = new Turn(facet);
|
||||
try {
|
||||
f(t);
|
||||
t.queues!.forEach((q, facet) => queueTask(() => Turn.for(facet, t=> q.forEach(f => f(t)))));
|
||||
t.queues = null;
|
||||
facet.actor.repairDataflowGraph(t);
|
||||
t.deliver();
|
||||
} catch (err) {
|
||||
Turn.for(facet.actor.root, t => facet.actor.terminateWith(t, { ok: false, err }));
|
||||
}
|
||||
}
|
||||
|
||||
private constructor(facet: Facet, queues = new Map<Facet, LocalAction[]>()) {
|
||||
this.activeFacet = facet;
|
||||
private constructor(facet: Facet, queues = new Map<Actor, LocalAction[]>()) {
|
||||
this._activeFacet = facet;
|
||||
this.queues = queues;
|
||||
}
|
||||
|
||||
get activeFacet(): Facet {
|
||||
return this._activeFacet;
|
||||
}
|
||||
|
||||
_inFacet(facet: Facet, f: LocalAction): void {
|
||||
const t = new Turn(facet, this.queues!);
|
||||
f(t);
|
||||
t.queues = null;
|
||||
const saved = this._activeFacet;
|
||||
this._activeFacet = facet;
|
||||
f(this);
|
||||
this._activeFacet = saved;
|
||||
}
|
||||
|
||||
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 }));
|
||||
}
|
||||
|
||||
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 {
|
||||
const h = nextHandle++;
|
||||
this._assert(ref, assertion, h);
|
||||
|
@ -238,8 +286,10 @@ export class Turn {
|
|||
}
|
||||
}
|
||||
|
||||
replace(ref: Ref, h: Handle | undefined, assertion: Assertion | undefined): Handle | undefined {
|
||||
const newHandle = assertion === void 0 ? void 0 : this.assert(ref, assertion);
|
||||
replace(ref: Ref | undefined, h: Handle | undefined, assertion: Assertion | undefined): Handle | undefined {
|
||||
const newHandle = (assertion === void 0 || ref === void 0)
|
||||
? void 0
|
||||
: this.assert(ref, assertion);
|
||||
this.retract(h);
|
||||
return newHandle;
|
||||
}
|
||||
|
@ -271,14 +321,14 @@ export class Turn {
|
|||
if (this.queues === null) {
|
||||
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 {
|
||||
if (this.queues !== null) {
|
||||
throw new Error("Attempt to freshen a non-stale Turn");
|
||||
}
|
||||
Turn.for(this.activeFacet, a);
|
||||
deliver() {
|
||||
this.queues!.forEach((q, actor) =>
|
||||
queueTask(() => Turn.for(actor.root, t => q.forEach(f => f(t)))));
|
||||
this.queues = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,4 +5,3 @@
|
|||
|
||||
export type NonEmptySkeleton<Shape> = { shape: Shape, members: Skeleton<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).
|
||||
|
||||
import { Value, Set, Dictionary, GenericEmbedded } from '@preserves/core';
|
||||
import { Value, Set, Dictionary, KeyedDictionary, KeyedSet } from '@preserves/core';
|
||||
|
||||
export enum ChangeDescription {
|
||||
PRESENT_TO_ABSENT = -1,
|
||||
|
@ -12,19 +12,19 @@ export enum ChangeDescription {
|
|||
PRESENT_TO_PRESENT = 2,
|
||||
}
|
||||
|
||||
export class Bag<T extends object = GenericEmbedded> {
|
||||
_items: Dictionary<T, number>;
|
||||
export class Bag<T, V extends Value<T> = Value<T>> {
|
||||
_items: KeyedDictionary<V, number, T>;
|
||||
|
||||
constructor(s?: Set<T>) {
|
||||
this._items = new Dictionary();
|
||||
constructor(s?: KeyedSet<V, T>) {
|
||||
this._items = new KeyedDictionary();
|
||||
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;
|
||||
}
|
||||
|
||||
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 newCount = oldCount + delta;
|
||||
if (clamp) {
|
||||
|
@ -45,10 +45,10 @@ export class Bag<T extends object = GenericEmbedded> {
|
|||
}
|
||||
|
||||
clear() {
|
||||
this._items = new Dictionary();
|
||||
this._items = new KeyedDictionary();
|
||||
}
|
||||
|
||||
includes(key: Value<T>): boolean {
|
||||
includes(key: V): boolean {
|
||||
return this._items.has(key);
|
||||
}
|
||||
|
||||
|
@ -56,24 +56,24 @@ export class Bag<T extends object = GenericEmbedded> {
|
|||
return this._items.size;
|
||||
}
|
||||
|
||||
keys(): IterableIterator<Value<T>> {
|
||||
keys(): IterableIterator<V> {
|
||||
return this._items.keys();
|
||||
}
|
||||
|
||||
entries(): IterableIterator<[Value<T>, number]> {
|
||||
entries(): IterableIterator<[V, number]> {
|
||||
return this._items.entries();
|
||||
}
|
||||
|
||||
forEach(f: (count: number, value: Value<T>) => void) {
|
||||
this._items.forEach(f);
|
||||
forEach(f: (count: number, value: V) => void) {
|
||||
this._items.forEach((c, v) => f(c, v));
|
||||
}
|
||||
|
||||
snapshot(): Dictionary<T, number> {
|
||||
snapshot(): KeyedDictionary<V, number, T> {
|
||||
return this._items.clone();
|
||||
}
|
||||
|
||||
clone(): Bag<T> {
|
||||
const b = new Bag<T>();
|
||||
clone(): Bag<T, V> {
|
||||
const b = new Bag<T, V>();
|
||||
b._items = this._items.clone();
|
||||
return b;
|
||||
}
|
||||
|
|
|
@ -3,15 +3,16 @@
|
|||
|
||||
// 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';
|
||||
|
||||
export interface PropertyOptions<ObjectId> {
|
||||
objectId: ObjectId;
|
||||
noopGuard?: (oldValue: any, newValue: any) => boolean;
|
||||
};
|
||||
export interface ObservingGraph<ObjectId> {
|
||||
recordObservation(objectId: ObjectId): void;
|
||||
recordDamage(objectId: ObjectId): void;
|
||||
}
|
||||
|
||||
export class Graph<SubjectId, ObjectId> {
|
||||
export class Graph<SubjectId, ObjectId> implements ObservingGraph<ObjectId> {
|
||||
readonly edgesForward: FlexMap<ObjectId, FlexSet<SubjectId>>;
|
||||
readonly edgesReverse: FlexMap<SubjectId, FlexSet<ObjectId>>;
|
||||
readonly subjectIdCanonicalizer: Canonicalizer<SubjectId>;
|
||||
|
@ -90,36 +91,70 @@ export class Graph<SubjectId, ObjectId> {
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineObservableProperty<T, K extends keyof T>(
|
||||
obj: T,
|
||||
prop: K,
|
||||
value: T[K],
|
||||
options: PropertyOptions<ObjectId>)
|
||||
{
|
||||
const { objectId, noopGuard } = options;
|
||||
Object.defineProperty(obj, prop, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => {
|
||||
this.recordObservation(objectId);
|
||||
return value;
|
||||
},
|
||||
set: (newValue) => {
|
||||
if (!noopGuard || !noopGuard(value, newValue)) {
|
||||
this.recordDamage(objectId);
|
||||
value = newValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.recordDamage(objectId);
|
||||
return objectId;
|
||||
let __nextCellId = 0;
|
||||
|
||||
export abstract class Cell {
|
||||
readonly id: string = '' + (__nextCellId++);
|
||||
readonly graph: ObservingGraph<Cell>;
|
||||
__value: unknown;
|
||||
|
||||
static readonly canonicalizer: Canonicalizer<Cell> = v => v.id;
|
||||
|
||||
constructor(graph: ObservingGraph<Cell>, initial: unknown) {
|
||||
this.graph = graph;
|
||||
this.__value = initial;
|
||||
}
|
||||
|
||||
static newScope<T, R extends T>(o: T): R {
|
||||
const Scope: { new (): R, prototype: T } =
|
||||
(function Scope () {}) as unknown as ({ new (): R, prototype: T });
|
||||
Scope.prototype = o;
|
||||
return new Scope();
|
||||
observe(): unknown {
|
||||
this.graph.recordObservation(this);
|
||||
return this.__value;
|
||||
}
|
||||
|
||||
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-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { Value, is, Set } from '@preserves/core';
|
||||
|
||||
import * as Skeleton from './skeleton.js';
|
||||
import { Bag, ChangeDescription } from './bag.js';
|
||||
import * as Dataflow from './dataflow.js';
|
||||
import { IdentitySet, IdentityMap } from './idcoll.js';
|
||||
import { IdentityMap } from '@preserves/core';
|
||||
import { Index } from './skeleton.js';
|
||||
import { Assertion, Entity, Handle, Turn } from './actor.js';
|
||||
import { Observe, toObserve } from '../gen/dataspace.js';
|
||||
|
||||
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) {
|
||||
// return this.index.adjustAssertion(a, count);
|
||||
// }
|
||||
|
||||
// subscribe(handler: Skeleton.Analysis) {
|
||||
// this.index.addHandler(handler, handler.callback!);
|
||||
// }
|
||||
|
||||
// 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");
|
||||
assert(turn: Turn, v: Assertion, handle: Handle): void {
|
||||
this.index.addAssertion(turn, v);
|
||||
const o = toObserve(v);
|
||||
if (o !== void 0) {
|
||||
this.index.addObserver(turn, o.pattern, o.observer);
|
||||
}
|
||||
this.handleMap.set(handle, [v, o]);
|
||||
}
|
||||
|
||||
retract(turn: Turn, upstreamHandle: Handle): void {
|
||||
console.log(preserves`ds ${turn.activeFacet.id} retract ${upstreamHandle}`);
|
||||
throw new Error("Full dataspaces not implemented");
|
||||
retract(turn: Turn, handle: Handle): void {
|
||||
const entry = this.handleMap.get(handle);
|
||||
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 {
|
||||
console.log(preserves`ds ${turn.activeFacet.id} message ${rec}`);
|
||||
throw new Error("Full dataspaces not implemented");
|
||||
message(turn: Turn, v: Assertion): void {
|
||||
this.index.deliverMessage(turn, v);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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-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;
|
||||
use std::convert::TryInto;
|
||||
export type Shape = string;
|
||||
|
||||
#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub enum PathStep {
|
||||
Index(usize),
|
||||
Key(_Any),
|
||||
}
|
||||
|
||||
pub type Path = Vec<PathStep>;
|
||||
pub type Paths = Vec<Path>;
|
||||
|
||||
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,
|
||||
}
|
||||
export function classOfValue(v: any): Shape | null {
|
||||
if (Record.isRecord(v)) {
|
||||
return constructorInfoSignature(Record.constructorInfo(v));
|
||||
} else if (Array.isArray(v)) {
|
||||
return '' + v.length;
|
||||
} else if (Map.isMap(v)) {
|
||||
return '{}';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
impl Analyzer {
|
||||
fn walk_step(&mut self, path: &mut Path, s: PathStep, p: &Pattern) {
|
||||
path.push(s);
|
||||
self.walk(path, p);
|
||||
path.pop();
|
||||
}
|
||||
|
||||
fn walk(&mut self, path: &mut Path, p: &Pattern) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
export function classOfCtor(v: P.DCompound): Shape {
|
||||
switch (v._variant) {
|
||||
case 'rec':
|
||||
return canonicalString(v.ctor.label) + '/' + v.ctor.arity;
|
||||
case 'arr':
|
||||
return '' + v.ctor.arity;
|
||||
case 'dict':
|
||||
return '{}';
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: NestedValue> Pattern<N> {
|
||||
pub fn match_value(&self, value: &N) -> Option<Vec<N>> {
|
||||
let mut matcher = PatternMatcher::new();
|
||||
if matcher.run(self, value) {
|
||||
Some(matcher.captures)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
// Called by generated code in addition to functions in this module
|
||||
export function constructorInfoSignature(ci: RecordConstructorInfo<Value>): string {
|
||||
return canonicalString(ci.label) + '/' + ci.arity;
|
||||
}
|
||||
|
||||
export function step(v: AnyValue, index: AnyValue): AnyValue | undefined {
|
||||
if (Map.isMap(v)) {
|
||||
return v.get(index);
|
||||
} else {
|
||||
return (v as Array<AnyValue> /* includes Record! */)[index as number];
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: NestedValue> PatternMatcher<N> {
|
||||
fn new() -> Self {
|
||||
PatternMatcher {
|
||||
captures: Vec::new(),
|
||||
export type PatternAnalysis = {
|
||||
constPaths: Array<Path>,
|
||||
constValues: Array<AnyValue>,
|
||||
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 {
|
||||
match pattern {
|
||||
Pattern::DDiscard(_) => true,
|
||||
Pattern::DBind(b) => {
|
||||
self.captures.push(value.clone());
|
||||
self.run(&b.pattern, value)
|
||||
walk(p);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function match(p: P.Pattern, v: AnyValue): Array<AnyValue> | false {
|
||||
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,
|
||||
Pattern::DCompound(b) => match &**b {
|
||||
DCompound::Rec { ctor, members } => {
|
||||
let arity = (&ctor.arity).try_into().expect("reasonable arity");
|
||||
match value.value().as_record(Some(arity)) {
|
||||
None => false,
|
||||
Some(r) => {
|
||||
for (i, p) in members.iter() {
|
||||
let i: usize = i.try_into().expect("reasonable index");
|
||||
if !self.run(p, &r.fields()[i]) {
|
||||
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
|
||||
}
|
||||
}
|
||||
case 'DDiscard':
|
||||
return true;
|
||||
case 'DLit':
|
||||
return is(p.value.value, v);
|
||||
case 'DCompound': {
|
||||
const pcls = classOfCtor(p.value);
|
||||
const vcls = classOfValue(v);
|
||||
if (pcls !== vcls) return false;
|
||||
for (const [stepIndex, pp] of p.value.members.entries()) {
|
||||
const vv = step(v, stepIndex);
|
||||
if (vv === void 0) return false;
|
||||
if (!walk(pp, vv)) return false;
|
||||
}
|
||||
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-FileCopyrightText: Copyright © 2016-2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
||||
|
||||
import { IdentitySet } from './idcoll.js';
|
||||
import { is, Value, Record, Set, Dictionary, canonicalString, RecordConstructorInfo, GenericEmbedded } from '@preserves/core';
|
||||
|
||||
import { Set, Dictionary, KeyedDictionary, IdentityMap } from '@preserves/core';
|
||||
import { AnyValue, Assertion, Handle, Ref, Turn } from './actor.js';
|
||||
import { Bag, ChangeDescription } from './bag.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';
|
||||
|
||||
export enum EventType {
|
||||
enum EventType {
|
||||
ADDED = +1,
|
||||
REMOVED = -1,
|
||||
MESSAGE = 0,
|
||||
}
|
||||
|
||||
export type HandlerCallback = (eventType: EventType, bindings: Array<Value>) => void;
|
||||
|
||||
export type Shape = string;
|
||||
|
||||
export interface Analysis {
|
||||
skeleton: Skeleton<Shape>;
|
||||
constPaths: Array<Path>;
|
||||
constVals: Array<Value>;
|
||||
capturePaths: Array<Path>;
|
||||
callback?: HandlerCallback;
|
||||
}
|
||||
|
||||
const _nop = () => {};
|
||||
const _nop = function() {};
|
||||
|
||||
export class Index {
|
||||
readonly allAssertions: Bag = new Bag();
|
||||
readonly allAssertions: Bag<Ref> = new Bag();
|
||||
readonly root: Node = new Node(new Continuation(new Set()));
|
||||
|
||||
addHandler(analysisResults: Analysis, callback: HandlerCallback) {
|
||||
let {skeleton, constPaths, constVals, capturePaths} = analysisResults;
|
||||
const continuation = this.root.extend(skeleton);
|
||||
addObserver(t: Turn, pattern: P.Pattern, observer: Ref) {
|
||||
let {constPaths, constValues, capturePaths} = analysePattern(pattern);
|
||||
const continuation = this.root.extend(pattern);
|
||||
let constValMap = continuation.leafMap.get(constPaths);
|
||||
if (!constValMap) {
|
||||
constValMap = new Dictionary();
|
||||
|
@ -51,46 +37,51 @@ export class Index {
|
|||
});
|
||||
continuation.leafMap.set(constPaths, constValMap);
|
||||
}
|
||||
let leaf = constValMap.get(constVals);
|
||||
let leaf = constValMap.get(constValues);
|
||||
if (!leaf) {
|
||||
leaf = new Leaf();
|
||||
constValMap.set(constVals, leaf);
|
||||
constValMap.set(constValues, leaf);
|
||||
}
|
||||
let handler = leaf.handlerMap.get(capturePaths);
|
||||
if (!handler) {
|
||||
const cachedCaptures = new Bag();
|
||||
let observerGroup = leaf.observerGroups.get(capturePaths);
|
||||
if (!observerGroup) {
|
||||
const cachedCaptures = new Bag<Ref, Array<AnyValue>>();
|
||||
leaf.cachedAssertions.forEach((a) =>
|
||||
cachedCaptures._items.update(projectPaths(a, capturePaths), n => n! + 1, 0));
|
||||
handler = new Handler(cachedCaptures);
|
||||
leaf.handlerMap.set(capturePaths, handler);
|
||||
observerGroup = new ObserverGroup(cachedCaptures);
|
||||
leaf.observerGroups.set(capturePaths, observerGroup);
|
||||
}
|
||||
handler.callbacks.add(callback);
|
||||
handler.cachedCaptures.forEach((_count, captures) =>
|
||||
callback(EventType.ADDED, captures as Array<Value>));
|
||||
const captureMap: KeyedDictionary<Array<AnyValue>, Handle, Ref> = new KeyedDictionary();
|
||||
observerGroup.observers.set(observer, captureMap);
|
||||
observerGroup.cachedCaptures.forEach((_count, captures) =>
|
||||
captureMap.set(captures, t.assert(observer, captures)));
|
||||
}
|
||||
|
||||
removeHandler(analysisResults: Analysis, callback: HandlerCallback) {
|
||||
let {skeleton, constPaths, constVals, capturePaths} = analysisResults;
|
||||
const continuation = this.root.extend(skeleton);
|
||||
removeObserver(t: Turn, pattern: P.Pattern, observer: Ref) {
|
||||
let {constPaths, constValues, capturePaths} = analysePattern(pattern);
|
||||
const continuation = this.root.extend(pattern);
|
||||
let constValMap = continuation.leafMap.get(constPaths);
|
||||
if (!constValMap) return;
|
||||
let leaf = constValMap.get(constVals);
|
||||
let leaf = constValMap.get(constValues);
|
||||
if (!leaf) return;
|
||||
let handler = leaf.handlerMap.get(capturePaths);
|
||||
if (!handler) return;
|
||||
handler.callbacks.delete(callback);
|
||||
if (handler.callbacks.size === 0) {
|
||||
leaf.handlerMap.delete(capturePaths);
|
||||
let observerGroup = leaf.observerGroups.get(capturePaths);
|
||||
if (!observerGroup) return;
|
||||
const captureMap = observerGroup.observers.get(observer);
|
||||
if (captureMap) {
|
||||
captureMap.forEach((handle, _captures) => t.retract(handle));
|
||||
observerGroup.observers.delete(observer);
|
||||
}
|
||||
if (observerGroup.observers.size === 0) {
|
||||
leaf.observerGroups.delete(capturePaths);
|
||||
}
|
||||
if (leaf.isEmpty()) {
|
||||
constValMap.delete(constVals);
|
||||
constValMap.delete(constValues);
|
||||
}
|
||||
if (constValMap.size === 0) {
|
||||
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);
|
||||
switch (net) {
|
||||
case ChangeDescription.ABSENT_TO_PRESENT:
|
||||
|
@ -101,7 +92,8 @@ export class Index {
|
|||
(l, v) => l.cachedAssertions.add(v),
|
||||
(h, vs) => {
|
||||
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;
|
||||
|
||||
|
@ -113,111 +105,114 @@ export class Index {
|
|||
(l, v) => l.cachedAssertions.delete(v),
|
||||
(h, vs) => {
|
||||
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;
|
||||
}
|
||||
return net;
|
||||
}
|
||||
|
||||
addAssertion(v: Value) {
|
||||
this.adjustAssertion(v, +1);
|
||||
addAssertion(t: Turn, v: Assertion) {
|
||||
this.adjustAssertion(t, v, +1);
|
||||
}
|
||||
|
||||
removeAssertion(v: Value) {
|
||||
this.adjustAssertion(v, -1);
|
||||
removeAssertion(t: Turn, v: Assertion) {
|
||||
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) =>
|
||||
h.callbacks.forEach(cb => cb(EventType.MESSAGE, vs)));
|
||||
h.observers.forEach((_captureMap, observer) => t.message(observer, vs)));
|
||||
}
|
||||
}
|
||||
|
||||
type Selector = [number, AnyValue];
|
||||
|
||||
class Node {
|
||||
readonly continuation: Continuation;
|
||||
readonly edges: { [selector: string]: { [shape: string]: Node } } = {};
|
||||
readonly edges: KeyedDictionary<Selector, { [shape: string]: Node }, Ref> = new KeyedDictionary();
|
||||
|
||||
constructor(continuation: Continuation) {
|
||||
this.continuation = continuation;
|
||||
}
|
||||
|
||||
extend(skeleton: Skeleton<Shape>): Continuation {
|
||||
extend(p: P.Pattern): Continuation {
|
||||
const path: Path = [];
|
||||
|
||||
function walkNode(node: Node,
|
||||
popCount: number,
|
||||
index: number,
|
||||
skeleton: Skeleton<Shape>): [number, Node]
|
||||
function walkNode(node: Node, popCount: number, stepIndex: AnyValue, p: P.Pattern): [number, Node]
|
||||
{
|
||||
if (skeleton === null) {
|
||||
return [popCount, node];
|
||||
} else {
|
||||
const selector = '' + popCount + ',' + index;
|
||||
const cls = skeleton.shape;
|
||||
let table = node.edges[selector];
|
||||
if (!table) {
|
||||
table = {};
|
||||
node.edges[selector] = table;
|
||||
}
|
||||
let nextNode = table[cls];
|
||||
if (!nextNode) {
|
||||
nextNode = new Node(new Continuation(
|
||||
node.continuation.cachedAssertions.filter(
|
||||
(a) => classOf(projectPath(a, path)) === cls)));
|
||||
table[cls] = nextNode;
|
||||
}
|
||||
popCount = 0;
|
||||
index = 0;
|
||||
path.push(index);
|
||||
skeleton.members.forEach((member) => {
|
||||
[popCount, nextNode] = walkNode(nextNode, popCount, index, member);
|
||||
index++;
|
||||
switch (p._variant) {
|
||||
case 'DDiscard':
|
||||
case 'DLit':
|
||||
return [popCount, node];
|
||||
case 'DBind':
|
||||
return walkNode(node, popCount, stepIndex, p.value.pattern);
|
||||
case 'DCompound': {
|
||||
const selector: Selector = [popCount, stepIndex];
|
||||
let table = node.edges.get(selector);
|
||||
if (!table) {
|
||||
table = {};
|
||||
node.edges.set(selector, table);
|
||||
}
|
||||
let cls = classOfCtor(p.value);
|
||||
let nextNode = table[cls];
|
||||
if (!nextNode) {
|
||||
nextNode = new Node(new Continuation(
|
||||
node.continuation.cachedAssertions.filter(
|
||||
(a) => classOfValue(projectPath(a, path)) === cls)));
|
||||
table[cls] = nextNode;
|
||||
}
|
||||
popCount = 0;
|
||||
p.value.members.forEach((member, stepIndex) => {
|
||||
path.push(stepIndex);
|
||||
[popCount, nextNode] = walkNode(nextNode, popCount, stepIndex, member);
|
||||
path.pop();
|
||||
});
|
||||
path.pop();
|
||||
path.push(index);
|
||||
});
|
||||
path.pop();
|
||||
return [popCount + 1, nextNode];
|
||||
return [popCount + 1, nextNode];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return walkNode(this, 0, 0, skeleton)[1].continuation;
|
||||
return walkNode(this, 0, 0, p)[1].continuation;
|
||||
}
|
||||
|
||||
modify(operation: EventType,
|
||||
outerValue: Value,
|
||||
m_cont: (c: Continuation, v: Value) => void,
|
||||
m_leaf: (l: Leaf, v: Value) => void,
|
||||
m_handler: (h: Handler, vs: Array<Value>) => void)
|
||||
outerValue: Assertion,
|
||||
m_cont: (c: Continuation, v: Assertion) => void,
|
||||
m_leaf: (l: Leaf, v: Assertion) => 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);
|
||||
Object.entries(node.edges).forEach(([selectorStr, table]) => {
|
||||
const selector = parseSelector(selectorStr);
|
||||
const nextStack = Stack.dropNonEmpty(termStack, selector.popCount);
|
||||
const nextValue = step(nextStack.item, selector.index);
|
||||
const nextClass = classOf(nextValue);
|
||||
node.edges.forEach((table, [popCount, stepIndex]) => {
|
||||
const nextStack = Stack.dropNonEmpty(termStack, popCount);
|
||||
const nextValue = step(nextStack.item, stepIndex);
|
||||
const nextClass = classOfValue(nextValue);
|
||||
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) {
|
||||
m_cont(continuation, outerValue);
|
||||
continuation.leafMap.forEach((constValMap, constPaths) => {
|
||||
let constVals = projectPaths(outerValue, constPaths as Array<Path>);
|
||||
let leaf = constValMap.get(constVals);
|
||||
let constValues = projectPaths(outerValue, constPaths);
|
||||
let leaf = constValMap.get(constValues);
|
||||
if (!leaf && operation === EventType.ADDED) {
|
||||
leaf = new Leaf();
|
||||
constValMap.set(constVals, leaf);
|
||||
constValMap.set(constValues, leaf);
|
||||
}
|
||||
if (leaf) {
|
||||
m_leaf(leaf, outerValue);
|
||||
leaf.handlerMap.forEach((handler, capturePaths) => {
|
||||
m_handler(handler, projectPaths(outerValue, capturePaths as Array<Path>));
|
||||
leaf.observerGroups.forEach((observerGroup, capturePaths) => {
|
||||
m_observerGroup(observerGroup, projectPaths(outerValue, capturePaths));
|
||||
});
|
||||
if (operation === EventType.REMOVED && leaf.isEmpty()) {
|
||||
constValMap.delete(constVals);
|
||||
constValMap.delete(constValues);
|
||||
if (constValMap.size === 0) {
|
||||
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 {
|
||||
readonly cachedAssertions: Set;
|
||||
readonly leafMap: Dictionary<GenericEmbedded, Dictionary<GenericEmbedded, Leaf>> = new Dictionary();
|
||||
readonly cachedAssertions: Set<Ref>;
|
||||
readonly leafMap: KeyedDictionary<Array<Path>, Dictionary<Ref, Leaf>, Ref> = new KeyedDictionary();
|
||||
|
||||
constructor(cachedAssertions: Set) {
|
||||
constructor(cachedAssertions: Set<Ref>) {
|
||||
this.cachedAssertions = cachedAssertions;
|
||||
}
|
||||
}
|
||||
|
||||
class Leaf {
|
||||
readonly cachedAssertions: Set = new Set();
|
||||
readonly handlerMap: Dictionary<GenericEmbedded, Handler> = new Dictionary();
|
||||
readonly cachedAssertions: Set<Ref> = new Set();
|
||||
readonly observerGroups: KeyedDictionary<Array<Path>, ObserverGroup, Ref> = new KeyedDictionary();
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.cachedAssertions.size === 0 && this.handlerMap.size === 0;
|
||||
return this.cachedAssertions.size === 0 && this.observerGroups.size === 0;
|
||||
}
|
||||
}
|
||||
|
||||
class Handler {
|
||||
readonly cachedCaptures: Bag;
|
||||
readonly callbacks: IdentitySet<HandlerCallback> = new IdentitySet();
|
||||
class ObserverGroup {
|
||||
readonly cachedCaptures: Bag<Ref, Array<AnyValue>>;
|
||||
readonly observers: IdentityMap<Ref, KeyedDictionary<Array<AnyValue>, Handle, Ref>> = new IdentityMap();
|
||||
|
||||
constructor(cachedCaptures: Bag) {
|
||||
constructor(cachedCaptures: Bag<Ref, Array<AnyValue>>) {
|
||||
this.cachedCaptures = cachedCaptures;
|
||||
}
|
||||
}
|
||||
|
||||
export function constructorInfoSignature(ci: RecordConstructorInfo<Value>): string {
|
||||
return canonicalString(ci.label) + '/' + ci.arity;
|
||||
}
|
||||
|
||||
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) {
|
||||
// Total by assumption that path is valid for v
|
||||
function projectPath(v: AnyValue, path: Path): AnyValue {
|
||||
for (let index of path) {
|
||||
v = step(v as Array<Value>, index);
|
||||
v = step(v, index)!;
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
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