286 lines
8.4 KiB
TypeScript
286 lines
8.4 KiB
TypeScript
/// SPDX-License-Identifier: GPL-3.0-or-later
|
|
/// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
|
|
import { canonicalString, KeyedDictionary, is, Record, RecordConstructorInfo, Value, _iterMap, DictionaryMap, Dictionary, EncodableDictionary } from '@preserves/core';
|
|
import { AnyValue, Ref } from './actor.js';
|
|
import * as P from '../gen/dataspacePatterns.js';
|
|
|
|
export type Path = Array<AnyValue>;
|
|
|
|
export type Shape = string;
|
|
|
|
export function classOfValue(v: any): Shape | null {
|
|
if (Record.isRecord(v)) {
|
|
return constructorInfoSignature(Record.constructorInfo(v));
|
|
} else if (Array.isArray(v)) {
|
|
return '[]';
|
|
} else if (Dictionary.isDictionary(v)) {
|
|
return '{}';
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function classOfCtor(v: P.GroupType): Shape {
|
|
switch (v._variant) {
|
|
case 'rec':
|
|
return canonicalString(v.label);
|
|
case 'arr':
|
|
return '[]';
|
|
case 'dict':
|
|
return '{}';
|
|
}
|
|
}
|
|
|
|
// Called by generated code in addition to functions in this module
|
|
export function constructorInfoSignature(ci: RecordConstructorInfo<Value>): string {
|
|
return canonicalString(ci.label);
|
|
}
|
|
|
|
export function step(v: AnyValue, index: AnyValue): AnyValue | undefined {
|
|
const vMap = Dictionary.asMap<Ref>(v);
|
|
if (vMap) {
|
|
return vMap.get(index);
|
|
} else {
|
|
return (v as Array<AnyValue> /* includes Record! */)[index as number];
|
|
}
|
|
}
|
|
|
|
export type ConstantPositions = {
|
|
withValues: Array<Path>,
|
|
requiredToExist: Array<Path>,
|
|
}
|
|
|
|
export type PatternAnalysis = {
|
|
constPositions: ConstantPositions,
|
|
constValues: Array<AnyValue>,
|
|
capturePaths: Array<Path>,
|
|
};
|
|
|
|
export function analysePattern(p: P.Pattern): PatternAnalysis {
|
|
const result: PatternAnalysis = {
|
|
constPositions: {
|
|
withValues: [],
|
|
requiredToExist: [],
|
|
},
|
|
constValues: [],
|
|
capturePaths: [],
|
|
};
|
|
const path: Path = [];
|
|
|
|
function walk(p: P.Pattern) {
|
|
switch (p._variant) {
|
|
case 'group':
|
|
p.entries.forEach((p, k) => {
|
|
path.push(k);
|
|
walk(p);
|
|
path.pop();
|
|
});
|
|
break;
|
|
case 'bind':
|
|
result.capturePaths.push(path.slice());
|
|
walk(p.pattern);
|
|
break;
|
|
case 'discard':
|
|
result.constPositions.requiredToExist.push(path.slice());
|
|
break;
|
|
case 'lit':
|
|
result.constPositions.withValues.push(path.slice());
|
|
result.constValues.push(P.fromAnyAtom(p.value));
|
|
break;
|
|
}
|
|
}
|
|
|
|
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 'bind': {
|
|
captures.push(v);
|
|
return walk(p.pattern, v);
|
|
}
|
|
case 'discard':
|
|
return true;
|
|
case 'lit':
|
|
return is(p.value, v);
|
|
case 'group': {
|
|
const pcls = classOfCtor(p.type);
|
|
const vcls = classOfValue(v);
|
|
if (pcls !== vcls) return false;
|
|
for (const [stepIndex, pp] of p.entries.entries()) {
|
|
const vv = step(v, stepIndex);
|
|
if (vv === void 0 || !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 'bind': return false;
|
|
case 'discard': return false;
|
|
case 'lit': return true;
|
|
case 'group':
|
|
for (const pp of p.entries.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 'bind': return walk(p.pattern);
|
|
case 'discard': return p;
|
|
case 'lit': return p;
|
|
case 'group': {
|
|
const newEntries = new KeyedDictionary<Ref, Value<Ref>, P.Pattern<Ref>>();
|
|
for (const [kk, pp] of p.entries) {
|
|
newEntries.set(kk, walk(pp));
|
|
}
|
|
return P.Pattern.group({
|
|
type: p.type,
|
|
entries: newEntries,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return walk(p);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Constructor helpers
|
|
|
|
export function bind(p?: P.Pattern): P.Pattern {
|
|
return P.Pattern.bind(p ?? _);
|
|
}
|
|
|
|
export function discard(): P.Pattern {
|
|
return P.Pattern.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 {
|
|
if (Array.isArray(v)) {
|
|
if ('label' in v) {
|
|
return P.Pattern.group({
|
|
type: P.GroupType.rec(v.label),
|
|
entries: lit_seq_entries(v),
|
|
});
|
|
} else {
|
|
return P.Pattern.group({
|
|
type: P.GroupType.arr(),
|
|
entries: lit_seq_entries(v),
|
|
});
|
|
}
|
|
}
|
|
|
|
const vMap = Dictionary.asMap<Ref>(v);
|
|
if (vMap) {
|
|
const r = new KeyedDictionary<Ref, AnyValue, P.Pattern>();
|
|
vMap.forEach((val, key) => r.set(key, lit(val)));
|
|
return P.Pattern.group({
|
|
type: P.GroupType.dict(),
|
|
entries: r,
|
|
});
|
|
}
|
|
|
|
if (Set.isSet(v)) {
|
|
throw new Error("Cannot express literal set in pattern");
|
|
}
|
|
|
|
return P.Pattern.lit(P.asAnyAtom(v));
|
|
}
|
|
|
|
export function drop_lit(p: P.Pattern): AnyValue | null {
|
|
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 {
|
|
switch (p._variant) {
|
|
case 'group':
|
|
switch (p.type._variant) {
|
|
case 'rec': {
|
|
const v = [] as unknown as Record<AnyValue, AnyValue[], Ref>;
|
|
v.label = p.type.label;
|
|
walkEntries(v, p.entries);
|
|
return v;
|
|
}
|
|
case 'arr': {
|
|
const v = [] as AnyValue[];
|
|
walkEntries(v, p.entries);
|
|
return v;
|
|
}
|
|
case 'dict': {
|
|
const v = new DictionaryMap<Ref, AnyValue>();
|
|
p.entries.forEach((pp, key) => v.set(key, walk(pp)));
|
|
return v.simplifiedValue();
|
|
}
|
|
}
|
|
case 'lit':
|
|
return P.fromAnyAtom(p.value);
|
|
default:
|
|
throw e;
|
|
}
|
|
}
|
|
try {
|
|
return walk(p);
|
|
} catch (ee) {
|
|
if (ee == e) return null;
|
|
throw ee;
|
|
}
|
|
}
|
|
|
|
export function rec(label: AnyValue, ... fields: P.Pattern[]): P.Pattern {
|
|
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 {
|
|
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 {
|
|
return P.Pattern.group({
|
|
type: P.GroupType.dict(),
|
|
entries: new KeyedDictionary<Ref, AnyValue, P.Pattern>(entries),
|
|
});
|
|
}
|