preserves/implementations/javascript/packages/schema/src/compiler.ts

611 lines
26 KiB
TypeScript
Raw Normal View History

2021-03-11 09:56:49 +00:00
import { Pattern, NamedPattern, Schema, Input, Environment, Ref, lookup } from "./meta";
2021-03-09 14:59:40 +00:00
import * as M from './meta';
2021-03-11 16:59:40 +00:00
import { Annotated, Bytes, Dictionary, Fold, fold, KeyedSet, Position, preserves, Record, Set, Tuple, Value } from "@preserves/core";
2021-03-09 15:45:57 +00:00
import { Formatter, parens, seq, Item, opseq, block, commas, brackets, anglebrackets, braces } from "./block";
import { refPosition } from "./reader";
import { Alternative, Definition } from "gen/schema";
2021-03-09 14:59:40 +00:00
2021-03-11 16:59:40 +00:00
export interface CompilerOptions {
preservesModule?: string;
defaultPointer?: Ref;
warn?(message: string, pos: Position | null): void;
};
2021-03-09 14:59:40 +00:00
function fnblock(... items: Item[]): Item {
return seq('((() => ', block(... items), ')())');
}
2021-03-17 14:59:46 +00:00
class ModuleContext {
readonly env: Environment;
readonly schema: Schema;
readonly options: CompilerOptions;
2021-03-09 14:59:40 +00:00
2021-03-17 14:59:46 +00:00
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;
2021-03-09 14:59:40 +00:00
}
2021-03-17 14:59:46 +00:00
literal(v: Input): Item {
let varname = this.literals.get(v);
2021-03-09 14:59:40 +00:00
if (varname === void 0) {
2021-03-10 22:15:53 +00:00
const s = v.asPreservesText()
.replace('_', '__')
.replace('*', '_STAR_');
2021-03-17 14:59:46 +00:00
varname = M.isValidToken('_' + s, true) ? '$' + s : '__lit' + this.literals.size;
this.literals.set(v, varname);
2021-03-09 14:59:40 +00:00
}
return varname;
}
2021-03-17 19:23:55 +00:00
derefPattern([_name, p]: [string, Alternative]): Definition {
2021-03-14 20:10:47 +00:00
if (p.label === M.$ref) {
2021-03-17 14:59:46 +00:00
return lookup(refPosition(p), p, this.env,
2021-03-14 20:10:47 +00:00
(p) => p,
(p) => p,
2021-03-17 14:59:46 +00:00
(_modId, _modPath, pp) => pp ?? p);
2021-03-14 20:10:47 +00:00
} else {
return p;
}
}
2021-03-17 14:59:46 +00:00
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))];
2021-03-14 20:10:47 +00:00
} else {
2021-03-17 14:59:46 +00:00
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`))];
2021-03-14 20:10:47 +00:00
}
}
}
2021-03-17 14:59:46 +00:00
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()`];
2021-03-14 20:10:47 +00:00
}
2021-03-17 14:59:46 +00:00
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;
2021-03-14 20:10:47 +00:00
}
2021-03-17 14:59:46 +00:00
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 [
2021-03-17 18:17:19 +00:00
seq(`if (`, predicateFor(ctx, label, unname(alt[0])), `) `, ctx.block(() => {
2021-03-17 14:59:46 +00:00
const fs = ctx.gentemp();
2021-03-17 18:17:19 +00:00
return [... decoderFor(ctx, unname(alt[1]), fs, true),
2021-03-17 14:59:46 +00:00
seq(`if (${fs} !== void 0) ${dest} = _.Record`,
2021-03-17 18:17:19 +00:00
anglebrackets(typeFor(ctx.mod, unname(alt[0])),
typeFor(ctx.mod, unname(alt[1]))),
2021-03-17 14:59:46 +00:00
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: {
2021-03-17 14:59:46 +00:00
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)];
}
}
2021-03-17 14:59:46 +00:00
}
}
2021-03-17 14:59:46 +00:00
case M.$and:
switch (p[0].length) {
case 0: return [`${dest} = d.next()`];
2021-03-17 18:17:19 +00:00
case 1: return decoderFor(ctx, unname(p[0][0]), dest);
2021-03-17 14:59:46 +00:00
default: {
const [pp0, ... ppN] = p[0];
2021-03-17 18:17:19 +00:00
return [... decoderFor(ctx, unname(pp0), dest),
2021-03-17 14:59:46 +00:00
seq(`if (!`, opseq('true', ' && ',
2021-03-17 18:17:19 +00:00
... ppN.map(pp =>
predicateFor(ctx, dest, unname(pp)))),
2021-03-17 14:59:46 +00:00
`) ${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();
2021-03-17 18:17:19 +00:00
return [... decoderFor(ctx, unname(p[0]), label),
2021-03-17 14:59:46 +00:00
seq(`if (${label} !== void 0) `, ctx.block(() => {
const fs = ctx.gentemp();
2021-03-17 18:17:19 +00:00
return [... decoderFor(ctx, unname(p[1]), fs, true),
2021-03-17 14:59:46 +00:00
seq(`if (${fs} !== void 0) ${dest} = _.Record`,
2021-03-17 18:17:19 +00:00
anglebrackets(typeFor(ctx.mod, unname(p[0])),
typeFor(ctx.mod, unname(p[1]))),
2021-03-17 14:59:46 +00:00
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");
}
2021-03-17 14:59:46 +00:00
}
2021-03-17 14:59:46 +00:00
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:
2021-03-17 18:17:19 +00:00
return seq('_.Record', anglebrackets(typeFor(mod, unname(p[0])),
typeFor(mod, unname(p[1])), '_ptr'));
2021-03-17 14:59:46 +00:00
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");
2021-03-09 14:59:40 +00:00
}
2021-03-17 14:59:46 +00:00
}
2021-03-09 14:59:40 +00:00
2021-03-17 14:59:46 +00:00
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);
}
2021-03-17 14:59:46 +00:00
}
2021-03-17 14:59:46 +00:00
function typeForAlternative(mod: ModuleContext, a: Alternative): Item {
if (a.label === M.$and) {
2021-03-17 18:17:19 +00:00
return opseq('_val', ' & ', ... a[0].map(p => typeFor(mod, unname(p))));
2021-03-17 14:59:46 +00:00
} else {
return typeFor(mod, a);
}
2021-03-17 14:59:46 +00:00
}
2021-03-17 14:59:46 +00:00
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(
2021-03-17 18:17:19 +00:00
predicateFor(ctx, `${v}.label`, unname(r[0])),
2021-03-17 14:59:46 +00:00
' && ',
2021-03-17 18:17:19 +00:00
predicateFor(ctx, v, unname(r[1]), true)))))));
2021-03-17 14:59:46 +00:00
} else {
return opseq('false', ' || ', ... p[0].map(pp => predicateFor(ctx, v, pp[1])));
2021-03-14 20:10:47 +00:00
}
2021-03-09 14:59:40 +00:00
}
2021-03-17 14:59:46 +00:00
case M.$and:
2021-03-17 18:17:19 +00:00
return opseq('true', ' && ', ... p[0].map(pp => predicateFor(ctx, v, unname(pp))));
2021-03-17 14:59:46 +00:00
case M.$pointer:
return `_.isPointer(${v})`;
case M.$rec:
return opseq('true', ' && ',
`_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`,
2021-03-17 18:17:19 +00:00
predicateFor(ctx, `${v}.label`, unname(p[0])),
predicateFor(ctx, v, unname(p[1]), true));
2021-03-17 14:59:46 +00:00
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");
2021-03-09 14:59:40 +00:00
}
2021-03-17 14:59:46 +00:00
}
2021-03-09 14:59:40 +00:00
2021-03-17 14:59:46 +00:00
export function compile(env: Environment, schema: Schema, options: CompilerOptions = {}): string {
const mod = new ModuleContext(env, schema, options);
2021-03-09 14:59:40 +00:00
2021-03-17 15:25:29 +00:00
const pointerName = Schema._._field0(schema).get(M.$pointer);
mod.defineType(seq(`export type _ptr = `,
pointerName === false ? 'never' : typeFor(mod, pointerName),
`;`));
mod.defineType(`export type _val = _.Value<_ptr>;`);
mod.defineFunction(ctx =>
seq(`export const _decodePtr = `,
(pointerName === false
? '() => { throw new _.DecodeError("Pointers forbidden"); }'
: seq(`(d: _.TypedDecoder<_ptr>) => `, ctx.block(() => [
seq(`let result`),
... decoderFor(ctx, pointerName, 'result'),
seq(`return result`)]))),
`;`));
for (const [name0, def] of Schema._._field0(schema).get(M.$definitions)) {
2021-03-09 14:59:40 +00:00
const name = name0 as symbol;
2021-03-17 14:59:46 +00:00
if (def.label === M.$rec &&
def[0].label === M.$lit &&
def[1].label === M.$tuple)
2021-03-09 15:45:57 +00:00
{
2021-03-17 14:59:46 +00:00
function fieldEntry(np: NamedPattern, index: number): Item {
return seq(JSON.stringify(fieldName(np, index)), ': ', typeFor(mod, unname(np)));
}
mod.defineType(
2021-03-09 15:45:57 +00:00
seq(`export const ${name.description!} = _.Record.makeConstructor<`,
braces(... def[1][0].map(fieldEntry)),
2021-03-17 14:59:46 +00:00
`, _ptr>()(${mod.literal(def[0][0])}, `,
JSON.stringify(def[1][0].map(fieldName)), `);`));
2021-03-09 15:45:57 +00:00
}
2021-03-17 14:59:46 +00:00
mod.defineType(
seq(`export type ${name.description!} = `, typeForDefinition(mod, name, def), `;`));
2021-03-17 15:25:29 +00:00
}
for (const [name0, def] of Schema._._field0(schema).get(M.$definitions)) {
const name = name0 as symbol;
2021-03-17 14:59:46 +00:00
mod.defineFunction(ctx =>
seq(`export function is${name.description!}`,
2021-03-09 14:59:40 +00:00
'(v: any): v is ', name.description!, ' ',
2021-03-17 14:59:46 +00:00
ctx.block(() => [seq('return ', predicateFor(ctx, 'v', def))])));
mod.defineFunction(ctx =>
seq(`export function as${name.description!}`,
2021-03-11 08:25:17 +00:00
'(v: any): ', name.description!, ' ',
2021-03-17 14:59:46 +00:00
ctx.block(() => [
2021-03-11 08:25:17 +00:00
seq(`if (!is${name.description!}(v)) `,
2021-03-11 16:59:40 +00:00
block(`throw new TypeError(\`Invalid ${name.description!}: \${_.stringify(v)}\`)`),
2021-03-11 08:25:17 +00:00
' else ',
2021-03-17 14:59:46 +00:00
block(`return v`))])));
2021-03-17 14:59:46 +00:00
mod.defineFunction(ctx =>
seq(`export function decode${name.description!}`,
`(d: _.TypedDecoder<_ptr>): ${name.description!} | undefined `,
2021-03-17 14:59:46 +00:00
ctx.block(() => [seq(`let result`),
... decoderFor(ctx, def, 'result'),
seq(`return result`)])));
2021-03-09 14:59:40 +00:00
}
const f = new Formatter();
2021-03-11 16:59:40 +00:00
f.write(`import * as _ from ${JSON.stringify(options.preservesModule ?? '@preserves/core')};\n`);
2021-03-17 14:59:46 +00:00
mod.imports.forEach(([identifier, path]) => {
f.write(`import * as ${identifier} from ${JSON.stringify(path)};\n`);
});
2021-03-09 14:59:40 +00:00
f.newline();
2021-03-17 14:59:46 +00:00
const sortedLiterals = Array.from(mod.literals);
2021-03-09 15:45:57 +00:00
sortedLiterals.sort((a, b) => a[1] < b[1] ? -1 : a[1] === b[1] ? 0 : 1);
for (const [lit, varname] of sortedLiterals) {
f.write(seq(`export const ${varname} = `, sourceCodeFor(lit), `;\n`));
2021-03-09 14:59:40 +00:00
}
f.newline();
2021-03-17 14:59:46 +00:00
mod.typedefs.forEach(t => {
2021-03-09 14:59:40 +00:00
f.write(t);
f.newline();
2021-03-09 15:45:57 +00:00
f.newline();
2021-03-09 14:59:40 +00:00
});
f.newline();
2021-03-17 14:59:46 +00:00
mod.functiondefs.forEach(p => {
2021-03-09 14:59:40 +00:00
f.write(p);
f.newline();
f.newline();
});
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); },
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!)})`; },
record(r: Record<Value<any>, Tuple<Value<any>>, any>, k: Fold<any, Item>): Item {
2021-03-11 16:59:40 +00:00
return seq(`_.Record<_val, _.Tuple<_val>, _ptr>`, parens(k(r.label), brackets(... r.map(k))));
2021-03-09 14:59:40 +00:00
},
array(a: Array<Value<any>>, k: Fold<any, Item>): Item {
return brackets(... a.map(k));
},
set(s: Set<any>, k: Fold<any, Item>): Item {
2021-03-11 16:59:40 +00:00
return seq('new _.Set<_val>', parens(brackets(... Array.from(s).map(k))));
2021-03-09 14:59:40 +00:00
},
dictionary(d: Dictionary<any>, k: Fold<any, Item>): Item {
return seq('new _.Dictionary<_ptr>', parens(brackets(... Array.from(d).map(([kk,vv]) =>
2021-03-09 14:59:40 +00:00
brackets(k(kk), k(vv))))));
},
annotated(a: Annotated<any>, k: Fold<any, Item>): Item {
2021-03-11 16:59:40 +00:00
return seq('_.annotate<_ptr>', parens(k(a.item), ... a.annotations.map(k)));
2021-03-09 14:59:40 +00:00
},
pointer(t: any, _k: Fold<any, Item>): Item {
throw new Error(preserves`Cannot emit source code for construction of pointer ${t}`);
},
});
}