Interpreter
This commit is contained in:
parent
9c7770a54f
commit
f6ddf0ca3b
|
@ -4,6 +4,9 @@ export * from './reader';
|
|||
export * from './compiler';
|
||||
export * from './reflection';
|
||||
|
||||
export { SchemaInterpreter } from './interpreter';
|
||||
export * as Interpreter from './interpreter';
|
||||
|
||||
export * as Host from './host';
|
||||
export * as Meta from './meta';
|
||||
export * as Type from './compiler/type';
|
||||
|
|
|
@ -0,0 +1,509 @@
|
|||
import { EncodableDictionary, KeyedDictionary, Dictionary, Value, is, Record, Float, Bytes, isEmbedded, isSequence, Set, Atom, Embedded, merge as plainMerge, Preservable, PreserveWritable, _iterMap, stringify } from '@preserves/core';
|
||||
import * as M from './meta';
|
||||
import * as H from './host';
|
||||
|
||||
export const UNIT: true = true;
|
||||
|
||||
export type Parsed<V> = Atom | V | Parsed<V>[] | DictOf<V> | Bindings<V>;
|
||||
export type TopParsed<V> = Atom | V | Parsed<V>[] | DictOf<V> | TopBindings<V>;
|
||||
|
||||
export type Top<V> = Preservable<V> & PreserveWritable<V> & { __as_preserve__(): Value<V> };
|
||||
|
||||
export type DictOf<V> = EncodableDictionary<Parsed<V>, Parsed<V>, V>;
|
||||
|
||||
export type BindingName = string;
|
||||
export type Bindings<V> = { [key: BindingName]: Parsed<V> };
|
||||
export type TopBindings<V> = Bindings<V> & Top<V>;
|
||||
|
||||
export type SingleConstructor<V> = (input: any) => Parsed<V>;
|
||||
export type MultipleConstructors<V> = { [key: string]: SingleConstructor<V> };
|
||||
export type DefinitionConstructors<V> =
|
||||
| { single: SingleConstructor<V> }
|
||||
| { multiple: MultipleConstructors<V> }
|
||||
;
|
||||
|
||||
export namespace Bindings {
|
||||
export function empty<V>(): Bindings<V> {
|
||||
return {};
|
||||
}
|
||||
export function single<V>(k: BindingName, v: Parsed<V>): Bindings<V> {
|
||||
const bs = empty<V>();
|
||||
bs[k] = v;
|
||||
return bs;
|
||||
}
|
||||
export function merge<V>(... vs: Bindings<V>[]): Bindings<V> {
|
||||
const acc = empty<V>();
|
||||
for (const v of vs) {
|
||||
Object.entries(v).forEach(([kw, vw]) => acc[kw] = vw);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
||||
export type DynField<V> =
|
||||
| { type: 'simple', value: Parsed<V> }
|
||||
| { type: 'compound', values: Bindings<V> }
|
||||
;
|
||||
export namespace DynField {
|
||||
export function unwrap<V>(f: DynField<V>): Parsed<V> {
|
||||
if (f.type === 'simple') return f.value;
|
||||
return f.values;
|
||||
}
|
||||
export function unwrap_compound<V>(f: DynField<V>): Bindings<V> {
|
||||
if (f.type === 'simple') throw new Error("Cannot unwrap DynField.simple to compound fields");
|
||||
return f.values;
|
||||
}
|
||||
export function simple<V>(value: Parsed<V>): DynField<V> {
|
||||
return { type: 'simple', value };
|
||||
}
|
||||
export function maybeSimple<V>(value: Parsed<V> | null): DynField<V> {
|
||||
return value === null ? compound(Bindings.empty()) : simple(value);
|
||||
}
|
||||
export function compound<V>(values: Bindings<V>): DynField<V> {
|
||||
return { type: 'compound', values };
|
||||
}
|
||||
export function promote<V>(f: DynField<V>, key?: symbol): Bindings<V> {
|
||||
if (f.type === 'compound') return f.values;
|
||||
return key ? Bindings.single(M.jsId(key.description!), f.value) : Bindings.empty();
|
||||
}
|
||||
}
|
||||
|
||||
function optmap<A,B>(a: A | undefined, f: (a: A) => B): B | undefined {
|
||||
if (a === void 0) return void 0;
|
||||
return f(a);
|
||||
}
|
||||
|
||||
export type Unparseable<V> = TopParsed<V>;
|
||||
export type Unparser<V> = (v: Parsed<V>) => Value<V>;
|
||||
export type UnparserCompound<V> = (v: Bindings<V>) => Value<V>;
|
||||
|
||||
export class SchemaInterpreter<V> {
|
||||
activeModule: M.ModulePath = [];
|
||||
unparserCache: { [key: string]: [Unparser<V>] } = {};
|
||||
|
||||
constructor (
|
||||
public env: M.Modules<V> = new KeyedDictionary(),
|
||||
public mergeEmbeddeds: (a: V, b: V) => V | undefined = (_a, _b) => void 0,
|
||||
) {}
|
||||
|
||||
_withModule<R>(modulePath: M.ModulePath, f: () => R): R {
|
||||
const saved = this.activeModule;
|
||||
if (modulePath.length > 0) this.activeModule = modulePath;
|
||||
try {
|
||||
return f();
|
||||
} finally {
|
||||
if (modulePath.length > 0) this.activeModule = saved;
|
||||
}
|
||||
}
|
||||
|
||||
_findModule(modulePath: M.ModulePath): { resolved: M.ModulePath, schema: M.Schema<V> } {
|
||||
const prefix = this.activeModule.slice();
|
||||
while (true) {
|
||||
const probe = [... prefix, ... modulePath];
|
||||
const schema = this.env.get(probe);
|
||||
if (schema !== void 0) {
|
||||
return { resolved: probe, schema };
|
||||
}
|
||||
if (prefix.length === 0) {
|
||||
throw new Error(`No such preserves-schema module: ${M.formatModulePath(modulePath)}, referred to in module ${M.formatModulePath(this.activeModule)}`);
|
||||
}
|
||||
prefix.pop();
|
||||
}
|
||||
}
|
||||
|
||||
_lookup<R>(modulePath: M.ModulePath, name: symbol, f: (d: M.Definition<V>) => R): R {
|
||||
const { resolved, schema } = this._findModule(modulePath);
|
||||
return this._withModule(resolved, () => {
|
||||
const definition = schema.definitions.get(name);
|
||||
if (definition === void 0) {
|
||||
throw new Error(`No such preserves-schema definition: ${[... modulePath, name].map(s => s.description!).join('.')}`);
|
||||
}
|
||||
return f(definition);
|
||||
});
|
||||
}
|
||||
|
||||
makeTop(modulePath: M.ModulePath, name: symbol, fields: Bindings<V>): TopBindings<V> {
|
||||
const result = fields as TopBindings<V>;
|
||||
result.__as_preserve__ = () => this.unparser(modulePath, name)(result);
|
||||
result.__preserve_on__ = function (e) { e.push(this.__as_preserve__()); };
|
||||
result.__preserve_text_on__ = function (w) { w.push(this.__as_preserve__()); };
|
||||
return result;
|
||||
}
|
||||
|
||||
definitionConstructor(
|
||||
modulePath: M.ModulePath,
|
||||
name: symbol,
|
||||
): DefinitionConstructors<V> {
|
||||
return this._lookup(modulePath, name, (definition): DefinitionConstructors<V> => {
|
||||
const ty = H.definitionType(definition);
|
||||
if (ty._variant === 'union') {
|
||||
const multiple: MultipleConstructors<V> = {};
|
||||
ty.variants.forEach(v => {
|
||||
const _variant = v.label.description!;
|
||||
switch (v.type._variant) {
|
||||
case 'Field':
|
||||
multiple[_variant] = (value: any) =>
|
||||
this.makeTop(modulePath, name, { _variant, value });
|
||||
break;
|
||||
case 'Record':
|
||||
multiple[_variant] = (fields: object) =>
|
||||
this.makeTop(modulePath, name, { _variant, ... fields });
|
||||
break;
|
||||
}
|
||||
});
|
||||
return { multiple };
|
||||
} else {
|
||||
const flatName = M.formatModulePath([... modulePath, name]);
|
||||
switch (ty.value._variant) {
|
||||
case 'Field': {
|
||||
const tmp = { [flatName]: (value: any) => value };
|
||||
return { single: tmp[flatName] };
|
||||
}
|
||||
case 'Record': {
|
||||
const tmp = { [flatName]: (fields: Bindings<V>) =>
|
||||
this.makeTop(modulePath, name, fields) };
|
||||
return { single: tmp[flatName] };
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
parse(
|
||||
modulePath: M.ModulePath,
|
||||
name: symbol,
|
||||
input: Value<V>,
|
||||
): Unparseable<V> {
|
||||
const v = this.tryParse(modulePath, name, input);
|
||||
if (v === void 0) {
|
||||
throw new TypeError(
|
||||
`Invalid ${M.formatModulePath([... modulePath, name])}: ${stringify(input)}`)
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
tryParse(
|
||||
modulePath: M.ModulePath,
|
||||
name: symbol,
|
||||
input: Value<V>,
|
||||
): Unparseable<V> | undefined {
|
||||
return this._lookup(modulePath, name, definition =>
|
||||
optmap(this.parseDefinition(definition, input), result0 => {
|
||||
const ty = H.definitionType(definition);
|
||||
if (ty._variant === 'union' || ty.value._variant === 'Record') {
|
||||
return this.makeTop(modulePath, name, result0 as Bindings<V>);
|
||||
} else {
|
||||
return result0 as Exclude<Parsed<V>, Bindings<V>>;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
parseDefinition(d: M.Definition<V>, input: Value<V>): Parsed<V> | undefined {
|
||||
switch (d._variant) {
|
||||
case 'or':
|
||||
return this.parseNamedAlternative(d.pattern0, input) ??
|
||||
this.parseNamedAlternative(d.pattern1, input) ??
|
||||
(() => {
|
||||
for (const p of d.patternN) {
|
||||
const r = this.parseNamedAlternative(p, input);
|
||||
if (r !== void 0) return r;
|
||||
}
|
||||
return void 0;
|
||||
})();
|
||||
case 'and': {
|
||||
const rs = [this.parseNamedPattern(d.pattern0, input),
|
||||
this.parseNamedPattern(d.pattern1, input),
|
||||
... d.patternN.map(p => this.parseNamedPattern(p, input))];
|
||||
for (const r of rs) {
|
||||
if (r === void 0) return void 0;
|
||||
}
|
||||
return Bindings.merge(... rs as Bindings<V>[]);
|
||||
}
|
||||
case 'Pattern':
|
||||
return optmap(this.parsePattern(d.value, input), DynField.unwrap);
|
||||
}
|
||||
}
|
||||
|
||||
parseNamedAlternative(p: M.NamedAlternative<V>, input: Value<V>): Bindings<V> | undefined {
|
||||
return optmap(this.parsePattern(p.pattern, input), w => {
|
||||
const result = DynField.promote(w, Symbol.for('value'));
|
||||
result._variant = p.variantLabel;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
parseNamedPattern(p: M.NamedPattern<V>, input: Value<V>): Bindings<V> | undefined {
|
||||
switch (p._variant) {
|
||||
case 'named':
|
||||
return optmap(this.parseSimplePattern(p.value.pattern, input),
|
||||
w => DynField.promote(DynField.maybeSimple(w), p.value.name));
|
||||
case 'anonymous':
|
||||
return optmap(this.parsePattern(p.value, input),
|
||||
w => DynField.promote(w));
|
||||
}
|
||||
}
|
||||
|
||||
parseNamedSimplePattern(p: M.NamedSimplePattern<V>, input: Value<V>): DynField<V> | undefined {
|
||||
switch (p._variant) {
|
||||
case 'named':
|
||||
return optmap(this.parseSimplePattern(p.value.pattern, input),
|
||||
w => DynField.compound(DynField.promote(DynField.maybeSimple(w), p.value.name)));
|
||||
case 'anonymous':
|
||||
return optmap(this.parseSimplePattern(p.value, input), DynField.maybeSimple<V>);
|
||||
}
|
||||
}
|
||||
|
||||
parseSimplePattern(p: M.SimplePattern<V>, input: Value<V>): Parsed<V> | null | undefined {
|
||||
const inputIf = (b: boolean) => b ? input : void 0;
|
||||
switch (p._variant) {
|
||||
case 'any': return input;
|
||||
case 'atom': switch (p.atomKind._variant) {
|
||||
case 'Boolean': return inputIf(typeof input === 'boolean');
|
||||
case 'Float': return inputIf(Float.isFloat(input));
|
||||
case 'Double': return inputIf(Float.isDouble(input));
|
||||
case 'SignedInteger': return inputIf(typeof input === 'number' || typeof input === 'bigint');
|
||||
case 'String': return inputIf(typeof input === 'string');
|
||||
case 'ByteString': return inputIf(Bytes.isBytes(input));
|
||||
case 'Symbol': return inputIf(typeof input === 'symbol');
|
||||
}
|
||||
case 'embedded': return isEmbedded(input) ? input.embeddedValue : void 0;
|
||||
case 'lit': return is(input, p.value) ? null : void 0;
|
||||
case 'seqof': {
|
||||
if (!isSequence(input)) return void 0;
|
||||
const result: Parsed<V>[] = [];
|
||||
for (const v of input) {
|
||||
const w = this.parseSimplePattern(p.pattern, v);
|
||||
if (w === void 0) return void 0;
|
||||
if (w !== null) result.push(w);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case 'setof': {
|
||||
if (!Set.isSet<V>(input)) return void 0;
|
||||
const result: Parsed<V>[] = [];
|
||||
for (const v of input) {
|
||||
const w = this.parseSimplePattern(p.pattern, v);
|
||||
if (w === void 0) return void 0;
|
||||
if (w !== null) result.push(w);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case 'dictof': {
|
||||
if (!Dictionary.isDictionary<V>(input)) return void 0;
|
||||
const result: DictOf<V> = new EncodableDictionary(
|
||||
this.unparserSimplePattern(p.key),
|
||||
this.unparserSimplePattern(p.value));
|
||||
for (const [k, v] of input) {
|
||||
const kw = this.parseSimplePattern(p.key, k);
|
||||
if (kw === void 0) return void 0;
|
||||
const vw = this.parseSimplePattern(p.value, v);
|
||||
if (vw === void 0) return void 0;
|
||||
result.set(kw === null ? UNIT : kw, vw === null ? UNIT : vw);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case 'Ref': return this.tryParse(p.value.module, p.value.name, input);
|
||||
}
|
||||
}
|
||||
|
||||
parseCompoundPattern(p: M.CompoundPattern<V>, input: Value<V>): Bindings<V> | undefined {
|
||||
switch (p._variant) {
|
||||
case 'rec':
|
||||
if (!Record.isRecord<Value<V>, Array<Value<V>>, V>(input)) return void 0;
|
||||
return optmap(this.parseNamedPattern(p.label, input.label),
|
||||
lw => optmap(this.parseNamedPattern(p.fields, Array.from(input)),
|
||||
fsw => Bindings.merge(lw, fsw)));
|
||||
case 'tuple': {
|
||||
if (!isSequence(input)) return void 0;
|
||||
if (input.length !== p.patterns.length) return void 0;
|
||||
let results: Bindings<V>[] = [];
|
||||
for (let i = 0; i < p.patterns.length; i++) {
|
||||
const w = this.parseNamedPattern(p.patterns[i], input[i]);
|
||||
if (w === void 0) return void 0;
|
||||
results.push(w);
|
||||
}
|
||||
return Bindings.merge(... results);
|
||||
}
|
||||
case 'tuplePrefix': {
|
||||
if (!isSequence(input)) return void 0;
|
||||
if (input.length < p.fixed.length) return void 0;
|
||||
let fixed_results: Bindings<V>[] = [];
|
||||
for (let i = 0; i < p.fixed.length; i++) {
|
||||
const w = this.parseNamedPattern(p.fixed[i], input[i]);
|
||||
if (w === void 0) return void 0;
|
||||
fixed_results.push(w);
|
||||
}
|
||||
const remainder = input.slice(p.fixed.length);
|
||||
return optmap(this.parseNamedSimplePattern(p.variable, remainder), vw => {
|
||||
const variable_results = DynField.unwrap_compound(vw);
|
||||
return Bindings.merge(variable_results, ... fixed_results);
|
||||
});
|
||||
}
|
||||
case 'dict': {
|
||||
if (!Dictionary.isDictionary<V>(input)) return void 0;
|
||||
const results: Bindings<V>[] = [];
|
||||
for (const [key, vp] of p.entries) {
|
||||
const v = input.get(key);
|
||||
if (v === void 0) return void 0;
|
||||
const vw = this.parseNamedSimplePattern(vp, v);
|
||||
if (vw === void 0) return void 0;
|
||||
results.push(DynField.unwrap_compound(vw));
|
||||
}
|
||||
return Bindings.merge(... results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parsePattern(p: M.Pattern<V>, input: Value<V>): DynField<V> | undefined {
|
||||
switch (p._variant) {
|
||||
case 'SimplePattern':
|
||||
return optmap(this.parseSimplePattern(p.value, input), DynField.maybeSimple);
|
||||
case 'CompoundPattern':
|
||||
return optmap(this.parseCompoundPattern(p.value, input), DynField.compound);
|
||||
}
|
||||
}
|
||||
|
||||
unparse(
|
||||
modulePath: M.ModulePath,
|
||||
name: symbol,
|
||||
v: Unparseable<V>,
|
||||
): Value<V> {
|
||||
return this.unparser(modulePath, name)(v);
|
||||
}
|
||||
|
||||
unparser(modulePath: M.ModulePath, name: symbol): Unparser<V> {
|
||||
return this._unparser(modulePath, name)[0];
|
||||
}
|
||||
|
||||
_unparser(modulePath: M.ModulePath, name: symbol): [Unparser<V>] {
|
||||
const key = [... modulePath.map(n => n.description!), name.description!].join('.');
|
||||
if (!(key in this.unparserCache)) {
|
||||
const cell: [Unparser<V>] = [null!];
|
||||
this.unparserCache[key] = cell;
|
||||
cell[0] = this._lookup(modulePath, name, p => this.unparserDefinition(p));
|
||||
}
|
||||
return this.unparserCache[key];
|
||||
}
|
||||
|
||||
unparserDefinition(p: M.Definition<V>): Unparser<V> {
|
||||
switch (p._variant) {
|
||||
case 'or': {
|
||||
const ups = [p.pattern0, p.pattern1, ... p.patternN].map(
|
||||
p => this.unparserNamedAlternative(p));
|
||||
return v => {
|
||||
const bs = v as Bindings<V>;
|
||||
return ups.find(up => up[0] === bs._variant)![1](bs);
|
||||
};
|
||||
}
|
||||
case 'and': {
|
||||
const ups = [p.pattern0, p.pattern1, ... p.patternN].map(
|
||||
p => this.unparserNamedPattern(p));
|
||||
return v => plainMerge(this.mergeEmbeddeds,
|
||||
ups[0](v), ... ups.slice(1).map(up => up(v)));
|
||||
}
|
||||
case 'Pattern':
|
||||
return this.unparserPattern(p.value);
|
||||
}
|
||||
}
|
||||
|
||||
unparserNamedAlternative(p: M.NamedAlternative<V>): [string, UnparserCompound<V>] {
|
||||
const up = this.unparserPattern(p.pattern);
|
||||
const ty = H.patternType(p.pattern);
|
||||
switch (ty._variant) {
|
||||
case 'Field': return [p.variantLabel, bs => up(bs['value'])];
|
||||
case 'Record': return [p.variantLabel, up];
|
||||
}
|
||||
}
|
||||
|
||||
unparserNamedPattern(p: M.NamedPattern<V>): Unparser<V> {
|
||||
switch (p._variant) {
|
||||
case 'named': {
|
||||
const up = this.unparserSimplePattern(p.value.pattern);
|
||||
const key = M.jsId(p.value.name.description!);
|
||||
return v => up((v as Bindings<V>)[key]);
|
||||
}
|
||||
case 'anonymous':
|
||||
return this.unparserPattern(p.value);
|
||||
}
|
||||
}
|
||||
|
||||
unparserPattern(p: M.Pattern<V>): Unparser<V> {
|
||||
switch (p._variant) {
|
||||
case 'CompoundPattern': {
|
||||
const up = this.unparserCompoundPattern(p.value);
|
||||
return v => up(v as Bindings<V>);
|
||||
}
|
||||
case 'SimplePattern':
|
||||
return this.unparserSimplePattern(p.value);
|
||||
}
|
||||
}
|
||||
|
||||
unparserSimplePattern(p: M.SimplePattern<V>): Unparser<V> {
|
||||
switch (p._variant) {
|
||||
case 'any': return v => v as Value<V>; // ?!
|
||||
case 'atom': return v => v as Atom;
|
||||
case 'embedded': return v => new Embedded(v as V);
|
||||
case 'lit': return _v => p.value;
|
||||
case 'seqof': {
|
||||
const up = this.unparserSimplePattern(p.pattern);
|
||||
return vs => (vs as Parsed<V>[]).map(up);
|
||||
}
|
||||
case 'setof': {
|
||||
const up = this.unparserSimplePattern(p.pattern);
|
||||
return vs => new Set<V>((vs as Parsed<V>[]).map(up));
|
||||
}
|
||||
case 'dictof': {
|
||||
const kp = this.unparserSimplePattern(p.key);
|
||||
const vp = this.unparserSimplePattern(p.value);
|
||||
return vs => new Dictionary<V>(_iterMap(
|
||||
(vs as DictOf<V>).entries(), ([k, v]) => [kp(k), vp(v)]));
|
||||
}
|
||||
case 'Ref': {
|
||||
const up = this._unparser(p.value.module, p.value.name);
|
||||
return v => up[0](v as Bindings<V>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unparserCompoundPattern(p: M.CompoundPattern<V>): UnparserCompound<V> {
|
||||
switch (p._variant) {
|
||||
case 'rec': {
|
||||
const lp = this.unparserNamedPattern(p.label);
|
||||
const fp = this.unparserNamedPattern(p.fields);
|
||||
return bs => Record(lp(bs), fp(bs) as Value<V>[]);
|
||||
}
|
||||
case 'tuple': {
|
||||
const ups = p.patterns.map(p => this.unparserNamedPattern(p));
|
||||
return bs => ups.map(up => up(bs));
|
||||
}
|
||||
case 'tuplePrefix': {
|
||||
const fixed = p.fixed.map(p => this.unparserNamedPattern(p));
|
||||
const variable = this.unparserNamedSimplePattern(p.variable);
|
||||
return bs => [... fixed.map(up => up(bs)), ... variable(bs) as Value<V>[]];
|
||||
}
|
||||
case 'dict': {
|
||||
const ups: [Value<V>, Unparser<V>][] = Array.from(p.entries.entries()).map(
|
||||
([key, vp]) => [key, this.unparserNamedSimplePattern(vp)]);
|
||||
return bs => {
|
||||
const result = new Dictionary<V>();
|
||||
for (const [key, up] of ups) {
|
||||
result.set(key, up(bs));
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unparserNamedSimplePattern(p: M.NamedSimplePattern<V>): Unparser<V> {
|
||||
switch (p._variant) {
|
||||
case 'named': {
|
||||
const up = this.unparserSimplePattern(p.value.pattern);
|
||||
const key = M.jsId(p.value.name.description!);
|
||||
return v => up((v as Bindings<V>)[key]);
|
||||
}
|
||||
case 'anonymous':
|
||||
return this.unparserSimplePattern(p.value);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue