/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2021-2022 Tony Garnock-Jones import { AnyValue, Ref } from './actor.js'; import { Pattern, toPattern } from '../gen/dataspacePatterns.js'; import * as P from './pattern.js'; import { RecordConstructorInfo, is, Record } from '@preserves/core'; import { Meta, Type, GenType, SchemaDefinition } from '@preserves/schema'; export type QuasiValueConstructorInfo = | { constructorInfo: RecordConstructorInfo } | { schema(): SchemaDefinition } | { quasiValue(... args: QuasiValue[]): QuasiValue } ; export type QuasiValue = | { type: 'bind', inner: QuasiValue } | { type: 'discard' } | { type: 'lit', value: AnyValue } | { type: 'rec', label: AnyValue, items: QuasiValue[] } | { type: 'arr', items: QuasiValue[] } | { type: 'dict', entries: [AnyValue, QuasiValue][] } | { type: 'unquote', unquoted: QuasiValue } ; export function bind(p?: QuasiValue): QuasiValue { return { type: 'bind', inner: p ?? _ }; } bind.quasiValue = (inner?: QuasiValue) => rec(Symbol.for('bind'), inner ?? _); export function discard(): QuasiValue { return { type: 'discard' }; } discard.quasiValue = () => rec(Symbol.for('_')); export const _ = discard(); export function lit(value: AnyValue): QuasiValue { return { type: 'lit', value }; } lit.quasiValue = (q: QuasiValue) => rec(Symbol.for('lit'), q); export function drop_lit(patValue: AnyValue): AnyValue | null; export function drop_lit(patValue: AnyValue, parser: (v: AnyValue) => R): R | null; export function drop_lit(patValue: AnyValue, parser?: (v: AnyValue) => R): any { const pat = toPattern(patValue); if (pat === void 0) return null; const val = P.drop_lit(pat); if (val === null) return null; return parser === void 0 ? val : parser(val); } export function rec(label: AnyValue, ... items: QuasiValue[]): QuasiValue { const literals = items.flatMap(i => i.type === 'lit' ? [i.value] : []); if (literals.length === items.length) { return lit(Record(label, literals)); } else { return { type: 'rec', label, items }; } } export function arr(... items: QuasiValue[]): QuasiValue { const literals = items.flatMap(i => i.type === 'lit' ? [i.value] : []); if (literals.length === items.length) { return lit(literals); } else { return { type: 'arr', items }; } } export function dict(... entries: [AnyValue, QuasiValue][]): QuasiValue { return { type: 'dict', entries }; } export function quote(quoted: QuasiValue): QuasiValue { switch (quoted.type) { case 'bind': return quote(bind.quasiValue(quoted.inner)); case 'discard': return quote(discard.quasiValue()); case 'lit': return rec(Symbol.for('lit'), lit(quoted.value)); case 'arr': return rec(Symbol.for('arr'), arr( ... quoted.items.map(quote))); case 'rec': return rec(Symbol.for('rec'), lit(quoted.label), arr( ... 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; } } export function unquote(unquoted: QuasiValue): QuasiValue { return { type: 'unquote', unquoted }; } export function ctor(info: QuasiValueConstructorInfo, ... items: QuasiValue[]): QuasiValue { if ('constructorInfo' in info) { return rec(info.constructorInfo.label, ... items); } else if ('schema' in info) { const definfo = info.schema(); const schema = Meta.asSchema(definfo.schema); const def = schema.definitions.get(definfo.definitionName)!; const defType = GenType.typeForDefinition(r => Type.Type.ref(r.name.description!, null), def); const defNameStr = definfo.definitionName.description!; const ctorArgs = items.slice(); function qLiteral(p: Meta.NamedPattern): AnyValue { if (p._variant === 'anonymous' && p.value._variant === 'SimplePattern' && p.value.value._variant === 'lit') { return p.value.value.value as AnyValue; // TODO ughhhh!! } if (p._variant === 'named') { const qv = qLookup(p.value); if (qv.type === 'lit') { return qv.value; } } throw new Error("Only very simple record labels are supported"); } function qNamed(p: Meta.NamedPattern): QuasiValue { switch (p._variant) { case 'anonymous': return qPattern(p.value); case 'named': return qLookup(p.value); } } function qNamedSimple(p: Meta.NamedSimplePattern): QuasiValue { switch (p._variant) { case 'anonymous': return qSimple(p.value); case 'named': return qLookup(p.value); } } function qLookup(b: Meta.Binding): QuasiValue { if (ctorArgs.length === 0) { throw new Error(`Missing dictionary argument to ${defNameStr}`); } const d = ctorArgs[0]; if (defType.kind === 'record' && defType.fields.size !== 1) { if (d.type !== 'dict') { throw new Error(`Dictionary argument needed to ${defNameStr}`); } for (const [k, p] of d.entries) { if (is(k, b.name.description!)) { return p; } } return _; } else { return d; } } function qPattern(p: Meta.Pattern): QuasiValue { switch (p._variant) { case 'SimplePattern': return qSimple(p.value); case 'CompoundPattern': return qCompound(p.value); } } function qSimple(p: Meta.SimplePattern): QuasiValue { switch (p._variant) { case 'lit': return lit(p.value as AnyValue); // TODO: hate this cast case 'any': case 'atom': case 'embedded': case 'seqof': case 'setof': case 'dictof': case 'Ref': throw new Error("Cannot synthesize value for simple pattern"); } } function qCompound(p: Meta.CompoundPattern): QuasiValue { switch (p._variant) { case 'rec': switch (p.fields._variant) { case 'named': return rec(qLiteral(p.label), ... qArr(qLookup(p.fields.value))); case 'anonymous': return rec(qLiteral(p.label), ... qArr(qPattern(p.fields.value))); } case 'tuple': return arr(... p.patterns.map(qNamed)); case 'tuplePrefix': throw new Error("Cannot use tuplePrefix pattern as dataspace pattern"); case 'dict': { const entries: [AnyValue, QuasiValue][] = []; p.entries.forEach((pp, k) => entries.push([k as AnyValue, // TODO ugh!! qNamedSimple(pp)])); return dict(... entries); } } } function qArr(q: QuasiValue): QuasiValue[] { if (q.type === 'arr') { return q.items; } else if (q.type === 'lit' && Array.isArray(q.value)) { return q.value.map(lit); } else { throw new Error("Array of quasivalues needed"); } } function qTopPattern(p: Meta.Pattern): QuasiValue { switch (p._variant) { case 'SimplePattern': { if (ctorArgs.length === 0) { throw new Error(`Missing argument to ${defNameStr}`); } return ctorArgs[0]; } case 'CompoundPattern': return qCompound(p.value); } } switch (def._variant) { case 'or': { const variant = definfo.variant?.description; if (variant === void 0) { throw new Error("Cannot use union definition as pattern"); } for (const p of [def.pattern0, def.pattern1, ...def.patternN]) { if (p.variantLabel === variant) { return qTopPattern(p.pattern); } } throw new Error(`Unknown variant ${variant} in definition ${defNameStr}`); } case 'and': throw new Error("Cannot use intersection definition as pattern"); case 'Pattern': return qTopPattern(def.value); } } else if ('quasiValue' in info) { return info.quasiValue(... items); } else { ((_i: never) => { throw new Error("INTERNAL ERROR"); })(info); } } export function finish(q: QuasiValue): Pattern { // console.log('--------------------------'); // console.log(q); const p = walk(q); // console.log(p); return p; } function walk(q: QuasiValue): Pattern { switch (q.type) { case 'bind': return P.bind(walk(q.inner)); case 'discard': return P._; case 'lit': return P.lit(q.value); case 'arr': return P.arr(... q.items.map(walk)); case 'rec': return P.rec(q.label, ... q.items.map(walk)); 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'); } }