Initial QuasiValue support
This commit is contained in:
parent
a1c8203b5d
commit
4ec02591c0
|
@ -12,6 +12,7 @@ export * from './runtime/bag.js';
|
|||
export * as Dataflow from './runtime/dataflow.js';
|
||||
export * from './runtime/dataspace.js';
|
||||
export * as Pattern from './runtime/pattern.js';
|
||||
export * as QuasiValue from './runtime/quasivalue.js';
|
||||
export * from './runtime/randomid.js';
|
||||
export * as Rewrite from './runtime/rewrite.js';
|
||||
export * as Skeleton from './runtime/skeleton.js';
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
throw new Error(`Missing named parameter ${stringify(b.name.description!)} to ${defNameStr}`);
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue