syndicate-js/packages/core/src/runtime/pattern.ts

258 lines
8.2 KiB
TypeScript

/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { canonicalString, Dictionary, is, Record, RecordConstructorInfo, Value } 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 '' + v.length;
} else if (Map.isMap(v)) {
return '{}';
} else {
return null;
}
}
export function classOfCtor(v: P.DCompound): Shape {
switch (v._variant) {
case 'rec':
return canonicalString(v.label) + '/' + v.fields.length;
case 'arr':
return '' + v.items.length;
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) + '/' + 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];
}
}
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 walkKey(p: P.Pattern, key: AnyValue) {
path.push(key);
walk(p);
path.pop();
}
function walk(p: P.Pattern) {
switch (p._variant) {
case 'DCompound':
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;
}
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.fromAnyAtom(p.value.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 'DBind': {
captures.push(v);
return walk(p.value.pattern, v);
}
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;
let items: Array<P.Pattern>;
switch (p.value._variant) {
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;
}
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': switch (p.value._variant) {
case 'rec': return p.value.fields.every(isCompletelyConcrete);
case 'arr': return p.value.items.every(isCompletelyConcrete);
case 'dict': {
for (const pp of p.value.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 'DBind': return walk(p.value.pattern);
case 'DDiscard': return p;
case 'DLit': return p;
case 'DCompound':
switch (p.value._variant) {
case 'rec':
return P.Pattern.DCompound(P.DCompound.rec({
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':
return P.Pattern.DCompound(P.DCompound.dict(p.value.entries.mapEntries(
e => [e[0], walk(e[1])])));
}
}
}
return walk(p);
}
//---------------------------------------------------------------------------
// Constructor helpers
export function bind(p?: P.Pattern): P.Pattern {
return P.Pattern.DBind(P.DBind(p ?? _));
}
export function discard(): P.Pattern {
return P.Pattern.DDiscard(P.DDiscard());
}
export const _ = discard();
export function lit(v: AnyValue): P.Pattern {
if (Array.isArray(v)) {
if ('label' in v) {
return P.Pattern.DCompound(P.DCompound.rec({
label: v.label,
fields: v.map(lit),
}));
} else {
return P.Pattern.DCompound(P.DCompound.arr(v.map(lit)));
}
} else if (Map.isMap(v)) {
return P.Pattern.DCompound(P.DCompound.dict(v.mapEntries(
e => [e[0], lit(e[1])])));
} else if (Set.isSet(v)) {
throw new Error("Cannot express literal set in pattern");
} else {
return P.Pattern.DLit(P.DLit(P.asAnyAtom(v)));
}
}
export function drop_lit(p: P.Pattern): AnyValue | null {
const e = new Error();
function walk(p: P.Pattern): AnyValue {
switch (p._variant) {
case 'DCompound':
switch (p.value._variant) {
case 'rec': {
const v = [] as unknown as Record<AnyValue, AnyValue[], Ref>;
v.label = p.value.label;
p.value.fields.forEach(tt => v.push(walk(tt)));
return v;
}
case 'arr':
return p.value.items.map(walk);
case 'dict': {
const v = new Dictionary<Ref, AnyValue>();
p.value.entries.forEach((pp, key) => v.set(key, walk(pp)));
return v;
}
}
case 'DLit':
return P.fromAnyAtom(p.value.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.DCompound(P.DCompound.rec({ label, fields }));
}
export function arr(... patterns: P.Pattern[]): P.Pattern {
return P.Pattern.DCompound(P.DCompound.arr(patterns));
}
export function dict(... entries: [AnyValue, P.Pattern][]): P.Pattern {
return P.Pattern.DCompound(P.DCompound.dict(new Dictionary<Ref, P.Pattern>(entries)));
}