/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2016-2024 Tony Garnock-Jones 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; 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): string { return canonicalString(ci.label); } export function step(v: AnyValue, index: AnyValue): AnyValue | undefined { const vMap = Dictionary.asMap(v); if (vMap) { return vMap.get(index); } else { return (v as Array /* includes Record! */)[index as number]; } } export type ConstantPositions = { withValues: Array, requiredToExist: Array, } export type PatternAnalysis = { constPositions: ConstantPositions, constValues: Array, capturePaths: Array, }; 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 | false { const captures: Array = []; 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, P.Pattern>(); 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> { const entries = new KeyedDictionary>(); 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(v); if (vMap) { const r = new KeyedDictionary(); 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>): 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; 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(); 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(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(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(entries), }); }