Export reflective information about schema-generated types
This commit is contained in:
parent
5a0fbeb1b5
commit
1e4bf0dc51
|
@ -1,13 +1,14 @@
|
|||
import { stringify } from "@preserves/core";
|
||||
import { encode, stringify } from "@preserves/core";
|
||||
import * as M from "./meta";
|
||||
import { CompilerOptions, ModuleContext } from "./compiler/context";
|
||||
import { Formatter, block, seq } from "./compiler/block";
|
||||
import { Formatter, block, seq, braces } from "./compiler/block";
|
||||
import { typeForDefinition } from "./compiler/gentype";
|
||||
import { converterForDefinition } from "./compiler/genconverter";
|
||||
import { renderType } 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,
|
||||
|
@ -17,6 +18,12 @@ export function compile(
|
|||
): string {
|
||||
const mod = new ModuleContext(env, modulePath, schema, options);
|
||||
|
||||
mod.definePreamble(`let __schema: _.Value<unknown> | null = null;`);
|
||||
mod.definePreamble(seq(`export function _schema() `, block(
|
||||
seq(`if (__schema === null) `, block(
|
||||
`__schema = _.decode(_.Bytes.fromHex("${encode(fromSchema(schema)).toHex()}"))`)),
|
||||
`return __schema`)));
|
||||
|
||||
const embeddedName = schema.embeddedType;
|
||||
if (embeddedName._variant !== 'false') {
|
||||
mod.defineType(seq(`export type _embedded = `, mod.embeddedType, `;`));
|
||||
|
@ -34,11 +41,11 @@ export function compile(
|
|||
mod.defineFunctions(_ctx =>
|
||||
[seq(`export namespace ${nameStr} `, block(
|
||||
... Array.from(t.variants).flatMap(([vn, vt]) =>
|
||||
genConstructor(mod, vn, vn, vt, t, resultTypeItem))
|
||||
genConstructor(mod, nameStr, vn, vn, vt, t, resultTypeItem))
|
||||
))]);
|
||||
} else {
|
||||
mod.defineFunctions(_ctx =>
|
||||
genConstructor(mod, nameStr, void 0, t, t, resultTypeItem));
|
||||
genConstructor(mod, nameStr, nameStr, void 0, t, t, resultTypeItem));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,9 +75,14 @@ export function compile(
|
|||
ctx.block(() => unconverterForDefinition(ctx, def, `_v`)))]);
|
||||
}
|
||||
|
||||
mod.definePreamble(
|
||||
seq(`export const _imports = `, braces(... Array.from(mod.imports.values()).map(
|
||||
([modulePath, identifier, _path]) =>
|
||||
seq(stringify(M.formatModulePath(modulePath)), ': ', identifier)))));
|
||||
|
||||
const f = new Formatter();
|
||||
f.write(`import * as _ from ${JSON.stringify(options.preservesModule ?? '@preserves/core')};\n`);
|
||||
mod.imports.forEach(([identifier, path]) => {
|
||||
mod.imports.forEach(([_modulePath, identifier, path]) => {
|
||||
f.write(`import * as ${identifier} from ${JSON.stringify(path)};\n`);
|
||||
});
|
||||
f.newline();
|
||||
|
@ -82,6 +94,13 @@ export function compile(
|
|||
}
|
||||
f.newline();
|
||||
|
||||
mod.preamble.forEach(i => {
|
||||
f.write(i);
|
||||
f.newline();
|
||||
f.newline();
|
||||
});
|
||||
f.newline();
|
||||
|
||||
mod.typedefs.forEach(t => {
|
||||
f.write(t);
|
||||
f.newline();
|
||||
|
|
|
@ -28,9 +28,10 @@ export class ModuleContext {
|
|||
readonly embeddedType: Item;
|
||||
|
||||
readonly literals = new Dictionary<M.InputEmbedded, string>();
|
||||
readonly preamble: Item[] = [];
|
||||
readonly typedefs: Item[] = [];
|
||||
readonly functiondefs: Item[] = [];
|
||||
readonly imports = new KeyedSet<[string, string]>();
|
||||
readonly imports = new KeyedSet<[M.ModulePath, string, string]>();
|
||||
|
||||
constructor(
|
||||
env: M.Environment,
|
||||
|
@ -71,14 +72,19 @@ export class ModuleContext {
|
|||
p.value._variant === 'SimplePattern' &&
|
||||
p.value.value._variant === 'Ref')
|
||||
{
|
||||
return this.lookup(p.value.value.value,
|
||||
(p, _t) => this.derefPattern(p, refCount + 1),
|
||||
(_modId, _modPath, pp, _tt) => this.derefPattern(pp ?? p, refCount + 1));
|
||||
return this.lookup(
|
||||
p.value.value.value,
|
||||
(p, _t) => this.derefPattern(p, refCount + 1),
|
||||
(_modPath, _modId, _modFile, pp, _tt) => this.derefPattern(pp ?? p, refCount + 1));
|
||||
} else {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
definePreamble(i: Item): void {
|
||||
this.preamble.push(i);
|
||||
}
|
||||
|
||||
defineType(f: Item): void {
|
||||
this.typedefs.push(f);
|
||||
}
|
||||
|
@ -88,38 +94,43 @@ export class ModuleContext {
|
|||
}
|
||||
|
||||
resolver(modulePath?: M.ModulePath): (ref: M.Ref) => RefType {
|
||||
return (ref) => this.lookup(ref,
|
||||
(_p, _t) => Type.ref(ref.name.description!, ref),
|
||||
(modId, modPath, _p, _t) => {
|
||||
this.imports.add([modId, modPath]);
|
||||
return Type.ref(`${modId}.${ref.name.description!}`, ref);
|
||||
},
|
||||
modulePath);
|
||||
return (ref) => this.lookup(
|
||||
ref,
|
||||
(_p, _t) => Type.ref(ref.name.description!, ref),
|
||||
(modPath, modId, modFile, _p, _t) => {
|
||||
this.imports.add([modPath, modId, modFile]);
|
||||
return Type.ref(`${modId}.${ref.name.description!}`, ref);
|
||||
},
|
||||
modulePath);
|
||||
}
|
||||
|
||||
lookupType(name: M.Ref, modulePath?: M.ModulePath): Type | null {
|
||||
const t = this.lookup(name, (_p, t) => t, (_modId, _modPath, _p, t) => t, modulePath);
|
||||
const t = this.lookup(name, (_p, t) => t, (_modPath, _modId, _modFile, _p, t) => t, modulePath);
|
||||
return t ? t() : null;
|
||||
}
|
||||
|
||||
lookup<R>(name: M.Ref,
|
||||
kLocal: (p: M.Definition, t: () => Type) => R,
|
||||
kOther: (modId: string, modPath: string, p: M.Definition | null, t: (() => Type) | null) => R,
|
||||
modulePath?: M.ModulePath): R
|
||||
{
|
||||
kOther: (modPath: M.ModulePath,
|
||||
modId: string,
|
||||
modFile: string,
|
||||
p: M.Definition | null,
|
||||
t: (() => Type) | null) => R,
|
||||
modulePath?: M.ModulePath)
|
||||
: R {
|
||||
const soughtModule = name.module.length ? name.module : (modulePath ?? this.modulePath);
|
||||
|
||||
for (const e of this.env) {
|
||||
if (is(e.schemaModulePath, soughtModule)) {
|
||||
if (e.schema === null) {
|
||||
// It's an artificial module, not from a schema. Assume the identifier is present.
|
||||
return kOther(M.modsymFor(e), e.typescriptModulePath, null, null);
|
||||
return kOther(soughtModule, M.modsymFor(e), e.typescriptModulePath, null, null);
|
||||
} else {
|
||||
const p = e.schema.definitions.get(name.name);
|
||||
if (p !== void 0) {
|
||||
let t = () => typeForDefinition(this.resolver(soughtModule), p);
|
||||
if (name.module.length) {
|
||||
return kOther(M.modsymFor(e), e.typescriptModulePath, p, t);
|
||||
return kOther(soughtModule, M.modsymFor(e), e.typescriptModulePath, p, t);
|
||||
} else {
|
||||
return kLocal(p, t);
|
||||
}
|
||||
|
|
|
@ -183,12 +183,13 @@ export function converterForSimple(
|
|||
seq(`break`)]))];
|
||||
}))];
|
||||
case 'Ref':
|
||||
return ctx.mod.lookup(p.value,
|
||||
(_p, _t) => [`${dest} = to${p.value.name.description!}(${src})`],
|
||||
(modId, modPath, _p, _t) => {
|
||||
ctx.mod.imports.add([modId, modPath]);
|
||||
return [`${dest} = ${modId}.to${p.value.name.description!}(${src})`];
|
||||
});
|
||||
return ctx.mod.lookup(
|
||||
p.value,
|
||||
(_p, _t) => [`${dest} = to${p.value.name.description!}(${src})`],
|
||||
(modPath, modId, modFile, _p, _t) => {
|
||||
ctx.mod.imports.add([modPath, modId, modFile]);
|
||||
return [`${dest} = ${modId}.to${p.value.name.description!}(${src})`];
|
||||
});
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
|
|
|
@ -6,6 +6,7 @@ import { ModuleContext } from './context';
|
|||
|
||||
export function genConstructor(
|
||||
mod: ModuleContext,
|
||||
definitionName: string,
|
||||
name: string,
|
||||
variant: string | undefined,
|
||||
arg: SimpleType,
|
||||
|
@ -38,13 +39,25 @@ export function genConstructor(
|
|||
braces(...formals.map(f => seq(M.jsId(f[0]), ': ', renderType(mod, f[1])))))]
|
||||
: formals.map(f => seq(M.jsId(f[0]), ': ', renderType(mod, f[1])));
|
||||
|
||||
return [seq(`export function ${M.jsId(name)}`, mod.genericParametersFor(resultType),
|
||||
parens(...declArgs),
|
||||
': ', resultTypeItem, ' ', block(
|
||||
seq(`return `,
|
||||
((arg.kind === 'unit' && initializers.length === 0)
|
||||
? 'null'
|
||||
: (simpleValue
|
||||
? 'value'
|
||||
: braces(...initializers))))))];
|
||||
return [
|
||||
seq(`export function ${M.jsId(name)}`, mod.genericParametersFor(resultType),
|
||||
parens(...declArgs),
|
||||
': ', resultTypeItem, ' ', block(
|
||||
seq(`return `,
|
||||
((arg.kind === 'unit' && initializers.length === 0)
|
||||
? 'null'
|
||||
: (simpleValue
|
||||
? 'value'
|
||||
: braces(...initializers)))))),
|
||||
seq(`${M.jsId(name)}.schema = function () `, block(
|
||||
seq(`return `, braces(
|
||||
`schema: _schema()`,
|
||||
`imports: _imports`,
|
||||
`definitionName: _.Symbol.for(${JSON.stringify(definitionName)})`,
|
||||
... (variant === void 0) ? [] : [`variant: _.Symbol.for(${JSON.stringify(variant)})`],
|
||||
`unparse: from${definitionName}`,
|
||||
`parse: to${definitionName}`,
|
||||
`cast: as${definitionName}`,
|
||||
)))),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -65,12 +65,13 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string): Item {
|
|||
unconverterFor(ctx, M.Pattern.SimplePattern(p.value), 'v')),
|
||||
`)`)));
|
||||
case 'Ref':
|
||||
return ctx.mod.lookup(p.value,
|
||||
(_p, _t) => `from${p.value.name.description!}${ctx.mod.genericArgs()}(${src})`,
|
||||
(modId, modPath, _p, _t) => {
|
||||
ctx.mod.imports.add([modId, modPath]);
|
||||
return `${modId}.from${p.value.name.description!}${ctx.mod.genericArgs()}(${src})`;
|
||||
});
|
||||
return ctx.mod.lookup(
|
||||
p.value,
|
||||
(_p, _t) => `from${p.value.name.description!}${ctx.mod.genericArgs()}(${src})`,
|
||||
(modPath, modId, modFile, _p, _t) => {
|
||||
ctx.mod.imports.add([modPath, modId, modFile]);
|
||||
return `${modId}.from${p.value.name.description!}${ctx.mod.genericArgs()}(${src})`;
|
||||
});
|
||||
}
|
||||
})(p.value);
|
||||
case 'CompoundPattern':
|
||||
|
|
|
@ -12,7 +12,7 @@ export function sourceCodeFor(v: Value<M.InputEmbedded>): Item {
|
|||
bytes(b: Bytes): Item {
|
||||
return seq(`Uint8Array.from(`, brackets(... Array.from(b).map(b => b.toString())), `)`);
|
||||
},
|
||||
symbol(s: symbol): Item { return `Symbol.for(${JSON.stringify(s.description!)})`; },
|
||||
symbol(s: symbol): Item { return `_.Symbol.for(${JSON.stringify(s.description!)})`; },
|
||||
|
||||
record(r: Record<Value<M.InputEmbedded>, Tuple<Value<M.InputEmbedded>>, M.InputEmbedded>, k: Fold<M.InputEmbedded, Item>): Item {
|
||||
return seq(`_.Record<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>`, parens(k(r.label), brackets(... r.map(k))));
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -54,6 +54,10 @@ export function modsymFor(e: SchemaEnvEntry): string {
|
|||
return '_i_' + e.schemaModulePath.map(s => s.description!).join('$');
|
||||
}
|
||||
|
||||
export function formatModulePath(p: M.ModulePath): string {
|
||||
return p.map(s => s.description!).join('.');
|
||||
}
|
||||
|
||||
export function formatRef(r: M.Ref): string {
|
||||
return [... r.module, r.name].map(s => s.description!).join('.');
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue