Initial QuasiValue support

This commit is contained in:
Tony Garnock-Jones 2021-12-09 18:53:41 +01:00
parent a1c8203b5d
commit 4ec02591c0
2 changed files with 239 additions and 0 deletions

View File

@ -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';

View File

@ -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');
}
}