Work on converter; split up compiler
This commit is contained in:
parent
98558b81f0
commit
084f54f869
|
@ -1,485 +1,17 @@
|
|||
import { Pattern, NamedPattern, Schema, Input, Environment, Ref, lookup } from "./meta";
|
||||
import * as M from './meta';
|
||||
import { Annotated, Bytes, Dictionary, Fold, fold, KeyedSet, Position, preserves, Record, Set, Tuple, Value } from "@preserves/core";
|
||||
import { Formatter, parens, seq, Item, opseq, block, commas, brackets, anglebrackets, braces } from "./block";
|
||||
import { refPosition } from "./reader";
|
||||
import { Alternative, Definition } from "gen/schema";
|
||||
import { Annotated, Bytes, Dictionary, Fold, fold, Record, stringify, Tuple, Value } from "@preserves/core";
|
||||
import * as M from "./meta";
|
||||
import { CompilerOptions, ModuleContext } from "./compiler/context";
|
||||
import { brackets, Formatter, Item, parens, seq } from "./compiler/block";
|
||||
import { typeForDefinition } from "./compiler/type";
|
||||
import { decoderFor } from "./compiler/decoder";
|
||||
import { converterForDefinition } from "./compiler/converter";
|
||||
|
||||
export interface CompilerOptions {
|
||||
preservesModule?: string;
|
||||
defaultPointer?: Ref;
|
||||
warn?(message: string, pos: Position | null): void;
|
||||
};
|
||||
|
||||
function fnblock(... items: Item[]): Item {
|
||||
return seq('((() => ', block(... items), ')())');
|
||||
}
|
||||
|
||||
class ModuleContext {
|
||||
readonly env: Environment;
|
||||
readonly schema: Schema;
|
||||
readonly options: CompilerOptions;
|
||||
|
||||
readonly literals = new Dictionary<never, string>();
|
||||
readonly typedefs: Item[] = [];
|
||||
readonly functiondefs: Item[] = [];
|
||||
readonly imports = new KeyedSet<[string, string]>();
|
||||
|
||||
constructor(env: Environment, schema: Schema, options: CompilerOptions) {
|
||||
this.env = env;
|
||||
this.schema = schema;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
literal(v: Input): Item {
|
||||
let varname = this.literals.get(v);
|
||||
if (varname === void 0) {
|
||||
const s = v.asPreservesText()
|
||||
.replace('_', '__')
|
||||
.replace('*', '_STAR_');
|
||||
varname = M.isValidToken('_' + s, true) ? '$' + s : '__lit' + this.literals.size;
|
||||
this.literals.set(v, varname);
|
||||
}
|
||||
return varname;
|
||||
}
|
||||
|
||||
derefPattern([_name, p]: [string, Alternative]): Definition {
|
||||
if (p.label === M.$ref) {
|
||||
return lookup(refPosition(p), p, this.env,
|
||||
(p) => p,
|
||||
(p) => p,
|
||||
(_modId, _modPath, pp) => pp ?? p);
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
defineType(f: Item): void {
|
||||
this.typedefs.push(f);
|
||||
}
|
||||
|
||||
defineFunction(f: (ctx: FunctionContext) => Item): void {
|
||||
this.functiondefs.push(f(new FunctionContext(this)));
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionContext {
|
||||
readonly mod: ModuleContext;
|
||||
|
||||
tempCounter = 0;
|
||||
temps: string[] = [];
|
||||
|
||||
constructor(mod: ModuleContext) {
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
gentemp(): string {
|
||||
const varname = '_tmp' + this.tempCounter++;
|
||||
this.temps.push(varname);
|
||||
return varname;
|
||||
}
|
||||
|
||||
gentemps(n: number): string[] {
|
||||
const temps = [];
|
||||
while (temps.length < n) temps.push(this.gentemp());
|
||||
return temps;
|
||||
}
|
||||
|
||||
block(f: () => Item[]): Item {
|
||||
const oldTemps = this.temps;
|
||||
this.temps = [];
|
||||
const items = f();
|
||||
const ts = this.temps;
|
||||
this.temps = oldTemps;
|
||||
return block(
|
||||
... ts.length > 0 ? [seq(`let `, commas(... ts), ': any')] : [],
|
||||
... items);
|
||||
}
|
||||
}
|
||||
|
||||
function unname(p: NamedPattern): Pattern {
|
||||
return (p.label === M.$named) ? p[1] : p;
|
||||
}
|
||||
|
||||
function fieldName(np: NamedPattern, index: number): string {
|
||||
return (np.label === M.$named) ? np[0].description! : `_field${index}`;
|
||||
}
|
||||
|
||||
function accumulateCompound(ctx: FunctionContext,
|
||||
p: Pattern,
|
||||
kFail: () => Item[],
|
||||
kAcc: (temp: string) => Item[]): Item
|
||||
{
|
||||
const t = ctx.gentemp();
|
||||
return seq(`while (!d.closeCompound()) `, ctx.block(() => [
|
||||
seq(`${t} = void 0`),
|
||||
... decoderFor(ctx, p, t),
|
||||
seq(`if (${t} === void 0) `, block(... kFail(), seq(`break`))),
|
||||
... kAcc(t)]));
|
||||
}
|
||||
|
||||
function decoderForTuple(ctx: FunctionContext,
|
||||
tuplePattern: Pattern,
|
||||
ps: Pattern[],
|
||||
dest: string,
|
||||
recordFields: boolean,
|
||||
variablePattern: Pattern | undefined): Item[]
|
||||
{
|
||||
const temps = ctx.gentemps(ps.length);
|
||||
|
||||
function loop(i: number): Item[] {
|
||||
if (i < ps.length) {
|
||||
return [... decoderFor(ctx, ps[i], temps[i]),
|
||||
seq(`if (${temps[i]} !== void 0) `, ctx.block(() => loop(i + 1)))];
|
||||
} else {
|
||||
if (variablePattern === void 0) {
|
||||
return [seq(`if (d.closeCompound()) ${dest} = `, brackets(... temps),
|
||||
` as `, typeFor(ctx.mod, tuplePattern))];
|
||||
} else {
|
||||
return [block(
|
||||
seq(`let vN: `, typeFor(ctx.mod, tuplePattern),
|
||||
` | undefined = `, brackets(... temps)),
|
||||
accumulateCompound(ctx,
|
||||
variablePattern,
|
||||
() => [`vN = void 0`],
|
||||
(t) => [`vN.push(${t})`]),
|
||||
seq(`${dest} = vN`))];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return recordFields
|
||||
? loop(0)
|
||||
: [seq(`if (d.openSequence()) `, ctx.block(() => loop(0)))];
|
||||
}
|
||||
|
||||
function decoderFor(ctx: FunctionContext, p: Definition, dest: string, recordFields = false): Item[]
|
||||
{
|
||||
switch (p.label) {
|
||||
case M.$atom:
|
||||
switch (p[0]) {
|
||||
case M.$Boolean: return [`${dest} = d.nextBoolean()`];
|
||||
case M.$Float: return [`${dest} = d.nextFloat()`];
|
||||
case M.$Double: return [`${dest} = d.nextDouble()`];
|
||||
case M.$SignedInteger: return [`${dest} = d.nextSignedInteger()`];
|
||||
case M.$String: return [`${dest} = d.nextString()`];
|
||||
case M.$ByteString: return [`${dest} = d.nextByteString()`];
|
||||
case M.$Symbol: return [`${dest} = d.nextSymbol()`];
|
||||
}
|
||||
case M.$lit: {
|
||||
let n: string;
|
||||
switch (typeof p[0]) {
|
||||
case 'boolean': n = `d.nextBoolean()`; break;
|
||||
case 'string': n = `d.nextString()`; break;
|
||||
case 'number': n = `d.nextSignedInteger()`; break;
|
||||
case 'symbol': n = `d.nextSymbol()`; break;
|
||||
default: n = `d.next()`; break;
|
||||
}
|
||||
return [`${dest} = _.asLiteral(${n}, ${ctx.mod.literal(p[0])})`];
|
||||
}
|
||||
case M.$ref:
|
||||
return lookup(refPosition(p), p, ctx.mod.env,
|
||||
(_p) => [`${dest} = decode${p[1].description!}(d)`],
|
||||
(p) => decoderFor(ctx, p, dest),
|
||||
(modId, modPath,_p) => {
|
||||
ctx.mod.imports.add([modId, modPath]);
|
||||
return [`${dest} = ${modId}.decode${p[1].description!}(d)`];
|
||||
});
|
||||
case M.$or: {
|
||||
const alts = p[0];
|
||||
const recs = alts.map(p => ctx.mod.derefPattern(p));
|
||||
if (recs.length > 1 && recs.every(pp => pp.label === M.$rec)) {
|
||||
// Hoist the record check up.
|
||||
// This is pretty hacky. If we lift the level of
|
||||
// discourse a little, we can do this
|
||||
// automatically and generically...
|
||||
return [seq(`if (d.openRecord()) `, ctx.block(() => {
|
||||
const label = ctx.gentemp();
|
||||
const mark = ctx.gentemp();
|
||||
function loop(i: number): Item[] {
|
||||
const alt = recs[i];
|
||||
if (alt.label !== M.$rec) throw new Error("Internal error"); // avoid a cast
|
||||
return [
|
||||
seq(`if (`, predicateFor(ctx, label, unname(alt[0])), `) `, ctx.block(() => {
|
||||
const fs = ctx.gentemp();
|
||||
return [... decoderFor(ctx, unname(alt[1]), fs, true),
|
||||
seq(`if (${fs} !== void 0) ${dest} = _.Record`,
|
||||
anglebrackets(typeFor(ctx.mod, unname(alt[0])),
|
||||
typeFor(ctx.mod, unname(alt[1]))),
|
||||
parens(seq(label, ` as any`),
|
||||
seq(fs, ` as any`)))];
|
||||
})),
|
||||
... (i < recs.length - 1)
|
||||
? [seq(`if (${dest} === void 0) `,
|
||||
ctx.block(() => [`d.restoreMark(${mark})`, ... loop(i + 1)]))]
|
||||
: [],
|
||||
];
|
||||
}
|
||||
return [seq(`${label} = d.next()`),
|
||||
seq(`${mark} = d.mark()`),
|
||||
... loop(0)];
|
||||
}))];
|
||||
} else {
|
||||
switch (alts.length) {
|
||||
case 0: return []; // assume dest is already void 0
|
||||
case 1: return decoderFor(ctx, alts[0][1], dest);
|
||||
default: {
|
||||
const mark = ctx.gentemp();
|
||||
function loop(i: number): Item[] {
|
||||
return [
|
||||
... decoderFor(ctx, alts[i][1], dest),
|
||||
... (i < alts.length - 1)
|
||||
? [seq(`if (${dest} === void 0) `, ctx.block(() =>
|
||||
[`d.restoreMark(${mark})`, ... loop(i + 1)]))]
|
||||
: [],
|
||||
];
|
||||
}
|
||||
return [`${mark} = d.mark()`, ... loop(0)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case M.$and:
|
||||
switch (p[0].length) {
|
||||
case 0: return [`${dest} = d.next()`];
|
||||
case 1: return decoderFor(ctx, unname(p[0][0]), dest);
|
||||
default: {
|
||||
const [pp0, ... ppN] = p[0];
|
||||
return [... decoderFor(ctx, unname(pp0), dest),
|
||||
seq(`if (!`, opseq('true', ' && ',
|
||||
... ppN.map(pp =>
|
||||
predicateFor(ctx, dest, unname(pp)))),
|
||||
`) ${dest} = void 0`)];
|
||||
}
|
||||
}
|
||||
case M.$pointer:
|
||||
return [`${dest} = _decodePtr(d)`];
|
||||
case M.$rec:
|
||||
// assume dest is already void 0
|
||||
return [seq(`if (d.openRecord()) `, ctx.block(() => {
|
||||
const label = ctx.gentemp();
|
||||
return [... decoderFor(ctx, unname(p[0]), label),
|
||||
seq(`if (${label} !== void 0) `, ctx.block(() => {
|
||||
const fs = ctx.gentemp();
|
||||
return [... decoderFor(ctx, unname(p[1]), fs, true),
|
||||
seq(`if (${fs} !== void 0) ${dest} = _.Record`,
|
||||
anglebrackets(typeFor(ctx.mod, unname(p[0])),
|
||||
typeFor(ctx.mod, unname(p[1]))),
|
||||
parens(seq(label, ` as any`),
|
||||
seq(fs, ` as any`)))];
|
||||
}))];
|
||||
}))];
|
||||
case M.$tuple:
|
||||
// assume dest is already void 0
|
||||
return decoderForTuple(ctx, p, p[0].map(unname), dest, recordFields, void 0);
|
||||
case M.$tuple_STAR_:
|
||||
// assume dest is already void 0
|
||||
return decoderForTuple(ctx, p, p[0].map(unname), dest, recordFields, unname(p[1]));
|
||||
case M.$setof:
|
||||
// assume dest is already void 0
|
||||
return [seq(`if (d.openSet()) `, ctx.block(() => [
|
||||
seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedSet()`),
|
||||
accumulateCompound(ctx,
|
||||
p[0],
|
||||
() => [`r = void 0`],
|
||||
(t) => [`r.add(${t})`]),
|
||||
`${dest} = r`]))];
|
||||
case M.$dictof:
|
||||
// assume dest is already void 0
|
||||
return [seq(`if (d.openDictionary()) `, ctx.block(() => [
|
||||
seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedDictionary()`),
|
||||
seq(`while (!d.closeCompound()) `, ctx.block(() => [
|
||||
seq(`let K: undefined | `, typeFor(ctx.mod, p[0]), ` = void 0`),
|
||||
... decoderFor(ctx, p[0], 'K'),
|
||||
seq(`if (K === void 0) { r = void 0; break; }`),
|
||||
seq(`let V: undefined | `, typeFor(ctx.mod, p[1]), ` = void 0`),
|
||||
... decoderFor(ctx, p[1], 'V'),
|
||||
seq(`if (V === void 0) { r = void 0; break; }`),
|
||||
seq(`r.set(K, V)`)])),
|
||||
seq(`${dest} = r`)]))];
|
||||
case M.$dict:
|
||||
return [seq(`${dest} = d.next()`),
|
||||
seq(`if (${dest} !== void 0 && !(`, predicateFor(ctx, dest, p),
|
||||
`)) ${dest} = void 0`)];
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
function typeFor(mod: ModuleContext, p: Pattern): Item {
|
||||
switch (p.label) {
|
||||
case M.$atom:
|
||||
switch (p[0]) {
|
||||
case M.$Boolean: return `boolean`;
|
||||
case M.$Float: return `_.SingleFloat`;
|
||||
case M.$Double: return `_.DoubleFloat`;
|
||||
case M.$SignedInteger: return `number`;
|
||||
case M.$String: return `string`;
|
||||
case M.$ByteString: return `_.Bytes`;
|
||||
case M.$Symbol: return `symbol`;
|
||||
}
|
||||
case M.$lit:
|
||||
return `(typeof ${mod.literal(p[0])})`;
|
||||
case M.$ref:
|
||||
return lookup(refPosition(p), p, mod.env,
|
||||
(_p) => p[1].description!,
|
||||
(p) => typeForAlternative(mod, p),
|
||||
(modId, modPath,_p) => {
|
||||
mod.imports.add([modId, modPath]);
|
||||
return `${modId}.${p[1].description!}`;
|
||||
});
|
||||
case M.$pointer:
|
||||
return `_ptr`;
|
||||
case M.$rec:
|
||||
return seq('_.Record', anglebrackets(typeFor(mod, unname(p[0])),
|
||||
typeFor(mod, unname(p[1])), '_ptr'));
|
||||
case M.$tuple:
|
||||
return brackets(... p[0].map(pp => typeFor(mod, unname(pp))));
|
||||
case M.$tuple_STAR_:
|
||||
if (p[0].length === 0) {
|
||||
return seq('Array<', typeFor(mod, unname(p[1])), '>');
|
||||
} else {
|
||||
return brackets(... p[0].map(pp => typeFor(mod, unname(pp))),
|
||||
seq('... Array<', typeFor(mod, unname(p[1])), '>'));
|
||||
}
|
||||
case M.$setof:
|
||||
return seq('_.KeyedSet', anglebrackets(typeFor(mod, p[0]), '_ptr'));
|
||||
case M.$dictof:
|
||||
return seq('_.KeyedDictionary', anglebrackets(
|
||||
typeFor(mod, p[0]),
|
||||
typeFor(mod, p[1]),
|
||||
'_ptr'));
|
||||
case M.$dict:
|
||||
return parens(seq(
|
||||
block(
|
||||
... Array.from(p[0]).map(([k, vp]) =>
|
||||
seq(`get(k: typeof ${mod.literal(k)}): `, typeFor(mod, unname(vp)))),
|
||||
... Array.from(p[0]).map(([k, _vp]) =>
|
||||
seq(`has(k: typeof ${mod.literal(k)}): true`))),
|
||||
' & _.Dictionary<_ptr>'));
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
function typeForDefinition(mod: ModuleContext, _name: symbol, d: Definition): Item {
|
||||
if (d.label === M.$or) {
|
||||
return opseq('never', ' | ', ... d[0].map(a => typeForAlternative(mod, a[1])));
|
||||
} else {
|
||||
return typeForAlternative(mod, d);
|
||||
}
|
||||
}
|
||||
|
||||
function typeForAlternative(mod: ModuleContext, a: Alternative): Item {
|
||||
if (a.label === M.$and) {
|
||||
return opseq('_val', ' & ', ... a[0].map(p => typeFor(mod, unname(p))));
|
||||
} else {
|
||||
return typeFor(mod, a);
|
||||
}
|
||||
}
|
||||
|
||||
function predicateFor(ctx: FunctionContext, v: string, p: Definition, recordOkAsTuple = false): Item
|
||||
{
|
||||
switch (p.label) {
|
||||
case M.$atom:
|
||||
switch (p[0]) {
|
||||
case M.$Boolean: return `typeof ${v} === 'boolean'`;
|
||||
case M.$Float: return `_.Float.isSingle(${v})`;
|
||||
case M.$Double: return `_.Float.isDouble(${v})`;
|
||||
case M.$SignedInteger: return `typeof ${v} === 'number'`;
|
||||
case M.$String: return `typeof ${v} === 'string'`;
|
||||
case M.$ByteString: return `_.Bytes.isBytes(${v})`;
|
||||
case M.$Symbol: return `typeof ${v} === 'symbol'`;
|
||||
}
|
||||
case M.$lit:
|
||||
return `_.is(${v}, ${ctx.mod.literal(p[0])})`;
|
||||
case M.$ref:
|
||||
return lookup(refPosition(p), p, ctx.mod.env,
|
||||
(_p) => `is${Ref._.name(p).description!}(${v})`,
|
||||
(pp) => predicateFor(ctx, v, pp),
|
||||
(modId, modPath, _p) => {
|
||||
ctx.mod.imports.add([modId, modPath]);
|
||||
return `${modId}.is${Ref._.name(p).description!}(${v})`;
|
||||
});
|
||||
case M.$or: {
|
||||
const alts = p[0];
|
||||
const recs = alts.map(p => ctx.mod.derefPattern(p));
|
||||
if (recs.length > 1 && recs.every(pp => pp.label === M.$rec)) {
|
||||
return seq(
|
||||
`_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v}) && `,
|
||||
parens(opseq('false', ' || ',
|
||||
... recs.map(r =>
|
||||
(r.label !== M.$rec) ? '' : parens(seq(
|
||||
predicateFor(ctx, `${v}.label`, unname(r[0])),
|
||||
' && ',
|
||||
predicateFor(ctx, v, unname(r[1]), true)))))));
|
||||
} else {
|
||||
return opseq('false', ' || ', ... p[0].map(pp => predicateFor(ctx, v, pp[1])));
|
||||
}
|
||||
}
|
||||
case M.$and:
|
||||
return opseq('true', ' && ', ... p[0].map(pp => predicateFor(ctx, v, unname(pp))));
|
||||
case M.$pointer:
|
||||
return `_.isPointer(${v})`;
|
||||
case M.$rec:
|
||||
return opseq('true', ' && ',
|
||||
`_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`,
|
||||
predicateFor(ctx, `${v}.label`, unname(p[0])),
|
||||
predicateFor(ctx, v, unname(p[1]), true));
|
||||
case M.$tuple:
|
||||
return opseq('true', ' && ',
|
||||
... (recordOkAsTuple ? []
|
||||
: [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]),
|
||||
`(${v}.length === ${p[0].length})`,
|
||||
... p[0].map((pp, i) => predicateFor(ctx, `${v}[${i}]`, unname(pp))));
|
||||
case M.$tuple_STAR_:
|
||||
return opseq('true', ' && ',
|
||||
... (recordOkAsTuple ? []
|
||||
: [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]),
|
||||
`(${v}.length >= ${p[0].length})`,
|
||||
seq(p[0].length > 0 ? `${v}.slice(${p[0].length})` : v,
|
||||
`.every(v => `,
|
||||
parens(predicateFor(ctx, 'v', unname(p[1]))),
|
||||
`)`),
|
||||
... p[0].map((pp, i) => predicateFor(ctx, `${v}[${i}]`, unname(pp))));
|
||||
case M.$setof:
|
||||
return opseq('true', ' && ',
|
||||
`_.Set.isSet<_val>(${v})`,
|
||||
fnblock(
|
||||
seq(`for (const vv of ${v}) `, block(
|
||||
seq('if (!(', predicateFor(ctx, 'vv', p[0]), ')) return false'))),
|
||||
seq('return true')));
|
||||
case M.$dictof:
|
||||
return opseq('true', ' && ',
|
||||
`_.Dictionary.isDictionary<_ptr>(${v})`,
|
||||
fnblock(
|
||||
seq(`for (const e of ${v}) `, block(
|
||||
seq('if (!(', predicateFor(ctx, 'e[0]', p[0]), ')) return false'),
|
||||
seq('if (!(', predicateFor(ctx, 'e[1]', p[1]), ')) return false'))),
|
||||
seq('return true')));
|
||||
case M.$dict:
|
||||
return opseq('true', ' && ',
|
||||
`_.Dictionary.isDictionary<_ptr>(${v})`,
|
||||
... Array.from(p[0]).map(([k, vp]) => {
|
||||
const tmp = ctx.gentemp();
|
||||
return parens(seq(
|
||||
`(${tmp} = ${v}.get(${ctx.mod.literal(k)})) !== void 0 && `,
|
||||
predicateFor(ctx, tmp, unname(vp))));
|
||||
}));
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
export function compile(env: Environment, schema: Schema, options: CompilerOptions = {}): string {
|
||||
export function compile(env: M.Environment, schema: M.Schema, options: CompilerOptions = {}): string {
|
||||
const mod = new ModuleContext(env, schema, options);
|
||||
|
||||
const pointerName = Schema._._field0(schema).get(M.$pointer);
|
||||
const pointerName = M.Schema._._field0(schema).get(M.$pointer);
|
||||
mod.defineType(seq(`export type _ptr = `,
|
||||
pointerName === false ? 'never' : typeFor(mod, pointerName),
|
||||
pointerName === false ? 'never' : typeForDefinition(mod, pointerName),
|
||||
`;`));
|
||||
mod.defineType(`export type _val = _.Value<_ptr>;`);
|
||||
|
||||
|
@ -493,47 +25,33 @@ export function compile(env: Environment, schema: Schema, options: CompilerOptio
|
|||
seq(`return result`)]))),
|
||||
`;`));
|
||||
|
||||
for (const [name0, def] of Schema._._field0(schema).get(M.$definitions)) {
|
||||
const name = name0 as symbol;
|
||||
|
||||
if (def.label === M.$rec &&
|
||||
def[0].label === M.$lit &&
|
||||
def[1].label === M.$tuple)
|
||||
{
|
||||
function fieldEntry(np: NamedPattern, index: number): Item {
|
||||
return seq(JSON.stringify(fieldName(np, index)), ': ', typeFor(mod, unname(np)));
|
||||
}
|
||||
mod.defineType(
|
||||
seq(`export const ${name.description!} = _.Record.makeConstructor<`,
|
||||
braces(... def[1][0].map(fieldEntry)),
|
||||
`, _ptr>()(${mod.literal(def[0][0])}, `,
|
||||
JSON.stringify(def[1][0].map(fieldName)), `);`));
|
||||
}
|
||||
|
||||
for (const [name, def] of M.Schema._._field0(schema).get(M.$definitions)) {
|
||||
mod.defineType(
|
||||
seq(`export type ${name.description!} = `, typeForDefinition(mod, name, def), `;`));
|
||||
seq(`export type ${stringify(name)} = `, typeForDefinition(mod, def), `;`));
|
||||
}
|
||||
|
||||
for (const [name0, def] of Schema._._field0(schema).get(M.$definitions)) {
|
||||
for (const [name0, def] of M.Schema._._field0(schema).get(M.$definitions)) {
|
||||
const name = name0 as symbol;
|
||||
|
||||
mod.defineFunction(ctx =>
|
||||
seq(`export function is${name.description!}`,
|
||||
'(v: any): v is ', name.description!, ' ',
|
||||
ctx.block(() => [seq('return ', predicateFor(ctx, 'v', def))])));
|
||||
|
||||
mod.defineFunction(ctx =>
|
||||
seq(`export function as${name.description!}`,
|
||||
'(v: any): ', name.description!, ' ',
|
||||
ctx.block(() => [
|
||||
seq(`if (!is${name.description!}(v)) `,
|
||||
block(`throw new TypeError(\`Invalid ${name.description!}: \${_.stringify(v)}\`)`),
|
||||
' else ',
|
||||
block(`return v`))])));
|
||||
seq(`let result = to${name.description!}(v)`),
|
||||
seq(`if (result === void 0) `,
|
||||
`throw new TypeError(\`Invalid ${name.description!}: \${_.stringify(v)}\`)`),
|
||||
seq(`return result`)])));
|
||||
|
||||
mod.defineFunction(ctx =>
|
||||
seq(`export function to${name.description!}`,
|
||||
'(v: any): ', name.description!, ' | undefined ',
|
||||
ctx.block(() => [seq(`let result`),
|
||||
... converterForDefinition(ctx, def, 'v', 'result'),
|
||||
seq(`return result`)])));
|
||||
|
||||
mod.defineFunction(ctx =>
|
||||
seq(`export function decode${name.description!}`,
|
||||
`(d: _.TypedDecoder<_ptr>): ${name.description!} | undefined `,
|
||||
`(d: _.TypedDecoder<_ptr>): `, name.description!, ` | undefined `,
|
||||
ctx.block(() => [seq(`let result`),
|
||||
... decoderFor(ctx, def, 'result'),
|
||||
seq(`return result`)])));
|
||||
|
@ -569,21 +87,17 @@ export function compile(env: Environment, schema: Schema, options: CompilerOptio
|
|||
return f.toString();
|
||||
}
|
||||
|
||||
export function stringSource(s: string) {
|
||||
return JSON.stringify(s);
|
||||
}
|
||||
|
||||
export function sourceCodeFor(v: Value<any>): Item {
|
||||
return fold(v, {
|
||||
boolean(b: boolean): Item { return b.toString(); },
|
||||
single(f: number): Item { return f.toString(); },
|
||||
double(f: number): Item { return f.toString(); },
|
||||
integer(i: number): Item { return i.toString(); },
|
||||
string(s: string): Item { return stringSource(s); },
|
||||
string(s: string): Item { return JSON.stringify(s); },
|
||||
bytes(b: Bytes): Item {
|
||||
return seq(`Uint8Array.from(`, brackets(... Array.from(b).map(b => b.toString())), `)`);
|
||||
},
|
||||
symbol(s: symbol): Item { return `Symbol.for(${stringSource(s.description!)})`; },
|
||||
symbol(s: symbol): Item { return `Symbol.for(${JSON.stringify(s.description!)})`; },
|
||||
|
||||
record(r: Record<Value<any>, Tuple<Value<any>>, any>, k: Fold<any, Item>): Item {
|
||||
return seq(`_.Record<_val, _.Tuple<_val>, _ptr>`, parens(k(r.label), brackets(... r.map(k))));
|
||||
|
@ -604,7 +118,7 @@ export function sourceCodeFor(v: Value<any>): Item {
|
|||
},
|
||||
|
||||
pointer(t: any, _k: Fold<any, Item>): Item {
|
||||
throw new Error(preserves`Cannot emit source code for construction of pointer ${t}`);
|
||||
throw new Error(`Cannot emit source code for construction of pointer ${stringify(t)}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ export class Sequence extends Emittable {
|
|||
|
||||
constructor(items: Array<Item>) {
|
||||
super();
|
||||
if (items.some(i => i === void 0)) throw new Error('aiee');
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
|
@ -158,3 +159,5 @@ export const brackets = (... items: Item[]) => new Brackets(items);
|
|||
export const anglebrackets = (... items: Item[]) => new AngleBrackets(items);
|
||||
export const braces = (... items: Item[]) => new Braces(items);
|
||||
export const block = (... items: Item[]) => new Block(items);
|
||||
export const fnblock = (... items: Item[]) => seq('((() => ', block(... items), ')())');
|
||||
export const keyvalue = (k: Item, v: Item) => seq(k, ': ', v);
|
|
@ -0,0 +1,92 @@
|
|||
import { Dictionary, KeyedSet, Position } from "@preserves/core";
|
||||
import { refPosition } from "../reader";
|
||||
import * as M from "../meta";
|
||||
import { block, commas, Item, seq } from "./block";
|
||||
|
||||
export interface CompilerOptions {
|
||||
preservesModule?: string;
|
||||
defaultPointer?: M.Ref;
|
||||
warn?(message: string, pos: Position | null): void;
|
||||
}
|
||||
|
||||
export class ModuleContext {
|
||||
readonly env: M.Environment;
|
||||
readonly schema: M.Schema;
|
||||
readonly options: CompilerOptions;
|
||||
|
||||
readonly literals = new Dictionary<never, string>();
|
||||
readonly typedefs: Item[] = [];
|
||||
readonly functiondefs: Item[] = [];
|
||||
readonly imports = new KeyedSet<[string, string]>();
|
||||
|
||||
constructor(env: M.Environment, schema: M.Schema, options: CompilerOptions) {
|
||||
this.env = env;
|
||||
this.schema = schema;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
literal(v: M.Input): Item {
|
||||
let varname = this.literals.get(v);
|
||||
if (varname === void 0) {
|
||||
const s = v.asPreservesText()
|
||||
.replace('_', '__')
|
||||
.replace('*', '_STAR_');
|
||||
varname = M.isValidToken('_' + s, true) ? '$' + s : '__lit' + this.literals.size;
|
||||
this.literals.set(v, varname);
|
||||
}
|
||||
return varname;
|
||||
}
|
||||
|
||||
derefPattern([_name, p]: [string, M.Alternative]): M.Definition {
|
||||
if (p.label === M.$ref) {
|
||||
return M.lookup(refPosition(p), p, this.env,
|
||||
(p) => p,
|
||||
(p) => p,
|
||||
(_modId, _modPath, pp) => pp ?? p);
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
defineType(f: Item): void {
|
||||
this.typedefs.push(f);
|
||||
}
|
||||
|
||||
defineFunction(f: (ctx: FunctionContext) => Item): void {
|
||||
this.functiondefs.push(f(new FunctionContext(this)));
|
||||
}
|
||||
}
|
||||
|
||||
export class FunctionContext {
|
||||
readonly mod: ModuleContext;
|
||||
|
||||
tempCounter = 0;
|
||||
temps: string[] = [];
|
||||
|
||||
constructor(mod: ModuleContext) {
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
gentemp(): string {
|
||||
const varname = '_tmp' + this.tempCounter++;
|
||||
this.temps.push(varname);
|
||||
return varname;
|
||||
}
|
||||
|
||||
gentemps(n: number): string[] {
|
||||
const temps = [];
|
||||
while (temps.length < n) temps.push(this.gentemp());
|
||||
return temps;
|
||||
}
|
||||
|
||||
block(f: () => Item[]): Item {
|
||||
const oldTemps = this.temps;
|
||||
this.temps = [];
|
||||
const items = f();
|
||||
const ts = this.temps;
|
||||
this.temps = oldTemps;
|
||||
return block(
|
||||
... ts.length > 0 ? [seq(`let `, commas(... ts), ': any')] : [],
|
||||
... items);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
import { FunctionContext } from "./context";
|
||||
import * as M from '../meta';
|
||||
import { block, braces, Item, keyvalue, seq } from "./block";
|
||||
import { typeFor, variantFor, variantInitFor } from "./type";
|
||||
import { refPosition } from "../reader";
|
||||
import { stringify } from "@preserves/core";
|
||||
|
||||
function converterForTuple(ctx: FunctionContext,
|
||||
ps: M.NamedPattern[],
|
||||
src: string,
|
||||
dest: string,
|
||||
variantName: string | undefined,
|
||||
recordFields: boolean,
|
||||
variablePattern: M.NamedSimplePattern | undefined): Item[]
|
||||
{
|
||||
const temps = ctx.gentemps(ps.length);
|
||||
|
||||
function loop(i: number): Item[] {
|
||||
if (i < ps.length) {
|
||||
return [...converterFor(ctx, M.unname(ps[i]), `${src}[${i}]`, temps[i]),
|
||||
seq(`if (${temps[i]} !== void 0) `, ctx.block(() => loop(i + 1)))];
|
||||
} else {
|
||||
if (variablePattern === void 0) {
|
||||
return [seq(`${dest} = `, braces(
|
||||
... variantInitFor(variantName),
|
||||
... ps.flatMap((pp, i) => converterField(pp, temps[i]))))];
|
||||
} else {
|
||||
return [ps.length > 0 ? `let vN = ${src}.slice(${ps.length})` : `let vN = ${src}`,
|
||||
converterForArray(ctx, M.unname(variablePattern), 'vN', dest, false)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lengthCheck = variablePattern === void 0
|
||||
? seq(`${src}.length === ${ps.length}`)
|
||||
: seq(`${src}.length >= ${ps.length}`);
|
||||
|
||||
return recordFields
|
||||
? loop(0)
|
||||
: [seq(`if (_.Array.isArray(${src}) && `, lengthCheck, `) `, ctx.block(() => loop(0)))];
|
||||
}
|
||||
|
||||
export function converterForDefinition(
|
||||
ctx: FunctionContext,
|
||||
p: M.Definition,
|
||||
src: string,
|
||||
dest: string): Item[]
|
||||
{
|
||||
if (p.label === M.$or) {
|
||||
const alts = p[0];
|
||||
switch (alts.length) {
|
||||
case 0: return []; // assume dest is already void 0
|
||||
case 1: return converterForAlternative(ctx, alts[0][1], src, dest, alts[0][0]);
|
||||
default: {
|
||||
function loop(i: number): Item[] {
|
||||
return [
|
||||
... converterForAlternative(ctx, alts[i][1], src, dest, alts[i][0]),
|
||||
... (i < alts.length - 1)
|
||||
? [seq(`if (${dest} === void 0) `, ctx.block(() => loop(i + 1)))]
|
||||
: []];
|
||||
}
|
||||
return loop(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return converterForAlternative(ctx, p, src, dest, void 0);
|
||||
}
|
||||
}
|
||||
|
||||
function converterForAlternative(ctx: FunctionContext, p: M.Alternative, src: string, dest: string, variantName: string | undefined): Item[] {
|
||||
if (p.label === M.$and) {
|
||||
switch (p[0].length) {
|
||||
case 0: return [`${dest} = ${src}`];
|
||||
case 1: {
|
||||
return converterFor(ctx, M.unname(p[0][0]), src, dest, variantName);
|
||||
}
|
||||
default: {
|
||||
const alts = p[0];
|
||||
const temps = ctx.gentemps(alts.length);
|
||||
function loop(i: number): Item[] {
|
||||
return (i < temps.length)
|
||||
? [...converterFor(ctx, M.unname(alts[i]), src, temps[i]),
|
||||
seq(`if (${temps[i]} !== void 0) `, ctx.block(() => loop(i + 1)))]
|
||||
: [seq(`${dest} = `, braces(
|
||||
... variantInitFor(variantName),
|
||||
... alts.flatMap((pp, i) => converterField(pp, temps[i]))))];
|
||||
}
|
||||
return loop(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return converterFor(ctx, p, src, dest, variantName);
|
||||
}
|
||||
}
|
||||
|
||||
function converterForArray(ctx: FunctionContext,
|
||||
arrayType: M.SimplePattern,
|
||||
src: string,
|
||||
dest: string,
|
||||
checkArray: boolean): Item
|
||||
{
|
||||
const postCheck = () => [
|
||||
seq(`let r: Array<`, typeFor(ctx.mod, arrayType), `> | undefined = []`),
|
||||
seq(`for (const v of ${src}) `, ctx.block(() => [
|
||||
seq(`let vv`),
|
||||
... converterFor(ctx, arrayType, 'v', 'vv'),
|
||||
seq(`if (vv === void 0) { r = void 0; break; }`),
|
||||
seq(`r.push(vv)`)])),
|
||||
seq(`${dest} = r`)];
|
||||
return (checkArray
|
||||
? seq(`if (_.Array.isArray(${src})) `, ctx.block(postCheck))
|
||||
: block(... postCheck()));
|
||||
}
|
||||
|
||||
function converterFor(
|
||||
ctx: FunctionContext,
|
||||
p: M.Pattern,
|
||||
src: string,
|
||||
dest: string,
|
||||
variantName?: string,
|
||||
recordFields = false): Item[]
|
||||
{
|
||||
let converterItem: Item[];
|
||||
const unlabeled = variantName === void 0 ? dest : ctx.gentemp();
|
||||
|
||||
if (M.isSimplePattern(p)) {
|
||||
converterItem = converterForSimple(ctx, p, src, unlabeled);
|
||||
} else {
|
||||
switch (p.label) {
|
||||
case M.$setof:
|
||||
// assume dest is already void 0
|
||||
converterItem = [
|
||||
seq(`if (_.Set.isSet(${src})) `, ctx.block(() => [
|
||||
seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedSet()`),
|
||||
seq(`for (const v of ${src}) `, ctx.block(() => [
|
||||
seq(`let vv`),
|
||||
... converterFor(ctx, p[0], 'v', 'vv'),
|
||||
seq(`if (vv === void 0) { r = void 0; break; }`),
|
||||
seq(`r.add(vv)`)])),
|
||||
seq(`${unlabeled} = r`)]))];
|
||||
break;
|
||||
case M.$dictof:
|
||||
// assume dest is already void 0
|
||||
converterItem = [
|
||||
seq(`if (_.Dictionary.isDictionary(${src})) `, ctx.block(() => [
|
||||
seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedDictionary()`),
|
||||
seq(`for (const [k, v] of ${src}) `, ctx.block(() => [
|
||||
seq(`let kk`),
|
||||
... converterFor(ctx, p[0], 'k', 'kk'),
|
||||
seq(`if (kk === void 0) { r = void 0; break; }`),
|
||||
seq(`let vv`),
|
||||
... converterFor(ctx, p[1], 'v', 'vv'),
|
||||
seq(`if (vv === void 0) { r = void 0; break; }`),
|
||||
seq(`r.set(kk, vv)`)])),
|
||||
seq(`${unlabeled} = r`)]))];
|
||||
break;
|
||||
default: {
|
||||
const arrayType = M.simpleArray(p);
|
||||
if (arrayType === void 0) {
|
||||
return converterForCompound(ctx, p, src, dest, variantName, recordFields);
|
||||
} else {
|
||||
converterItem = [
|
||||
converterForArray(ctx, arrayType, src, unlabeled, !recordFields)];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (variantName === void 0) {
|
||||
return converterItem;
|
||||
} else {
|
||||
return [... converterItem,
|
||||
seq(`if (${unlabeled} !== void 0) ${dest} = `, braces(
|
||||
variantFor(variantName),
|
||||
keyvalue('value', unlabeled)))];
|
||||
}
|
||||
}
|
||||
|
||||
function converterForSimple(
|
||||
ctx: FunctionContext,
|
||||
p: M.SimplePattern,
|
||||
src: string,
|
||||
dest: string): Item[]
|
||||
{
|
||||
switch (p.label) {
|
||||
case M.$atom: {
|
||||
let test: Item;
|
||||
switch (p[0]) {
|
||||
case M.$Boolean: test = `typeof ${src} === 'boolean'`; break;
|
||||
case M.$Float: test = `_.Float.isSingle(${src})`; break;
|
||||
case M.$Double: test =`_.Float.isDouble(${src})`; break;
|
||||
case M.$SignedInteger: test = `typeof ${src} === 'number'`; break;
|
||||
case M.$String: test = `typeof ${src} === 'string'`; break;
|
||||
case M.$ByteString: test = `_.Bytes.isBytes(${src})`; break;
|
||||
case M.$Symbol: test = `typeof ${src} === 'symbol'`; break;
|
||||
}
|
||||
return [seq(`${dest} = `, test, ` ? ${src} : void 0`)];
|
||||
}
|
||||
case M.$lit:
|
||||
return [`${dest} = _.is(${src}, ${ctx.mod.literal(p[0])}) ? ${src} : void 0`];
|
||||
case M.$ref:
|
||||
return M.lookup(refPosition(p), p, ctx.mod.env,
|
||||
(_p) => [`${dest} = to${p[1].description!}(${src})`],
|
||||
(p) => converterForAlternative(ctx, p, src, dest, void 0),
|
||||
(modId, modPath,_p) => {
|
||||
ctx.mod.imports.add([modId, modPath]);
|
||||
return [`${dest} = ${modId}.decode${p[1].description!}(${src})`];
|
||||
});
|
||||
case M.$pointer:
|
||||
return [`${dest} = _toPtr(${src})`];
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
function converterForCompound(
|
||||
ctx: FunctionContext,
|
||||
p: M.CompoundPattern,
|
||||
src: string,
|
||||
dest: string,
|
||||
variantName: string | undefined,
|
||||
recordFields: boolean): Item[]
|
||||
{
|
||||
switch (p.label) {
|
||||
case M.$rec:
|
||||
// assume dest is already void 0
|
||||
return [seq(`if (_.Record.isRecord(${src})) `, ctx.block(() => {
|
||||
const label = ctx.gentemp();
|
||||
return [...converterFor(ctx, M.unname(p[0]), `${src}.label`, label),
|
||||
seq(`if (${label} !== void 0) `, ctx.block(() => {
|
||||
const fs = ctx.gentemp();
|
||||
return [...converterFor(ctx, M.unname(p[1]), src, fs, void 0, true),
|
||||
seq(`if (${fs} !== void 0) ${dest} = `,
|
||||
braces(... variantInitFor(variantName),
|
||||
... converterField(p[0], label),
|
||||
`... ${fs}`))];
|
||||
}))];
|
||||
}))];
|
||||
case M.$tuple:
|
||||
// assume dest is already void 0
|
||||
return converterForTuple(ctx, p[0], src, dest, variantName, recordFields, void 0);
|
||||
case M.$tuple_STAR_:
|
||||
// assume dest is already void 0
|
||||
return converterForTuple(ctx, p[0], src, dest, variantName, recordFields, p[1]);
|
||||
case M.$setof:
|
||||
case M.$dictof:
|
||||
throw new Error('Internal error: setof and dictof are handled in converterFor()');
|
||||
case M.$dict: {
|
||||
const entries = Array.from(p[0]);
|
||||
const temps = ctx.gentemps(entries.length);
|
||||
function loop(i: number): Item[] {
|
||||
if (i < entries.length) {
|
||||
const [k, n] = entries[i];
|
||||
const tmpSrc = ctx.gentemp();
|
||||
return [
|
||||
seq(`if ((${tmpSrc} = ${src}.get(${ctx.mod.literal(k)})) !== void 0) `,
|
||||
ctx.block(() => [
|
||||
...converterFor(ctx, M.unname(n), tmpSrc, temps[i]),
|
||||
seq(`if (${temps[i]} !== void 0) `, ctx.block(() =>
|
||||
loop(i + 1)))]))];
|
||||
} else {
|
||||
return [
|
||||
seq(`${dest} = `, braces(
|
||||
... variantInitFor(variantName),
|
||||
... entries.flatMap(([k, n], i) => converterField(n, temps[i], k))))];
|
||||
}
|
||||
}
|
||||
return [seq(`if (_.Dictionary.isDictionary(${src})) `, ctx.block(() => loop(0)))];
|
||||
}
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
function converterField(n: M.NamedPattern, src: string, k?: M.Input): Item[] {
|
||||
if (n.label === M.$named) {
|
||||
return [keyvalue(stringify(n[0]), src)];
|
||||
}
|
||||
if (k !== void 0) {
|
||||
const s = M.namelike(k);
|
||||
if (s !== void 0) {
|
||||
return [keyvalue(JSON.stringify(s), src)];
|
||||
}
|
||||
}
|
||||
if (M.isCompoundPattern(n)) {
|
||||
return [`... ${src}`];
|
||||
}
|
||||
return [];
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
import { FunctionContext } from "./context";
|
||||
import * as M from '../meta';
|
||||
import { anglebrackets, block, brackets, Item, opseq, parens, seq } from "./block";
|
||||
import { typeFor } from './type';
|
||||
import { refPosition } from "../reader";
|
||||
import { predicateFor } from "./predicate";
|
||||
|
||||
function decodeCompound(ctx: FunctionContext,
|
||||
p: M.Pattern,
|
||||
kFail: () => Item[],
|
||||
kAcc: (temp: string) => Item[]): Item
|
||||
{
|
||||
const t = ctx.gentemp();
|
||||
return seq(`while (!d.closeCompound()) `, ctx.block(() => [
|
||||
seq(`${t} = void 0`),
|
||||
... decoderFor(ctx, p, t),
|
||||
seq(`if (${t} === void 0) `, block(... kFail(), seq(`break`))),
|
||||
... kAcc(t)]));
|
||||
}
|
||||
|
||||
function decoderForTuple(ctx: FunctionContext,
|
||||
tuplePattern: M.Pattern,
|
||||
ps: M.Pattern[],
|
||||
dest: string,
|
||||
recordFields: boolean,
|
||||
variablePattern: M.Pattern | undefined): Item[]
|
||||
{
|
||||
const temps = ctx.gentemps(ps.length);
|
||||
|
||||
function loop(i: number): Item[] {
|
||||
if (i < ps.length) {
|
||||
return [... decoderFor(ctx, ps[i], temps[i]),
|
||||
seq(`if (${temps[i]} !== void 0) `, ctx.block(() => loop(i + 1)))];
|
||||
} else {
|
||||
if (variablePattern === void 0) {
|
||||
return [seq(`if (d.closeCompound()) ${dest} = `, brackets(... temps),
|
||||
` as `, typeFor(ctx.mod, tuplePattern))];
|
||||
} else {
|
||||
return [block(
|
||||
seq(`let vN: `, typeFor(ctx.mod, tuplePattern),
|
||||
` | undefined = `, brackets(... temps)),
|
||||
decodeCompound(ctx,
|
||||
variablePattern,
|
||||
() => [`vN = void 0`],
|
||||
(t) => [`vN.push(${t})`]),
|
||||
seq(`${dest} = vN`))];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return recordFields
|
||||
? loop(0)
|
||||
: [seq(`if (d.openSequence()) `, ctx.block(() => loop(0)))];
|
||||
}
|
||||
|
||||
export function decoderFor(ctx: FunctionContext, p: M.Definition, dest: string, recordFields = false): Item[]
|
||||
{
|
||||
switch (p.label) {
|
||||
case M.$atom:
|
||||
switch (p[0]) {
|
||||
case M.$Boolean: return [`${dest} = d.nextBoolean()`];
|
||||
case M.$Float: return [`${dest} = d.nextFloat()`];
|
||||
case M.$Double: return [`${dest} = d.nextDouble()`];
|
||||
case M.$SignedInteger: return [`${dest} = d.nextSignedInteger()`];
|
||||
case M.$String: return [`${dest} = d.nextString()`];
|
||||
case M.$ByteString: return [`${dest} = d.nextByteString()`];
|
||||
case M.$Symbol: return [`${dest} = d.nextSymbol()`];
|
||||
}
|
||||
case M.$lit: {
|
||||
let n: string;
|
||||
switch (typeof p[0]) {
|
||||
case 'boolean': n = `d.nextBoolean()`; break;
|
||||
case 'string': n = `d.nextString()`; break;
|
||||
case 'number': n = `d.nextSignedInteger()`; break;
|
||||
case 'symbol': n = `d.nextSymbol()`; break;
|
||||
default: n = `d.next()`; break;
|
||||
}
|
||||
return [`${dest} = _.asLiteral(${n}, ${ctx.mod.literal(p[0])})`];
|
||||
}
|
||||
case M.$ref:
|
||||
return M.lookup(refPosition(p), p, ctx.mod.env,
|
||||
(_p) => [`${dest} = decode${p[1].description!}(d)`],
|
||||
(p) => decoderFor(ctx, p, dest),
|
||||
(modId, modPath,_p) => {
|
||||
ctx.mod.imports.add([modId, modPath]);
|
||||
return [`${dest} = ${modId}.decode${p[1].description!}(d)`];
|
||||
});
|
||||
case M.$or: {
|
||||
const alts = p[0];
|
||||
const recs = alts.map(p => ctx.mod.derefPattern(p));
|
||||
if (recs.length > 1 && recs.every(pp => pp.label === M.$rec)) {
|
||||
// Hoist the record check up.
|
||||
// This is pretty hacky. If we lift the level of
|
||||
// discourse a little, we can do this
|
||||
// automatically and generically...
|
||||
return [seq(`if (d.openRecord()) `, ctx.block(() => {
|
||||
const label = ctx.gentemp();
|
||||
const mark = ctx.gentemp();
|
||||
function loop(i: number): Item[] {
|
||||
const alt = recs[i];
|
||||
if (alt.label !== M.$rec) throw new Error("Internal error"); // avoid a cast
|
||||
return [
|
||||
seq(`if (`, predicateFor(ctx, label, M.unname(alt[0])), `) `, ctx.block(() => {
|
||||
const fs = ctx.gentemp();
|
||||
return [...decoderFor(ctx, M.unname(alt[1]), fs, true),
|
||||
seq(`if (${fs} !== void 0) ${dest} = _.Record`,
|
||||
anglebrackets(typeFor(ctx.mod, M.unname(alt[0])),
|
||||
typeFor(ctx.mod, M.unname(alt[1]))),
|
||||
parens(seq(label, ` as any`),
|
||||
seq(fs, ` as any`)))];
|
||||
})),
|
||||
... (i < recs.length - 1)
|
||||
? [seq(`if (${dest} === void 0) `,
|
||||
ctx.block(() => [`d.restoreMark(${mark})`, ... loop(i + 1)]))]
|
||||
: [],
|
||||
];
|
||||
}
|
||||
return [seq(`${label} = d.next()`),
|
||||
seq(`${mark} = d.mark()`),
|
||||
... loop(0)];
|
||||
}))];
|
||||
} else {
|
||||
switch (alts.length) {
|
||||
case 0: return []; // assume dest is already void 0
|
||||
case 1: return decoderFor(ctx, alts[0][1], dest);
|
||||
default: {
|
||||
const mark = ctx.gentemp();
|
||||
function loop(i: number): Item[] {
|
||||
return [
|
||||
... decoderFor(ctx, alts[i][1], dest),
|
||||
... (i < alts.length - 1)
|
||||
? [seq(`if (${dest} === void 0) `, ctx.block(() =>
|
||||
[`d.restoreMark(${mark})`, ... loop(i + 1)]))]
|
||||
: [],
|
||||
];
|
||||
}
|
||||
return [`${mark} = d.mark()`, ... loop(0)];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case M.$and:
|
||||
switch (p[0].length) {
|
||||
case 0: return [`${dest} = d.next()`];
|
||||
case 1: return decoderFor(ctx, M.unname(p[0][0]), dest);
|
||||
default: {
|
||||
const [pp0, ... ppN] = p[0];
|
||||
return [...decoderFor(ctx, M.unname(pp0), dest),
|
||||
seq(`if (!`, opseq('true', ' && ',
|
||||
... ppN.map(pp =>
|
||||
predicateFor(ctx, dest, M.unname(pp)))),
|
||||
`) ${dest} = void 0`)];
|
||||
}
|
||||
}
|
||||
case M.$pointer:
|
||||
return [`${dest} = _decodePtr(d)`];
|
||||
case M.$rec:
|
||||
// assume dest is already void 0
|
||||
return [seq(`if (d.openRecord()) `, ctx.block(() => {
|
||||
const label = ctx.gentemp();
|
||||
return [...decoderFor(ctx, M.unname(p[0]), label),
|
||||
seq(`if (${label} !== void 0) `, ctx.block(() => {
|
||||
const fs = ctx.gentemp();
|
||||
return [...decoderFor(ctx, M.unname(p[1]), fs, true),
|
||||
seq(`if (${fs} !== void 0) ${dest} = _.Record`,
|
||||
anglebrackets(typeFor(ctx.mod, M.unname(p[0])),
|
||||
typeFor(ctx.mod, M.unname(p[1]))),
|
||||
parens(seq(label, ` as any`),
|
||||
seq(fs, ` as any`)))];
|
||||
}))];
|
||||
}))];
|
||||
case M.$tuple:
|
||||
// assume dest is already void 0
|
||||
return decoderForTuple(ctx, p, p[0].map(M.unname), dest, recordFields, void 0);
|
||||
case M.$tuple_STAR_:
|
||||
// assume dest is already void 0
|
||||
return decoderForTuple(ctx, p, p[0].map(M.unname), dest, recordFields, M.unname(p[1]));
|
||||
case M.$setof:
|
||||
// assume dest is already void 0
|
||||
return [seq(`if (d.openSet()) `, ctx.block(() => [
|
||||
seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedSet()`),
|
||||
decodeCompound(ctx,
|
||||
p[0],
|
||||
() => [`r = void 0`],
|
||||
(t) => [`r.add(${t})`]),
|
||||
`${dest} = r`]))];
|
||||
case M.$dictof:
|
||||
// assume dest is already void 0
|
||||
return [seq(`if (d.openDictionary()) `, ctx.block(() => [
|
||||
seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedDictionary()`),
|
||||
seq(`while (!d.closeCompound()) `, ctx.block(() => [
|
||||
seq(`let K: undefined | `, typeFor(ctx.mod, p[0]), ` = void 0`),
|
||||
... decoderFor(ctx, p[0], 'K'),
|
||||
seq(`if (K === void 0) { r = void 0; break; }`),
|
||||
seq(`let V: undefined | `, typeFor(ctx.mod, p[1]), ` = void 0`),
|
||||
... decoderFor(ctx, p[1], 'V'),
|
||||
seq(`if (V === void 0) { r = void 0; break; }`),
|
||||
seq(`r.set(K, V)`)])),
|
||||
seq(`${dest} = r`)]))];
|
||||
case M.$dict:
|
||||
return [seq(`${dest} = d.next()`),
|
||||
seq(`if (${dest} !== void 0 && !(`, predicateFor(ctx, dest, p),
|
||||
`)) ${dest} = void 0`)];
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
import { refPosition } from '../reader';
|
||||
import * as M from '../meta';
|
||||
import { block, fnblock, Item, opseq, parens, seq } from './block';
|
||||
import { FunctionContext } from './context';
|
||||
|
||||
export function predicateFor(ctx: FunctionContext, v: string, p: M.Definition, recordOkAsTuple = false): Item
|
||||
{
|
||||
switch (p.label) {
|
||||
case M.$atom:
|
||||
switch (p[0]) {
|
||||
case M.$Boolean: return `typeof ${v} === 'boolean'`;
|
||||
case M.$Float: return `_.Float.isSingle(${v})`;
|
||||
case M.$Double: return `_.Float.isDouble(${v})`;
|
||||
case M.$SignedInteger: return `typeof ${v} === 'number'`;
|
||||
case M.$String: return `typeof ${v} === 'string'`;
|
||||
case M.$ByteString: return `_.Bytes.isBytes(${v})`;
|
||||
case M.$Symbol: return `typeof ${v} === 'symbol'`;
|
||||
}
|
||||
case M.$lit:
|
||||
return `_.is(${v}, ${ctx.mod.literal(p[0])})`;
|
||||
case M.$ref:
|
||||
return M.lookup(refPosition(p), p, ctx.mod.env,
|
||||
(_p) => `is${M.Ref._.name(p).description!}(${v})`,
|
||||
(pp) => predicateFor(ctx, v, pp),
|
||||
(modId, modPath, _p) => {
|
||||
ctx.mod.imports.add([modId, modPath]);
|
||||
return `${modId}.is${M.Ref._.name(p).description!}(${v})`;
|
||||
});
|
||||
case M.$or: {
|
||||
const alts = p[0];
|
||||
const recs = alts.map(p => ctx.mod.derefPattern(p));
|
||||
if (recs.length > 1 && recs.every(pp => pp.label === M.$rec)) {
|
||||
return seq(
|
||||
`_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v}) && `,
|
||||
parens(opseq('false', ' || ',
|
||||
... recs.map(r =>
|
||||
(r.label !== M.$rec) ? '' : parens(seq(
|
||||
predicateFor(ctx, `${v}.label`, M.unname(r[0])),
|
||||
' && ',
|
||||
predicateFor(ctx, v, M.unname(r[1]), true)))))));
|
||||
} else {
|
||||
return opseq('false', ' || ', ... p[0].map(pp => predicateFor(ctx, v, pp[1])));
|
||||
}
|
||||
}
|
||||
case M.$and:
|
||||
return opseq('true', ' && ', ...p[0].map(pp => predicateFor(ctx, v, M.unname(pp))));
|
||||
case M.$pointer:
|
||||
return `_.isPointer(${v})`;
|
||||
case M.$rec:
|
||||
return opseq('true', ' && ',
|
||||
`_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`,
|
||||
predicateFor(ctx, `${v}.label`, M.unname(p[0])),
|
||||
predicateFor(ctx, v, M.unname(p[1]), true));
|
||||
case M.$tuple:
|
||||
return opseq('true', ' && ',
|
||||
... (recordOkAsTuple ? []
|
||||
: [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]),
|
||||
`(${v}.length === ${p[0].length})`,
|
||||
...p[0].map((pp, i) => predicateFor(ctx, `${v}[${i}]`, M.unname(pp))));
|
||||
case M.$tuple_STAR_:
|
||||
return opseq('true', ' && ',
|
||||
... (recordOkAsTuple ? []
|
||||
: [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]),
|
||||
`(${v}.length >= ${p[0].length})`,
|
||||
seq(p[0].length > 0 ? `${v}.slice(${p[0].length})` : v,
|
||||
`.every(v => `,
|
||||
parens(predicateFor(ctx, 'v', M.unname(p[1]))),
|
||||
`)`),
|
||||
...p[0].map((pp, i) => predicateFor(ctx, `${v}[${i}]`, M.unname(pp))));
|
||||
case M.$setof:
|
||||
return opseq('true', ' && ',
|
||||
`_.Set.isSet<_val>(${v})`,
|
||||
fnblock(
|
||||
seq(`for (const vv of ${v}) `, block(
|
||||
seq('if (!(', predicateFor(ctx, 'vv', p[0]), ')) return false'))),
|
||||
seq('return true')));
|
||||
case M.$dictof:
|
||||
return opseq('true', ' && ',
|
||||
`_.Dictionary.isDictionary<_ptr>(${v})`,
|
||||
fnblock(
|
||||
seq(`for (const e of ${v}) `, block(
|
||||
seq('if (!(', predicateFor(ctx, 'e[0]', p[0]), ')) return false'),
|
||||
seq('if (!(', predicateFor(ctx, 'e[1]', p[1]), ')) return false'))),
|
||||
seq('return true')));
|
||||
case M.$dict:
|
||||
return opseq('true', ' && ',
|
||||
`_.Dictionary.isDictionary<_ptr>(${v})`,
|
||||
... Array.from(p[0]).map(([k, vp]) => {
|
||||
const tmp = ctx.gentemp();
|
||||
return parens(seq(
|
||||
`(${tmp} = ${v}.get(${ctx.mod.literal(k)})) !== void 0 && `,
|
||||
predicateFor(ctx, tmp, M.unname(vp))));
|
||||
}));
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
import { refPosition } from "../reader";
|
||||
import * as M from "../meta";
|
||||
import { anglebrackets, braces, Item, keyvalue, opseq, seq } from "./block";
|
||||
import { ModuleContext } from "./context";
|
||||
import { stringify } from "@preserves/core";
|
||||
|
||||
export function typeFor(mod: ModuleContext, p: M.Pattern, variantName?: string): Item {
|
||||
let typeItem: Item;
|
||||
|
||||
if (M.isSimplePattern(p)) {
|
||||
typeItem = typeForSimple(mod, p);
|
||||
} else {
|
||||
switch (p.label) {
|
||||
case M.$setof:
|
||||
typeItem = seq(`_.KeyedSet`, anglebrackets(typeForSimple(mod, p[0]), '_ptr'));
|
||||
break;
|
||||
case M.$dictof:
|
||||
typeItem = seq(`_.KeyedDictionary`, anglebrackets(typeForSimple(mod, p[0]),
|
||||
typeForSimple(mod, p[1]),
|
||||
'_ptr'));
|
||||
break;
|
||||
default: {
|
||||
const arrayType = M.simpleArray(p);
|
||||
if (arrayType === void 0) {
|
||||
return braces(... variantInitFor(variantName),
|
||||
... typeForCompound(mod, p));
|
||||
} else {
|
||||
typeItem = seq('Array<', typeForSimple(mod, arrayType), '>');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (variantName === void 0) {
|
||||
return typeItem;
|
||||
} else {
|
||||
return braces(variantFor(variantName), keyvalue('value', typeItem));
|
||||
}
|
||||
}
|
||||
|
||||
function typeForSimple(mod: ModuleContext, p: M.SimplePattern): Item {
|
||||
switch (p.label) {
|
||||
case M.$atom:
|
||||
switch (p[0]) {
|
||||
case M.$Boolean: return `boolean`;
|
||||
case M.$Float: return `_.SingleFloat`;
|
||||
case M.$Double: return `_.DoubleFloat`;
|
||||
case M.$SignedInteger: return `number`;
|
||||
case M.$String: return `string`;
|
||||
case M.$ByteString: return `_.Bytes`;
|
||||
case M.$Symbol: return `symbol`;
|
||||
}
|
||||
case M.$pointer:
|
||||
return `_ptr`;
|
||||
case M.$lit:
|
||||
return `(typeof ${mod.literal(p[0])})`;
|
||||
case M.$ref:
|
||||
return M.lookup(refPosition(p), p, mod.env,
|
||||
(_p) => p[1].description!,
|
||||
(p) => typeForAlternative(mod, p, void 0),
|
||||
(modId, modPath,_p) => {
|
||||
mod.imports.add([modId, modPath]);
|
||||
return `${modId}.${p[1].description!}`;
|
||||
});
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
function typeField(mod: ModuleContext, n: M.NamedPattern): Item[] {
|
||||
return (n.label === M.$named)
|
||||
? [keyvalue(stringify(n[0]), typeForSimple(mod, n[1]))]
|
||||
: (M.isCompoundPattern(n)
|
||||
? typeForCompound(mod, n)
|
||||
: []);
|
||||
}
|
||||
|
||||
function typeForCompound(mod: ModuleContext, p: M.CompoundPattern): Item[] {
|
||||
switch (p.label) {
|
||||
case M.$rec:
|
||||
return [... typeField(mod, p[0]), ... typeField(mod, p[1])];
|
||||
case M.$tuple:
|
||||
return p[0].flatMap(pp => typeField(mod, pp));
|
||||
case M.$tuple_STAR_: {
|
||||
const n = p[1];
|
||||
return [... p[0].flatMap(pp => typeField(mod, pp)),
|
||||
... ((n.label === M.$named)
|
||||
? [keyvalue(stringify(n[0]),
|
||||
seq('Array<', typeForSimple(mod, n[1]), '>'))]
|
||||
: [])];
|
||||
}
|
||||
case M.$setof:
|
||||
case M.$dictof:
|
||||
return [];
|
||||
case M.$dict:
|
||||
return Array.from(p[0]).flatMap(([k, n]) => {
|
||||
if (n.label === M.$named) {
|
||||
return typeField(mod, n);
|
||||
} else {
|
||||
const s = M.namelike(k);
|
||||
if (s !== void 0) {
|
||||
return [keyvalue(JSON.stringify(s), typeForSimple(mod, n))];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
});
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
export function typeForDefinition(mod: ModuleContext, d: M.Definition): Item {
|
||||
if (d.label === M.$or) {
|
||||
return opseq('never', ' | ', ... d[0].map(a => typeForAlternative(mod, a[1], a[0])));
|
||||
} else {
|
||||
return typeForAlternative(mod, d, void 0);
|
||||
}
|
||||
}
|
||||
|
||||
export function variantInitFor(variantName: string | undefined) : Item[] {
|
||||
return variantName === void 0 ? [] : [variantFor(variantName)];
|
||||
}
|
||||
|
||||
export function variantFor(variantName: string): Item {
|
||||
return keyvalue('_variant', JSON.stringify(variantName));
|
||||
}
|
||||
|
||||
function typeForAlternative(mod: ModuleContext, a: M.Alternative, variantName: string | undefined): Item {
|
||||
if (a.label === M.$and) {
|
||||
return opseq('_val', ' & ',
|
||||
... variantName === void 0 ? [] : [braces(variantFor(variantName))],
|
||||
...a[0].map(p => typeFor(mod, M.unname(p))));
|
||||
} else {
|
||||
return typeFor(mod, a, variantName);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { Value, is, Position } from '@preserves/core';
|
||||
import { ModulePath, Ref, Schema, $definitions, Definition, Alternative } from './gen/schema';
|
||||
import { Value, is, Position, stringify } from '@preserves/core';
|
||||
import * as M from './gen/schema';
|
||||
import { BASE } from './base';
|
||||
import { SchemaSyntaxError } from './error';
|
||||
|
||||
|
@ -22,10 +22,10 @@ export const EQUALS = Symbol.for('=');
|
|||
export const INCLUDE = Symbol.for('include');
|
||||
export const ORSYM = Symbol.for('/');
|
||||
|
||||
export type SchemaEnvEntry = { schemaModulePath: ModulePath } & (
|
||||
export type SchemaEnvEntry = { schemaModulePath: M.ModulePath } & (
|
||||
({
|
||||
typescriptModulePath: string | null, // null means it's "this module" in disguise
|
||||
schema: Schema,
|
||||
schema: M.Schema,
|
||||
}) | ({
|
||||
typescriptModulePath: string,
|
||||
schema: null,
|
||||
|
@ -39,21 +39,21 @@ function modsymFor(e: SchemaEnvEntry): string {
|
|||
}
|
||||
|
||||
export function lookup<R>(namePos: Position | null,
|
||||
name: Ref,
|
||||
name: M.Ref,
|
||||
env: Environment,
|
||||
kLocal: (p: Definition) => R,
|
||||
kBase: (p: Alternative) => R,
|
||||
kOther: (modId: string, modPath: string, p: Definition | null) => R): R
|
||||
kLocal: (p: M.Definition) => R,
|
||||
kBase: (p: M.Alternative) => R,
|
||||
kOther: (modId: string, modPath: string, p: M.Definition | null) => R): R
|
||||
{
|
||||
for (const e of env) {
|
||||
if (is(e.schemaModulePath, Ref._.module(name)) ||
|
||||
(e.typescriptModulePath === null && Ref._.module(name).length === 0))
|
||||
if (is(e.schemaModulePath, M.Ref._.module(name)) ||
|
||||
(e.typescriptModulePath === null && M.Ref._.module(name).length === 0))
|
||||
{
|
||||
if (e.schema === null) {
|
||||
// It's an artificial module, not from a schema. Assume the identifier is present.
|
||||
return kOther(modsymFor(e), e.typescriptModulePath, null);
|
||||
} else {
|
||||
const p = Schema._._field0(e.schema).get($definitions).get(Ref._.name(name));
|
||||
const p = M.Schema._._field0(e.schema).get(M.$definitions).get(M.Ref._.name(name));
|
||||
if (p !== void 0) {
|
||||
if (e.typescriptModulePath === null) {
|
||||
return kLocal(p);
|
||||
|
@ -65,14 +65,37 @@ export function lookup<R>(namePos: Position | null,
|
|||
}
|
||||
}
|
||||
|
||||
if (Ref._.module(name).length === 0) {
|
||||
const p = Schema._._field0(BASE).get($definitions).get(Ref._.name(name));
|
||||
if (p !== void 0) return kBase(p as Alternative);
|
||||
if (M.Ref._.module(name).length === 0) {
|
||||
const p = M.Schema._._field0(BASE).get(M.$definitions).get(M.Ref._.name(name));
|
||||
if (p !== void 0) return kBase(p as M.Alternative);
|
||||
}
|
||||
|
||||
throw new SchemaSyntaxError(`Undefined reference: ${formatRef(name)}`, namePos);
|
||||
}
|
||||
|
||||
export function formatRef(r: Ref): string {
|
||||
export function formatRef(r: M.Ref): string {
|
||||
return [... r[0], r[1]].map(s => s.description!).join('.');
|
||||
}
|
||||
|
||||
export function unname<R extends M.Pattern | M.SimplePattern>(
|
||||
p: M.NamedSimplePattern_ | R): M.SimplePattern | R
|
||||
{
|
||||
return (p.label === M.$named) ? p[1] : p;
|
||||
}
|
||||
|
||||
// Simple arrays at toplevel for convenience
|
||||
//
|
||||
export function simpleArray(p: M.CompoundPattern): M.SimplePattern | undefined {
|
||||
if (p.label === M.$tuple_STAR_ && p[0].length === 0 && p[1].label !== M.$named) {
|
||||
return p[1];
|
||||
} else {
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function namelike(x: Input): string | undefined {
|
||||
if (typeof x === 'string') return x;
|
||||
if (typeof x === 'symbol') return stringify(x);
|
||||
if (typeof x === 'number') return '' + x;
|
||||
return void 0;
|
||||
}
|
||||
|
|
|
@ -141,7 +141,9 @@ function parseDefinition(name: symbol, body: Array<Input>): Definition {
|
|||
switch (typeof p[0]) {
|
||||
case 'symbol': return [p[0].description!, p];
|
||||
case 'string': return [p[0], p];
|
||||
case 'number': return ['' + p[0], p];
|
||||
case 'boolean':
|
||||
case 'number':
|
||||
return ['' + p[0], p];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -198,10 +200,20 @@ function parsePattern(name: symbol, body0: Array<Input>): Pattern {
|
|||
});
|
||||
const walk = (b: Input): Pattern => parsePattern(name, [b]);
|
||||
|
||||
function _maybeNamed<R>(recur: (b: Input) => R): (b: Input) => M.NamedSimplePattern_ | R {
|
||||
function _maybeNamed<R>(
|
||||
recur: (b: Input) => R,
|
||||
literalName?: Input): (b: Input) => M.NamedSimplePattern_ | R
|
||||
{
|
||||
return (b: Input) => {
|
||||
const name = findName(b);
|
||||
if (name === false) return recur(b);
|
||||
let name = findName(b);
|
||||
if (name === false) {
|
||||
if (literalName !== void 0 && typeof literalName === 'symbol') {
|
||||
name = literalName;
|
||||
}
|
||||
}
|
||||
if (name === false) {
|
||||
return recur(b);
|
||||
}
|
||||
return Record(M.$named, [name, parseSimple(b, () => {
|
||||
throw new SchemaSyntaxError(`Named patterns must be Simple patterns`, position(b));
|
||||
})]);
|
||||
|
@ -242,7 +254,7 @@ function parsePattern(name: symbol, body0: Array<Input>): Pattern {
|
|||
return Record(M.$dictof, [walkSimple(kp), walkSimple(vp)]);
|
||||
} else {
|
||||
return Record(M.$dict, [item.mapEntries<M.NamedSimplePattern, Input, never>(
|
||||
([k, vp]) => [strip(k), maybeNamedSimple(vp)])]);
|
||||
([k, vp]) => [strip(k), _maybeNamed(walkSimple, k)(vp)])]);
|
||||
}
|
||||
} else if (Set.isSet<never>(item)) {
|
||||
if (item.size !== 1) complain();
|
||||
|
|
Loading…
Reference in New Issue