2021-12-09 17:53:41 +00:00
|
|
|
import { AnyValue, Ref } from './actor.js';
|
|
|
|
import { Pattern, fromPattern } from '../gen/dataspacePatterns.js';
|
|
|
|
import * as P from './pattern.js';
|
|
|
|
import { Value, RecordConstructorInfo, stringify, is } from '@preserves/core';
|
|
|
|
import { Meta } from '@preserves/schema';
|
|
|
|
|
|
|
|
export type DefinitionOrVariantInfo = {
|
|
|
|
schema: Value<unknown>,
|
|
|
|
imports: unknown,
|
|
|
|
definitionName: symbol,
|
|
|
|
};
|
|
|
|
|
|
|
|
export type QuasiValueConstructorInfo =
|
|
|
|
| { constructorInfo: RecordConstructorInfo<AnyValue, Ref> }
|
|
|
|
| { schema(): DefinitionOrVariantInfo }
|
|
|
|
| { 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 rec(label: AnyValue, ... items: QuasiValue[]): QuasiValue {
|
|
|
|
return { type: 'rec', label, items };
|
|
|
|
}
|
|
|
|
|
|
|
|
export function arr(... items: QuasiValue[]): QuasiValue {
|
|
|
|
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('compound'),
|
|
|
|
rec(Symbol.for('arr'), lit(quoted.items.length)),
|
|
|
|
dict(... quoted.items.map((qq, i) => [i, quote(qq)] as [AnyValue, QuasiValue])));
|
|
|
|
case 'rec': return rec(
|
|
|
|
Symbol.for('compound'),
|
|
|
|
rec(Symbol.for('rec'), lit(quoted.label), lit(quoted.items.length)),
|
|
|
|
dict(... quoted.items.map((qq, i) => [i, quote(qq)] as [AnyValue, QuasiValue])));
|
|
|
|
case 'dict': return rec(
|
|
|
|
Symbol.for('compound'),
|
|
|
|
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 defNameStr = definfo.definitionName.description!;
|
|
|
|
const ctorArgs = items.slice();
|
|
|
|
|
|
|
|
function qLiteral(p: Meta.NamedPattern<unknown>): 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<unknown>): QuasiValue {
|
|
|
|
switch (p._variant) {
|
|
|
|
case 'anonymous': return qPattern(p.value);
|
|
|
|
case 'named': return qLookup(p.value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function qNamedSimple(p: Meta.NamedSimplePattern<unknown>): QuasiValue {
|
|
|
|
switch (p._variant) {
|
|
|
|
case 'anonymous': return qSimple(p.value);
|
|
|
|
case 'named': return qLookup(p.value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function qLookup(b: Meta.Binding<unknown>): QuasiValue {
|
|
|
|
if (ctorArgs.length === 0) {
|
|
|
|
throw new Error(`Missing dictionary argument to ${defNameStr}`);
|
|
|
|
}
|
|
|
|
const d = ctorArgs[0];
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2021-12-09 21:13:05 +00:00
|
|
|
return _;
|
2021-12-09 17:53:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function qPattern(p: Meta.Pattern<unknown>): QuasiValue {
|
|
|
|
switch (p._variant) {
|
|
|
|
case 'SimplePattern': return qSimple(p.value);
|
|
|
|
case 'CompoundPattern': return qCompound(p.value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function qSimple(p: Meta.SimplePattern<unknown>): 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<unknown>): 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') {
|
|
|
|
throw new Error("Array of quasivalues needed");
|
|
|
|
}
|
|
|
|
return q.items;
|
|
|
|
}
|
|
|
|
|
|
|
|
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(require('util').inspect(q, {depth: null}));
|
|
|
|
const p = walk(q);
|
|
|
|
// console.log(stringify(fromPattern(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');
|
|
|
|
}
|
|
|
|
}
|