Support access to "external" schema module via some dotted path within a TypeScript module

This commit is contained in:
Tony Garnock-Jones 2021-12-12 22:58:00 +01:00
parent ef6db841dd
commit 80dd3cd6b4
6 changed files with 44 additions and 22 deletions

View File

@ -91,13 +91,17 @@ export function runOnce(options: CommandLineArguments): CompilationResult {
const extensionEnv: M.Environment = options.module.map(arg => { const extensionEnv: M.Environment = options.module.map(arg => {
const i = arg.indexOf('='); const i = arg.indexOf('=');
if (i === -1) throw new Error(`--module argument must be Namespace=path: ${arg}`); if (i === -1) throw new Error(`--module argument must be Namespace=path or Namespace=path:expr; got ${arg}`);
const ns = arg.slice(0, i); const ns = arg.slice(0, i);
const path = arg.slice(i + 1); const pathAndExpr = arg.slice(i + 1);
const j = pathAndExpr.lastIndexOf(':');
const path = (j === -1) ? pathAndExpr : pathAndExpr.slice(0, j);
const expr = (j === -1) ? null : pathAndExpr.slice(j + 1);
return { return {
schema: null, schema: null,
schemaModulePath: ns.split('.').map(Symbol.for), schemaModulePath: ns.split('.').map(Symbol.for),
typescriptModulePath: path, typescriptModulePath: path,
typescriptModuleExpr: expr,
}; };
}); });
@ -120,6 +124,7 @@ export function runOnce(options: CommandLineArguments): CompilationResult {
schema: cc.schema, schema: cc.schema,
schemaModulePath: cc.schemaPath, schemaModulePath: cc.schemaPath,
typescriptModulePath: modulePathTo(c.outputFilePath, cc.outputFilePath), typescriptModulePath: modulePathTo(c.outputFilePath, cc.outputFilePath),
typescriptModuleExpr: null,
})), })),
]; ];
fs.mkdirSync(path.dirname(c.outputFilePath), { recursive: true }); fs.mkdirSync(path.dirname(c.outputFilePath), { recursive: true });

View File

