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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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