Update to new dataspace pattern language

This commit is contained in:
Tony Garnock-Jones 2024-04-12 10:21:54 +02:00
parent bdb759fe52
commit 96a8367667
3 changed files with 187 additions and 150 deletions

View File

@ -1,7 +1,7 @@
/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { canonicalString, KeyedDictionary, is, Record, RecordConstructorInfo, Value, _iterMap, DictionaryMap, Dictionary } from '@preserves/core'; import { canonicalString, KeyedDictionary, is, Record, RecordConstructorInfo, Value, _iterMap, DictionaryMap, Dictionary, EncodableDictionary } from '@preserves/core';
import { AnyValue, Ref } from './actor.js'; import { AnyValue, Ref } from './actor.js';
import * as P from '../gen/dataspacePatterns.js'; import * as P from '../gen/dataspacePatterns.js';
@ -21,7 +21,7 @@ export function classOfValue(v: any): Shape | null {
} }
} }
export function classOfCtor(v: P.DCompound): Shape { export function classOfCtor(v: P.GroupType): Shape {
switch (v._variant) { switch (v._variant) {
case 'rec': case 'rec':
return canonicalString(v.label); return canonicalString(v.label);
@ -46,44 +46,47 @@ export function step(v: AnyValue, index: AnyValue): AnyValue | undefined {
} }
} }
export type ConstantPositions = {
withValues: Array<Path>,
requiredToExist: Array<Path>,
}
export type PatternAnalysis = { export type PatternAnalysis = {
constPaths: Array<Path>, constPositions: ConstantPositions,
constValues: Array<AnyValue>, constValues: Array<AnyValue>,
capturePaths: Array<Path>, capturePaths: Array<Path>,
}; };
export function analysePattern(p: P.Pattern): PatternAnalysis { export function analysePattern(p: P.Pattern): PatternAnalysis {
const result: PatternAnalysis = { const result: PatternAnalysis = {
constPaths: [], constPositions: {
withValues: [],
requiredToExist: [],
},
constValues: [], constValues: [],
capturePaths: [], capturePaths: [],
}; };
const path: Path = []; const path: Path = [];
function walkKey(p: P.Pattern, key: AnyValue) {
path.push(key);
walk(p);
path.pop();
}
function walk(p: P.Pattern) { function walk(p: P.Pattern) {
switch (p._variant) { switch (p._variant) {
case 'DCompound': case 'group':
switch (p.value._variant) { p.entries.forEach((p, k) => {
case 'rec': p.value.fields.forEach(walkKey); break; path.push(k);
case 'arr': p.value.items.forEach(walkKey); break; walk(p);
case 'dict': p.value.entries.forEach(walkKey); break; path.pop();
} });
break; break;
case 'DBind': case 'bind':
result.capturePaths.push(path.slice()); result.capturePaths.push(path.slice());
walk(p.value.pattern); walk(p.pattern);
break; break;
case 'DDiscard': case 'discard':
result.constPositions.requiredToExist.push(path.slice());
break; break;
case 'DLit': case 'lit':
result.constPaths.push(path.slice()); result.constPositions.withValues.push(path.slice());
result.constValues.push(P.fromAnyAtom(p.value.value)); result.constValues.push(P.fromAnyAtom(p.value));
break; break;
} }
} }
@ -97,32 +100,20 @@ export function match(p: P.Pattern, v: AnyValue): Array<AnyValue> | false {
function walk(p: P.Pattern, v: AnyValue): boolean { function walk(p: P.Pattern, v: AnyValue): boolean {
switch (p._variant) { switch (p._variant) {
case 'DBind': { case 'bind': {
captures.push(v); captures.push(v);
return walk(p.value.pattern, v); return walk(p.pattern, v);
} }
case 'DDiscard': case 'discard':
return true; return true;
case 'DLit': case 'lit':
return is(p.value.value, v); return is(p.value, v);
case 'DCompound': { case 'group': {
const pcls = classOfCtor(p.value); const pcls = classOfCtor(p.type);
const vcls = classOfValue(v); const vcls = classOfValue(v);
if (pcls !== vcls) return false; if (pcls !== vcls) return false;
let items: Array<P.Pattern>; for (const [stepIndex, pp] of p.entries.entries()) {
switch (p.value._variant) { const vv = step(v, stepIndex);
case 'dict':
for (const [stepIndex, pp] of p.value.entries.entries()) {
const vv = step(v, stepIndex);
if (vv === void 0 || !walk(pp, vv)) return false;
}
return true;
case 'rec': items = p.value.fields; break;
case 'arr': items = p.value.items; break;
}
let index = 0;
for (const pp of items) {
const vv = step(v, index++);
if (vv === void 0 || !walk(pp, vv)) return false; if (vv === void 0 || !walk(pp, vv)) return false;
} }
return true; return true;
@ -136,19 +127,14 @@ export function match(p: P.Pattern, v: AnyValue): Array<AnyValue> | false {
export function isCompletelyConcrete(p: P.Pattern): boolean { export function isCompletelyConcrete(p: P.Pattern): boolean {
function walk(p: P.Pattern): boolean { function walk(p: P.Pattern): boolean {
switch (p._variant) { switch (p._variant) {
case 'DBind': return false; case 'bind': return false;
case 'DDiscard': return false; case 'discard': return false;
case 'DLit': return true; case 'lit': return true;
case 'DCompound': switch (p.value._variant) { case 'group':
case 'rec': return p.value.fields.every(isCompletelyConcrete); for (const pp of p.entries.values()) {
case 'arr': return p.value.items.every(isCompletelyConcrete); if (!walk(pp)) return false;
case 'dict': {
for (const pp of p.value.entries.values()) {
if (!walk(pp)) return false;
}
return true;
} }
} return true;
} }
} }
return walk(p); return walk(p);
@ -157,26 +143,19 @@ export function isCompletelyConcrete(p: P.Pattern): boolean {
export function withoutCaptures(p: P.Pattern): P.Pattern { export function withoutCaptures(p: P.Pattern): P.Pattern {
function walk(p: P.Pattern): P.Pattern { function walk(p: P.Pattern): P.Pattern {
switch (p._variant) { switch (p._variant) {
case 'DBind': return walk(p.value.pattern); case 'bind': return walk(p.pattern);
case 'DDiscard': return p; case 'discard': return p;
case 'DLit': return p; case 'lit': return p;
case 'DCompound': case 'group': {
switch (p.value._variant) { const newEntries = new KeyedDictionary<Ref, Value<Ref>, P.Pattern<Ref>>();
case 'rec': for (const [kk, pp] of p.entries) {
return P.Pattern.DCompound(P.DCompound.rec({ newEntries.set(kk, walk(pp));
label: p.value.label,
fields: p.value.fields.map(walk),
}));
case 'arr':
return P.Pattern.DCompound(P.DCompound.arr(p.value.items.map(walk)));
case 'dict': {
const newDict = new KeyedDictionary<Ref, Value<Ref>, P.Pattern<Ref>>();
for (const [k, v] of p.value.entries) {
newDict.set(k, walk(v));
}
return P.Pattern.DCompound(P.DCompound.dict(newDict));
}
} }
return P.Pattern.group({
type: p.type,
entries: newEntries,
});
}
} }
} }
return walk(p); return walk(p);
@ -186,24 +165,33 @@ export function withoutCaptures(p: P.Pattern): P.Pattern {
// Constructor helpers // Constructor helpers
export function bind(p?: P.Pattern): P.Pattern { export function bind(p?: P.Pattern): P.Pattern {
return P.Pattern.DBind(P.DBind(p ?? _)); return P.Pattern.bind(p ?? _);
} }
export function discard(): P.Pattern { export function discard(): P.Pattern {
return P.Pattern.DDiscard(P.DDiscard()); return P.Pattern.discard();
} }
export const _ = discard(); export const _ = discard();
function lit_seq_entries(vs: AnyValue[]): KeyedDictionary<Ref, AnyValue, P.Pattern<Ref>> {
const entries = new KeyedDictionary<Ref, AnyValue, P.Pattern<Ref>>();
vs.forEach((v, i) => entries.set(i, lit(v)));
return entries;
}
export function lit(v: AnyValue): P.Pattern { export function lit(v: AnyValue): P.Pattern {
if (Array.isArray(v)) { if (Array.isArray(v)) {
if ('label' in v) { if ('label' in v) {
return P.Pattern.DCompound(P.DCompound.rec({ return P.Pattern.group({
label: v.label, type: P.GroupType.rec(v.label),
fields: v.map(lit), entries: lit_seq_entries(v),
})); });
} else { } else {
return P.Pattern.DCompound(P.DCompound.arr(v.map(lit))); return P.Pattern.group({
type: P.GroupType.arr(),
entries: lit_seq_entries(v),
});
} }
} }
@ -211,38 +199,58 @@ export function lit(v: AnyValue): P.Pattern {
if (vMap) { if (vMap) {
const r = new KeyedDictionary<Ref, AnyValue, P.Pattern>(); const r = new KeyedDictionary<Ref, AnyValue, P.Pattern>();
vMap.forEach((val, key) => r.set(key, lit(val))); vMap.forEach((val, key) => r.set(key, lit(val)));
return P.Pattern.DCompound(P.DCompound.dict(r)); return P.Pattern.group({
type: P.GroupType.dict(),
entries: r,
});
} }
if (Set.isSet(v)) { if (Set.isSet(v)) {
throw new Error("Cannot express literal set in pattern"); throw new Error("Cannot express literal set in pattern");
} }
return P.Pattern.DLit(P.DLit(P.asAnyAtom(v))); return P.Pattern.lit(P.asAnyAtom(v));
} }
export function drop_lit(p: P.Pattern): AnyValue | null { export function drop_lit(p: P.Pattern): AnyValue | null {
const e = new Error(); const e = new Error();
function walkEntries(target: AnyValue[], entries: EncodableDictionary<Ref, AnyValue, P.Pattern<Ref>>): void {
let maxKey = -1;
for (const key of entries.keys()) {
if (typeof key !== 'number') throw e;
maxKey = Math.max(maxKey, key);
}
for (let i = 0; i < maxKey + 1; i++) {
const p = entries.get(i);
if (p === void 0) throw e;
target.push(walk(p));
}
}
function walk(p: P.Pattern): AnyValue { function walk(p: P.Pattern): AnyValue {
switch (p._variant) { switch (p._variant) {
case 'DCompound': case 'group':
switch (p.value._variant) { switch (p.type._variant) {
case 'rec': { case 'rec': {
const v = [] as unknown as Record<AnyValue, AnyValue[], Ref>; const v = [] as unknown as Record<AnyValue, AnyValue[], Ref>;
v.label = p.value.label; v.label = p.type.label;
p.value.fields.forEach(tt => v.push(walk(tt))); walkEntries(v, p.entries);
return v;
}
case 'arr': {
const v = [] as AnyValue[];
walkEntries(v, p.entries);
return v; return v;
} }
case 'arr':
return p.value.items.map(walk);
case 'dict': { case 'dict': {
const v = new DictionaryMap<Ref, AnyValue>(); const v = new DictionaryMap<Ref, AnyValue>();
p.value.entries.forEach((pp, key) => v.set(key, walk(pp))); p.entries.forEach((pp, key) => v.set(key, walk(pp)));
return v.simplifiedValue(); return v.simplifiedValue();
} }
} }
case 'DLit': case 'lit':
return P.fromAnyAtom(p.value.value); return P.fromAnyAtom(p.value);
default: default:
throw e; throw e;
} }
@ -256,14 +264,22 @@ export function drop_lit(p: P.Pattern): AnyValue | null {
} }
export function rec(label: AnyValue, ... fields: P.Pattern[]): P.Pattern { export function rec(label: AnyValue, ... fields: P.Pattern[]): P.Pattern {
return P.Pattern.DCompound(P.DCompound.rec({ label, fields })); return P.Pattern.group({
type: P.GroupType.rec(label),
entries: new KeyedDictionary<Ref, AnyValue, P.Pattern>(fields.map((p, i) => [i, p])),
});
} }
export function arr(... patterns: P.Pattern[]): P.Pattern { export function arr(... patterns: P.Pattern[]): P.Pattern {
return P.Pattern.DCompound(P.DCompound.arr(patterns)); return P.Pattern.group({
type: P.GroupType.arr(),
entries: new KeyedDictionary<Ref, AnyValue, P.Pattern>(patterns.map((p, i) => [i, p])),
});
} }
export function dict(... entries: [AnyValue, P.Pattern][]): P.Pattern { export function dict(... entries: [AnyValue, P.Pattern][]): P.Pattern {
return P.Pattern.DCompound(P.DCompound.dict( return P.Pattern.group({
new KeyedDictionary<Ref, AnyValue, P.Pattern>(entries))); type: P.GroupType.dict(),
entries: new KeyedDictionary<Ref, AnyValue, P.Pattern>(entries),
});
} }

View File

@ -2,7 +2,7 @@
/// SPDX-FileCopyrightText: Copyright © 2021-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com> /// SPDX-FileCopyrightText: Copyright © 2021-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { AnyValue, Ref } from './actor.js'; import { AnyValue, Ref } from './actor.js';
import { Pattern, toPattern } from '../gen/dataspacePatterns.js'; import { Pattern, toPattern, GroupType, fromGroupType } from '../gen/dataspacePatterns.js';
import * as P from './pattern.js'; import * as P from './pattern.js';
import { RecordConstructorInfo, is, Record, JsDictionary } from '@preserves/core'; import { RecordConstructorInfo, is, Record, JsDictionary } from '@preserves/core';
import { Meta, Type, GenType, SchemaDefinition } from '@preserves/schema'; import { Meta, Type, GenType, SchemaDefinition } from '@preserves/schema';
@ -17,9 +17,7 @@ export type QuasiValue =
| { type: 'bind', inner: QuasiValue } | { type: 'bind', inner: QuasiValue }
| { type: 'discard' } | { type: 'discard' }
| { type: 'lit', value: AnyValue } | { type: 'lit', value: AnyValue }
| { type: 'rec', label: AnyValue, items: QuasiValue[] } | { type: 'group', groupType: GroupType, entries: [AnyValue, QuasiValue][] }
| { type: 'arr', items: QuasiValue[] }
| { type: 'dict', entries: [AnyValue, QuasiValue][] }
| { type: 'unquote', unquoted: QuasiValue } | { type: 'unquote', unquoted: QuasiValue }
; ;
@ -58,7 +56,8 @@ export function rec(label: AnyValue, ... items: QuasiValue[]): QuasiValue {
if (literals.length === items.length) { if (literals.length === items.length) {
return lit(Record(label, literals)); return lit(Record(label, literals));
} else { } else {
return { type: 'rec', label, items }; const entries = items.map((v, i) => [i, v] as [number, QuasiValue]);
return { type: 'group', groupType: GroupType.rec(label), entries };
} }
} }
@ -67,12 +66,13 @@ export function arr(... items: QuasiValue[]): QuasiValue {
if (literals.length === items.length) { if (literals.length === items.length) {
return lit(literals); return lit(literals);
} else { } else {
return { type: 'arr', items }; const entries = items.map((v, i) => [i, v] as [number, QuasiValue]);
return { type: 'group', groupType: GroupType.arr(), entries };
} }
} }
export function dict(... entries: [AnyValue, QuasiValue][]): QuasiValue { export function dict(... entries: [AnyValue, QuasiValue][]): QuasiValue {
return { type: 'dict', entries }; return { type: 'group', groupType: GroupType.dict(), entries };
} }
export function quote(quoted: QuasiValue): QuasiValue { export function quote(quoted: QuasiValue): QuasiValue {
@ -80,12 +80,9 @@ export function quote(quoted: QuasiValue): QuasiValue {
case 'bind': return quote(bind.quasiValue(quoted.inner)); case 'bind': return quote(bind.quasiValue(quoted.inner));
case 'discard': return quote(discard.quasiValue()); case 'discard': return quote(discard.quasiValue());
case 'lit': return rec(Symbol.for('lit'), lit(quoted.value)); case 'lit': return rec(Symbol.for('lit'), lit(quoted.value));
case 'arr': return rec(Symbol.for('arr'), arr( case 'group':
... quoted.items.map(quote))); return rec(Symbol.for('group'), lit(fromGroupType(quoted.groupType)), dict(
case 'rec': return rec(Symbol.for('rec'), lit(quoted.label), arr( ... quoted.entries.map(([k, qq]) => [k, quote(qq)] as [AnyValue, QuasiValue])));
... quoted.items.map(quote)));
case 'dict': return rec(Symbol.for('dict'), dict(
... quoted.entries.map(([k, qq]) => [k, quote(qq)] as [AnyValue, QuasiValue])));
case 'unquote': return quoted.unquoted; case 'unquote': return quoted.unquoted;
} }
} }
@ -94,6 +91,20 @@ export function unquote(unquoted: QuasiValue): QuasiValue {
return { type: 'unquote', unquoted }; return { type: 'unquote', unquoted };
} }
function entriesSeq<V>(entries: [AnyValue, V][], defaultValue: V): V[] {
let maxKey = -1;
const result: V[] = [];
for (const [k, q] of entries) {
if (typeof k !== 'number') throw new Error("Invalid sequence quasivalue entries key");
result[k] = q;
maxKey = Math.max(maxKey, k);
}
for (let i = 0; i < maxKey + 1; i++) {
if (result[i] === void 0) result[i] = defaultValue;
}
return result;
}
export function ctor(info: QuasiValueConstructorInfo, ... items: QuasiValue[]): QuasiValue { export function ctor(info: QuasiValueConstructorInfo, ... items: QuasiValue[]): QuasiValue {
if ('constructorInfo' in info) { if ('constructorInfo' in info) {
return rec(info.constructorInfo.label, ... items); return rec(info.constructorInfo.label, ... items);
@ -158,7 +169,7 @@ export function ctor(info: QuasiValueConstructorInfo, ... items: QuasiValue[]):
if (d.type === 'discard') { if (d.type === 'discard') {
return d; return d;
} }
if (d.type !== 'dict') { if (d.type !== 'group' || d.groupType._variant !== 'dict') {
throw new Error(`Dictionary argument needed to ${defNameStr}`); throw new Error(`Dictionary argument needed to ${defNameStr}`);
} }
for (const [k, p] of d.entries) { for (const [k, p] of d.entries) {
@ -217,8 +228,8 @@ export function ctor(info: QuasiValueConstructorInfo, ... items: QuasiValue[]):
} }
function qArr(q: QuasiValue): QuasiValue[] { function qArr(q: QuasiValue): QuasiValue[] {
if (q.type === 'arr') { if (q.type === 'group' && q.groupType._variant === 'arr') {
return q.items; return entriesSeq(q.entries, discard());
} else if (q.type === 'lit' && Array.isArray(q.value)) { } else if (q.type === 'lit' && Array.isArray(q.value)) {
return q.value.map(lit); return q.value.map(lit);
} else { } else {
@ -284,10 +295,12 @@ function walk(q: QuasiValue): Pattern {
case 'bind': return P.bind(walk(q.inner)); case 'bind': return P.bind(walk(q.inner));
case 'discard': return P._; case 'discard': return P._;
case 'lit': return P.lit(q.value); case 'lit': return P.lit(q.value);
case 'arr': return P.arr(... q.items.map(walk)); case 'group': switch (q.groupType._variant) {
case 'rec': return P.rec(q.label, ... q.items.map(walk)); case 'arr': return P.arr(... entriesSeq(q.entries, discard()).map(walk));
case 'dict': return P.dict(... q.entries.map( case 'rec': return P.rec(q.groupType.label, ... entriesSeq(q.entries, discard()).map(walk));
([k, qq]) => [k, walk(qq)] as [AnyValue, Pattern])); case 'dict': return P.dict(... q.entries.map(
([k, qq]) => [k, walk(qq)] as [AnyValue, Pattern]));
}
case 'unquote': throw new Error('Unexpected unquote in QuasiValue'); case 'unquote': throw new Error('Unexpected unquote in QuasiValue');
} }
} }

View File

@ -6,7 +6,7 @@ import { AnyValue, Assertion, Ref } 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 * as P from '../gen/dataspacePatterns.js';
import { Path, analysePattern, classOfCtor, classOfValue, step, Shape } from './pattern.js'; import { Path, analysePattern, classOfCtor, classOfValue, step, Shape, ConstantPositions } from './pattern.js';
enum EventType { enum EventType {
ADDED = +1, ADDED = +1,
@ -31,13 +31,15 @@ export class Index<T> {
readonly root: Node<T> = new Node(new Continuation(new Set())); readonly root: Node<T> = new Node(new Continuation(new Set()));
addObserver(pattern: P.Pattern, observer: IndexObserver<T>, parameter: T) { addObserver(pattern: P.Pattern, observer: IndexObserver<T>, parameter: T) {
let {constPaths, constValues, capturePaths} = analysePattern(pattern); let {constPositions, constValues, capturePaths} = analysePattern(pattern);
const continuation = this.root.extend(pattern); const continuation = this.root.extend(pattern);
let constValMap = continuation.leafMap.get(constPaths); let constValMap = continuation.leafMap.get(constPositions);
if (!constValMap) { if (!constValMap) {
constValMap = new KeyedDictionary(); constValMap = new KeyedDictionary();
continuation.cachedAssertions.forEach((a) => { continuation.cachedAssertions.forEach((a) => {
const key = projectPaths(a, constPaths); if (projectPaths(a, constPositions.requiredToExist) === void 0) return;
const key = projectPaths(a, constPositions.withValues);
if (key === void 0) return;
let leaf = constValMap!.get(key); let leaf = constValMap!.get(key);
if (!leaf) { if (!leaf) {
leaf = new Leaf(); leaf = new Leaf();
@ -45,7 +47,7 @@ export class Index<T> {
} }
leaf.cachedAssertions.add(a); leaf.cachedAssertions.add(a);
}); });
continuation.leafMap.set(constPaths, constValMap); continuation.leafMap.set(constPositions, constValMap);
} }
let leaf = constValMap.get(constValues); let leaf = constValMap.get(constValues);
if (!leaf) { if (!leaf) {
@ -55,8 +57,10 @@ export class Index<T> {
let observerGroup = leaf.observerGroups.get(capturePaths); let observerGroup = leaf.observerGroups.get(capturePaths);
if (!observerGroup) { if (!observerGroup) {
const cachedCaptures = new Bag<Ref, Array<AnyValue>>(); 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)); const vs = projectPaths(a, capturePaths);
if (vs !== void 0) cachedCaptures._items.update(vs, n => n! + 1, 0);
});
observerGroup = new ObserverGroup(cachedCaptures); observerGroup = new ObserverGroup(cachedCaptures);
leaf.observerGroups.set(capturePaths, observerGroup); leaf.observerGroups.set(capturePaths, observerGroup);
} }
@ -65,9 +69,9 @@ export class Index<T> {
} }
removeObserver(pattern: P.Pattern, observer: IndexObserver<T>, parameter: T) { removeObserver(pattern: P.Pattern, observer: IndexObserver<T>, parameter: T) {
let {constPaths, constValues, capturePaths} = analysePattern(pattern); let {constPositions, constValues, capturePaths} = analysePattern(pattern);
const continuation = this.root.extend(pattern); const continuation = this.root.extend(pattern);
let constValMap = continuation.leafMap.get(constPaths); let constValMap = continuation.leafMap.get(constPositions);
if (!constValMap) return; if (!constValMap) return;
let leaf = constValMap.get(constValues); let leaf = constValMap.get(constValues);
if (!leaf) return; if (!leaf) return;
@ -82,7 +86,7 @@ export class Index<T> {
constValMap.delete(constValues); constValMap.delete(constValues);
} }
if (constValMap.size === 0) { if (constValMap.size === 0) {
continuation.leafMap.delete(constPaths); continuation.leafMap.delete(constPositions);
} }
} }
@ -168,19 +172,19 @@ class Node<T> {
function walkNode(node: Node<T>, popCount: number, stepIndex: AnyValue, p: P.Pattern): [number, Node<T>] function walkNode(node: Node<T>, popCount: number, stepIndex: AnyValue, p: P.Pattern): [number, Node<T>]
{ {
switch (p._variant) { switch (p._variant) {
case 'DDiscard': case 'discard':
case 'DLit': case 'lit':
return [popCount, node]; return [popCount, node];
case 'DBind': case 'bind':
return walkNode(node, popCount, stepIndex, p.value.pattern); return walkNode(node, popCount, stepIndex, p.pattern);
case 'DCompound': { case 'group': {
const selector: Selector = [popCount, stepIndex]; const selector: Selector = [popCount, stepIndex];
let table = node.edges.get(selector); let table = node.edges.get(selector);
if (!table) { if (!table) {
table = {}; table = {};
node.edges.set(selector, table); node.edges.set(selector, table);
} }
let cls = classOfCtor(p.value); let cls = classOfCtor(p.type);
let nextNode = table[cls]; let nextNode = table[cls];
if (!nextNode) { if (!nextNode) {
nextNode = new Node(new Continuation( nextNode = new Node(new Continuation(
@ -189,16 +193,11 @@ class Node<T> {
table[cls] = nextNode; table[cls] = nextNode;
} }
popCount = 0; popCount = 0;
function walkKey(pp: P.Pattern, stepIndex: AnyValue) { p.entries.forEach((pp, stepIndex) => {
path.push(stepIndex); path.push(stepIndex);
[popCount, nextNode] = walkNode(nextNode, popCount, stepIndex, pp); [popCount, nextNode] = walkNode(nextNode, popCount, stepIndex, pp);
path.pop(); path.pop();
} });
switch (p.value._variant) {
case 'rec': p.value.fields.forEach(walkKey); break;
case 'arr': p.value.items.forEach(walkKey); break;
case 'dict': p.value.entries.forEach(walkKey); break;
}
return [popCount + 1, nextNode]; return [popCount + 1, nextNode];
} }
} }
@ -226,8 +225,10 @@ class Node<T> {
function walkContinuation(continuation: Continuation<T>) { function walkContinuation(continuation: Continuation<T>) {
m_cont(continuation, outerValue); m_cont(continuation, outerValue);
continuation.leafMap.forEach((constValMap, constPaths) => { continuation.leafMap.forEach((constValMap, constPositions) => {
let constValues = projectPaths(outerValue, constPaths); if (projectPaths(outerValue, constPositions.requiredToExist) === void 0) return;
let constValues = projectPaths(outerValue, constPositions.withValues);
if (constValues === void 0) return;
let leaf = constValMap.get(constValues); let leaf = constValMap.get(constValues);
if (!leaf && operation === EventType.ADDED) { if (!leaf && operation === EventType.ADDED) {
leaf = new Leaf(); leaf = new Leaf();
@ -236,12 +237,13 @@ class Node<T> {
if (leaf) { if (leaf) {
m_leaf(leaf, outerValue); m_leaf(leaf, outerValue);
leaf.observerGroups.forEach((observerGroup, capturePaths) => { leaf.observerGroups.forEach((observerGroup, capturePaths) => {
m_observerGroup(observerGroup, projectPaths(outerValue, capturePaths)); const vs = projectPaths(outerValue, capturePaths);
if (vs !== void 0) m_observerGroup(observerGroup, vs);
}); });
if (operation === EventType.REMOVED && leaf.isEmpty()) { if (operation === EventType.REMOVED && leaf.isEmpty()) {
constValMap.delete(constValues); constValMap.delete(constValues);
if (constValMap.size === 0) { if (constValMap.size === 0) {
continuation.leafMap.delete(constPaths); continuation.leafMap.delete(constPositions);
} }
} }
} }
@ -267,7 +269,7 @@ class Node<T> {
class Continuation<T> { class Continuation<T> {
readonly cachedAssertions: Set<Ref>; readonly cachedAssertions: Set<Ref>;
readonly leafMap: KeyedDictionary<Ref, Array<Path>, KeyedDictionary<Ref, Assertion, Leaf<T>>> = new KeyedDictionary(); readonly leafMap: KeyedDictionary<Ref, ConstantPositions, KeyedDictionary<Ref, Assertion, Leaf<T>>> = new KeyedDictionary();
constructor(cachedAssertions: Set<Ref>) { constructor(cachedAssertions: Set<Ref>) {
this.cachedAssertions = cachedAssertions; this.cachedAssertions = cachedAssertions;
@ -320,15 +322,21 @@ class ObserverGroup<T> {
} }
} }
// Total by assumption that path is valid for v function projectPath(v: AnyValue, path: Path): AnyValue | undefined {
function projectPath(v: AnyValue, path: Path): AnyValue {
for (let index of path) { for (let index of path) {
v = step(v, index)!; const next = step(v, index);
if (next === void 0) return void 0;
v = next;
} }
return v; return v;
} }
// Total by assumption that paths are valid for v function projectPaths(v: AnyValue, paths: Array<Path>): AnyValue[] | undefined {
function projectPaths(v: AnyValue, paths: Array<Path>): AnyValue[] { const result = [];
return paths.map((path) => projectPath(v, path)); for (const path of paths) {
const w = projectPath(v, path);
if (w === void 0) return void 0;
result.push(w);
}
return result;
} }