@ -77,12 +77,12 @@ export function compile(
mod.definePreamble( mod.definePreamble(
seq(`export const _imports = `, braces(... Array.from(mod.imports.values()).map( seq(`export const _imports = `, braces(... Array.from(mod.imports.values()).map(
([modulePath, identifier, _path]) => ([modulePath, identifier, _path, expr]) =>
seq(stringify(M.formatModulePath(modulePath)), ': ', identifier))))); seq(stringify(M.formatModulePath(modulePath)), ': ', identifier, expr)))));
const f = new Formatter(); const f = new Formatter();
f.write(`import * as _ from ${JSON.stringify(options.preservesModule ?? '@preserves/core')};\n`); f.write(`import * as _ from ${JSON.stringify(options.preservesModule ?? '@preserves/core')};\n`);
mod.imports.forEach(([_modulePath, identifier, path]) => { mod.imports.forEach(([_modulePath, identifier, path, _expr]) => {
f.write(`import * as ${identifier} from ${JSON.stringify(path)};\n`); f.write(`import * as ${identifier} from ${JSON.stringify(path)};\n`);
}); });
f.newline(); f.newline();

View File

@ -31,7 +31,7 @@ export class ModuleContext {
readonly preamble: Item[] = []; readonly preamble: Item[] = [];
readonly typedefs: Item[] = []; readonly typedefs: Item[] = [];
readonly functiondefs: Item[] = []; readonly functiondefs: Item[] = [];
readonly imports = new KeyedSet<[M.ModulePath, string, string]>(); readonly imports = new KeyedSet<[M.ModulePath, string, string, string]>();
constructor( constructor(
env: M.Environment, env: M.Environment,
@ -72,10 +72,10 @@ export class ModuleContext {
p.value._variant === 'SimplePattern' && p.value._variant === 'SimplePattern' &&
p.value.value._variant === 'Ref') p.value.value._variant === 'Ref')
{ {
return this.lookup( return this.lookup(p.value.value.value,
p.value.value.value, (p, _t) => this.derefPattern(p, refCount + 1),
(p, _t) => this.derefPattern(p, refCount + 1), ((_modPath, _modId, _modFile, _modExpr, pp, _tt) =>
(_modPath, _modId, _modFile, pp, _tt) => this.derefPattern(pp ?? p, refCount + 1)); this.derefPattern(pp ?? p, refCount + 1)));
} else { } else {
return p; return p;
} }
@ -97,15 +97,19 @@ export class ModuleContext {
return (ref) => this.lookup( return (ref) => this.lookup(
ref, ref,
(_p, _t) => Type.ref(ref.name.description!, ref), (_p, _t) => Type.ref(ref.name.description!, ref),
(modPath, modId, modFile, _p, _t) => { (modPath, modId, modFile, modExpr, _p, _t) => {
this.imports.add([modPath, modId, modFile]); this.imports.add([modPath, modId, modFile, modExpr]);
return Type.ref(`${modId}.${ref.name.description!}`, ref); return Type.ref(`${modId}${modExpr}.${ref.name.description!}`, ref);
}, },
modulePath); modulePath);
} }
lookupType(name: M.Ref, modulePath?: M.ModulePath): Type | null { lookupType(name: M.Ref, modulePath?: M.ModulePath): Type | null {
const t = this.lookup(name, (_p, t) => t, (_modPath, _modId, _modFile, _p, t) => t, modulePath); const t = this.lookup(
name,
(_p, t) => t,
(_modPath, _modId, _modFile, _modExpr, _p, t) => t,
modulePath);
return t ? t() : null; return t ? t() : null;
} }
@ -114,6 +118,7 @@ export class ModuleContext {
kOther: (modPath: M.ModulePath, kOther: (modPath: M.ModulePath,
modId: string, modId: string,
modFile: string, modFile: string,
modExpr: string,
p: M.Definition | null, p: M.Definition | null,
t: (() => Type) | null) => R, t: (() => Type) | null) => R,
modulePath?: M.ModulePath) modulePath?: M.ModulePath)
@ -122,15 +127,26 @@ export class ModuleContext {
for (const e of this.env) { for (const e of this.env) {
if (is(e.schemaModulePath, soughtModule)) { if (is(e.schemaModulePath, soughtModule)) {
const expr = (e.typescriptModuleExpr === null) ? '' : '.' + e.typescriptModuleExpr;
if (e.schema === null) { if (e.schema === null) {
// It's an artificial module, not from a schema. Assume the identifier is present. // It's an artificial module, not from a schema. Assume the identifier is present.
return kOther(soughtModule, M.modsymFor(e), e.typescriptModulePath, null, null); return kOther(soughtModule,
M.modsymFor(e),
e.typescriptModulePath,
expr,
null,
null);
} else { } else {
const p = e.schema.definitions.get(name.name); const p = e.schema.definitions.get(name.name);
if (p !== void 0) { if (p !== void 0) {
let t = () => typeForDefinition(this.resolver(soughtModule), p); let t = () => typeForDefinition(this.resolver(soughtModule), p);
if (name.module.length) { if (name.module.length) {
return kOther(soughtModule, M.modsymFor(e), e.typescriptModulePath, p, t); return kOther(soughtModule,
M.modsymFor(e),
e.typescriptModulePath,
expr,
p,
t);
} else { } else {
return kLocal(p, t); return kLocal(p, t);
} }

View File

@ -186,9 +186,9 @@ export function converterForSimple(
return ctx.mod.lookup( return ctx.mod.lookup(
p.value, p.value,
(_p, _t) => [`${dest} = to${p.value.name.description!}(${src})`], (_p, _t) => [`${dest} = to${p.value.name.description!}(${src})`],
(modPath, modId, modFile, _p, _t) => { (modPath, modId, modFile, modExpr, _p, _t) => {
ctx.mod.imports.add([modPath, modId, modFile]); ctx.mod.imports.add([modPath, modId, modFile, modExpr]);
return [`${dest} = ${modId}.to${p.value.name.description!}(${src})`]; return [`${dest} = ${modId}${modExpr}.to${p.value.name.description!}(${src})`];
}); });
default: default:
((_p: never) => {})(p); ((_p: never) => {})(p);

View File

@ -68,9 +68,9 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string): Item {
return ctx.mod.lookup( return ctx.mod.lookup(
p.value, p.value,
(_p, _t) => `from${p.value.name.description!}${ctx.mod.genericArgs()}(${src})`, (_p, _t) => `from${p.value.name.description!}${ctx.mod.genericArgs()}(${src})`,
(modPath, modId, modFile, _p, _t) => { (modPath, modId, modFile, modExpr, _p, _t) => {
ctx.mod.imports.add([modPath, modId, modFile]); ctx.mod.imports.add([modPath, modId, modFile, modExpr]);
return `${modId}.from${p.value.name.description!}${ctx.mod.genericArgs()}(${src})`; return `${modId}${modExpr}.from${p.value.name.description!}${ctx.mod.genericArgs()}(${src})`;
}); });
} }
})(p.value); })(p.value);

View File

@ -45,6 +45,7 @@ export const ORSYM = Symbol.for('/');
export type SchemaEnvEntry = { export type SchemaEnvEntry = {
schemaModulePath: M.ModulePath, schemaModulePath: M.ModulePath,
typescriptModulePath: string, typescriptModulePath: string,
typescriptModuleExpr: string | null,
schema: M.Schema | null, // null means it's an artificial one, not corresponding to an input schema: M.Schema | null, // null means it's an artificial one, not corresponding to an input
}; };