258 lines
9.3 KiB
TypeScript
258 lines
9.3 KiB
TypeScript
/// SPDX-License-Identifier: GPL-3.0-or-later
|
|
/// SPDX-FileCopyrightText: Copyright © 2021 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
|
|
|
|
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<AnyValue, Ref> }
|
|
| { 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<R>(patValue: AnyValue, parser: (v: AnyValue) => R): R | null;
|
|
export function drop_lit<R>(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");
|
|
}
|
|
}
|
|
|
|
switch (def._variant) {
|
|
case 'or': throw new Error("Cannot use union definition as pattern");
|
|
case 'and': throw new Error("Cannot use intersection definition as pattern");
|
|
case 'Pattern': {
|
|
const p = def.value;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
} 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');
|
|
}
|
|
}
|