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

272 lines
9.8 KiB
TypeScript

/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2021-2022 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");
}
}
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');
}
}