/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2023 Tony Garnock-Jones 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; 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): 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 /* includes Record! */)[index as number]; } } export type PatternAnalysis = { constPaths: Array, constValues: Array, capturePaths: Array, }; 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 | false { const captures: Array = []; 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; 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; 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(); 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(entries))); }