import { encode, stringify } from "@preserves/core"; import * as M from "./meta"; import { CompilerOptions, ModuleContext } from "./compiler/context"; import { Formatter, block, seq, braces } from "./compiler/block"; import { typeForDefinition } from "./compiler/gentype"; import { converterForDefinition } from "./compiler/genconverter"; import { renderTypeWithConversionMixins } from "./compiler/rendertype"; import { genConstructor } from "./compiler/genctor"; import { unconverterForDefinition } from "./compiler/genunconverter"; import { sourceCodeFor } from "./compiler/value"; import { fromSchema } from "./meta"; export function compile( env: M.Environment, modulePath: M.ModulePath, schema: M.Schema, options: CompilerOptions = {}, ): string { const mod = new ModuleContext(env, modulePath, schema, options); mod.definePreamble(`let __schema: _.Value | null = null;`); mod.definePreamble(seq(`export function _schema() `, block( seq(`if (__schema === null) `, block( `__schema = _.decode<_.GenericEmbedded>(_.Bytes.fromHex("${encode(fromSchema(schema)).toHex()}"))`)), `return __schema`))); const embeddedName = schema.embeddedType; if (embeddedName._variant !== 'false') { mod.defineType(seq(`export type _embedded = `, mod.embeddedType, `;`)); } for (const [name, def] of schema.definitions) { const t = typeForDefinition(mod.resolver(), def); const nameStr = stringify(name); const resultTypeItem = seq(nameStr, mod.genericArgsFor(t)); mod.defineType(seq(`export type ${nameStr}`, mod.genericParametersFor(t), ` = `, renderTypeWithConversionMixins(mod, t), `;`)); if (t.kind === 'union') { mod.defineFunctions(nameStr, _ctx => [seq(`export namespace ${nameStr} `, block( ... Array.from(t.variants).flatMap(([vn, vt]) => genConstructor(mod, nameStr, vn, vn, vt, t, resultTypeItem)) ))]); } else { mod.defineFunctions(nameStr, _ctx => genConstructor(mod, nameStr, nameStr, void 0, t, t, resultTypeItem)); } } for (const [name0, def] of schema.definitions) { const t = typeForDefinition(mod.resolver(), def); const name = name0 as symbol; const nameStr = name0.description!; const resultTypeItem = seq(nameStr, mod.genericArgsFor(t)); mod.defineFunctions(nameStr, ctx => [seq(`export function as${name.description!}`, mod.genericParameters(), `(v: _.Value<_embedded>): `, resultTypeItem, ` `, ctx.block(() => [ seq(`let result = to${name.description!}(v)`), seq(`if (result === void 0) `, `throw new TypeError(\`Invalid ${name.description!}: \${_.stringify(v)}\`)`), seq(`return result`)]))]); mod.defineFunctions(nameStr, ctx => [seq(`export function to${name.description!}`, mod.genericParameters(), `(v: _.Value<_embedded>): undefined | `, resultTypeItem, ` `, ctx.block(() => [seq(`let result: undefined | `, resultTypeItem), ... converterForDefinition(ctx, def, `v`, `result`), seq(`return result`)])), (t.kind === 'union') ? seq(`export namespace ${M.jsId(name.description!)} `, block( seq(`export const __from_preserve__ = to${name.description!}`))) : seq(`${M.jsId(name.description!)}.__from_preserve__ = to${name.description!};`)]); mod.defineFunctions(nameStr, ctx => [seq(`export function from${name.description!}`, mod.genericParameters(), `(_v: `, name.description!, mod.genericArgsFor(t), `): _.Value<_embedded> `, ctx.block(() => unconverterForDefinition(ctx, def, `_v`)))]); } mod.definePreamble( seq(`export const _imports = `, braces(... Array.from(mod.imports.values()).map( ([modulePath, identifier, _path, expr]) => seq(stringify(M.formatModulePath(modulePath)), ': ', identifier, expr))))); const f = new Formatter(); f.write(`import * as _ from ${JSON.stringify(options.preservesModule ?? '@preserves/core')};\n`); mod.imports.forEach(([_modulePath, identifier, path, _expr]) => { f.write(`import * as ${identifier} from ${JSON.stringify(path)};\n`); }); f.newline(); const sortedLiterals = Array.from(mod.literals); 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`)); } f.newline(); mod.preamble.forEach(i => { f.write(i); f.newline(); f.newline(); }); f.newline(); mod.typedefs.forEach(t => { f.write(t); f.newline(); f.newline(); }); f.newline(); mod.functiondefs.forEach(p => { f.write(p); f.newline(); f.newline(); }); return f.toString(); }