Much progress

This commit is contained in:
Tony Garnock-Jones 2021-12-02 14:40:24 +01:00
parent d2f5c947ac
commit d51af436f5
14 changed files with 3173 additions and 4343 deletions

View File

@ -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"
} }
} }

View File

@ -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>;

View File

@ -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);
} }
} }

View File

@ -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>;

View File

@ -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;
} }

View File

@ -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}>`;
} }
} }

View File

@ -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");
} }
} }

View File

@ -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;

View File

@ -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);
} }

View File

@ -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);
}

0
packages/syndicatec/bin/syndicate-maptool.js Normal file → Executable file
View File

0
packages/syndicatec/bin/syndicatec.js Normal file → Executable file
View File

0
packages/tsc/bin/syndicate-tsc.js Normal file → Executable file
View File

6584
yarn.lock

File diff suppressed because it is too large Load Diff