From 4ec02591c08a104724ee9df163b70b54e47263af Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 9 Dec 2021 18:53:41 +0100 Subject: [PATCH] Initial QuasiValue support --- packages/core/src/index.ts | 1 + packages/core/src/runtime/quasivalue.ts | 238 ++++++++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 packages/core/src/runtime/quasivalue.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4eca739..a2aa724 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -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'; diff --git a/packages/core/src/runtime/quasivalue.ts b/packages/core/src/runtime/quasivalue.ts new file mode 100644 index 0000000..2f621cb --- /dev/null +++ b/packages/core/src/runtime/quasivalue.ts @@ -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, + imports: unknown, + definitionName: symbol, +}; + +export type QuasiValueConstructorInfo = + | { constructorInfo: RecordConstructorInfo } + | { 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): 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 (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): 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') { + 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'); + } +}