Better treatment of generics in TypeScript schema compiler, after the Rust implementation

This commit is contained in:
Tony Garnock-Jones 2021-10-11 12:56:53 +02:00
parent b2c3032e7a
commit 3a605e75d6
15 changed files with 569 additions and 390 deletions

View File

@ -4,6 +4,7 @@ import { glob } from 'glob';
import { formatPosition, Position } from '@preserves/core';
import { readSchema } from '../reader';
import chalk from 'chalk';
import * as M from '../meta';
export interface Diagnostic {
type: 'warn' | 'error';
@ -11,6 +12,18 @@ export interface Diagnostic {
detail: Error | { message: string, pos: Position | null };
};
export type Expanded = {
base: string,
inputFiles: Array<{
inputFilePath: string,
text: string,
baseRelPath: string,
modulePath: M.ModulePath,
schema: M.Schema,
}>,
failures: Array<Diagnostic>,
};
export function computeBase(paths: string[]): string {
if (paths.length === 0) {
return '';
@ -31,7 +44,7 @@ export function computeBase(paths: string[]): string {
}
}
export function expandInputGlob(input: string[], base0: string | undefined) {
export function expandInputGlob(input: string[], base0: string | undefined): Expanded {
const matches = input.flatMap(i => glob.sync(i));
const base = base0 ?? computeBase(matches);
const failures: Array<Diagnostic> = [];

View File

@ -74,8 +74,7 @@ export function run(options: CommandLineArguments): void {
}
}
export function modulePathTo(file1: string, file2: string): string | null {
if (file1 === file2) return null;
export function modulePathTo(file1: string, file2: string): string {
let naive = path.relative(path.dirname(file1), file2);
if (naive[0] !== '.' && naive[0] !== '/') naive = './' + naive;
return changeExt(naive, '');
@ -107,7 +106,6 @@ export function runOnce(options: CommandLineArguments): CompilationResult {
inputFiles.forEach(c => {
const env: M.Environment = [
... extensionEnv.flatMap(e => {
if (e.typescriptModulePath === null) return [];
const p = modulePathTo(c.outputFilePath, e.typescriptModulePath);
if (p === null) return [];
return [{... e, typescriptModulePath: p}];
@ -121,7 +119,7 @@ export function runOnce(options: CommandLineArguments): CompilationResult {
fs.mkdirSync(path.dirname(c.outputFilePath), { recursive: true });
let compiledModule;
try {
compiledModule = compile(env, c.schema, {
compiledModule = compile(env, c.schemaPath, c.schema, {
preservesModule: options.core,
warn: (message: string, pos: Position | null) =>
failures.push({ type: 'warn', file: c.inputFilePath, detail: { message, pos } }),

View File

@ -2,7 +2,7 @@ import { Command } from 'commander';
import { canonicalEncode, KeyedDictionary, underlying } from '@preserves/core';
import fs from 'fs';
import path from 'path';
import { fromSchema, fromBundle } from '../meta';
import * as M from '../meta';
import { expandInputGlob, formatFailures } from './cli-utils';
export type CommandLineArguments = {
@ -25,11 +25,12 @@ export function run(options: CommandLineArguments): void {
if (failures.length === 0) {
if (options.bundle) {
fs.writeSync(1, underlying(canonicalEncode(fromBundle({
modules: new KeyedDictionary(inputFiles.map(i => [i.modulePath, i.schema])),
fs.writeSync(1, underlying(canonicalEncode(M.fromBundle({
modules: new KeyedDictionary<M.ModulePath, M.Schema, M.InputEmbedded>(
inputFiles.map(i => [i.modulePath, i.schema])),
}))));
} else {
fs.writeSync(1, underlying(canonicalEncode(fromSchema(inputFiles[0].schema))));
fs.writeSync(1, underlying(canonicalEncode(M.fromSchema(inputFiles[0].schema))));
}
} else {
process.exit(1);

View File

@ -4,47 +4,51 @@ import { CompilerOptions, ModuleContext } from "./compiler/context";
import { Formatter, block, seq } from "./compiler/block";
import { typeForDefinition } from "./compiler/gentype";
import { converterForDefinition } from "./compiler/genconverter";
import { Type } from "./compiler/type";
import { renderType } from "./compiler/rendertype";
import { genConstructor } from "./compiler/genctor";
import { unconverterForDefinition } from "./compiler/genunconverter";
import { sourceCodeFor } from "./compiler/value";
export function compile(env: M.Environment, schema: M.Schema, options: CompilerOptions = {}): string {
const mod = new ModuleContext(env, schema, options);
export function compile(
env: M.Environment,
modulePath: M.ModulePath,
schema: M.Schema,
options: CompilerOptions = {},
): string {
const mod = new ModuleContext(env, modulePath, schema, options);
const embeddedName = schema.embeddedType;
mod.defineType(seq(`export type _embedded = `,
renderType(embeddedName._variant === 'false'
? Type.ref('any')
: typeForDefinition(mod.resolver(), M.Definition.Pattern(M.Pattern.SimplePattern(M.SimplePattern.Ref(embeddedName.value))))),
`;`));
mod.defineType(`export type _val = _.Value<_embedded>;`);
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 = nameStr + mod.genericArgsFor(t);
mod.defineType(seq(`export type ${nameStr} = `, renderType(t), `;`));
mod.defineType(seq(`export type ${nameStr}`, mod.genericParametersFor(t),
` = `, renderType(mod, t), `;`));
if (t.kind === 'union') {
mod.defineFunction(_ctx =>
seq(`export namespace ${nameStr} `, block(
... Array.from(t.variants).map(([vn, vt]) =>
genConstructor(vn, vn, vt, nameStr))
genConstructor(mod, vn, vn, vt, t, resultTypeItem))
)));
} else {
mod.defineFunction(_ctx =>
genConstructor(nameStr, void 0, t, nameStr));
genConstructor(mod, nameStr, void 0, t, t, resultTypeItem));
}
}
for (const [name0, def] of schema.definitions) {
const t = typeForDefinition(mod.resolver(), def);
const name = name0 as symbol;
mod.defineFunction(ctx =>
seq(`export function as${name.description!}`,
'(v: _val): ', name.description!, ' ',
seq(`export function as${name.description!}`, mod.genericParameters(),
`(v: _.Value<_embedded>): `, name.description!, mod.genericArgsFor(t), ` `,
ctx.block(() => [
seq(`let result = to${name.description!}(v)`),
seq(`if (result === void 0) `,
@ -52,16 +56,16 @@ export function compile(env: M.Environment, schema: M.Schema, options: CompilerO
seq(`return result`)])));
mod.defineFunction(ctx =>
seq(`export function to${name.description!}`,
'(v: _val): undefined | ', name.description!, ' ',
ctx.block(() => [seq(`let result: undefined | `, name.description!),
... converterForDefinition(ctx, def, 'v', 'result'),
seq(`export function to${name.description!}`, mod.genericParameters(),
`(v: _.Value<_embedded>): undefined | `, name.description!, mod.genericArgsFor(t), ` `,
ctx.block(() => [seq(`let result: undefined | `, name.description!, mod.genericArgsFor(t)),
... converterForDefinition(ctx, def, `v`, `result`),
seq(`return result`)])));
mod.defineFunction(ctx =>
seq(`export function from${name.description!}`,
'(_v: ', name.description!, '): _val ',
ctx.block(() => unconverterForDefinition(ctx, def, '_v'))));
seq(`export function from${name.description!}`, mod.genericParameters(),
`(_v: `, name.description!, mod.genericArgsFor(t), `): _.Value<_embedded> `,
ctx.block(() => unconverterForDefinition(ctx, def, `_v`))));
}
const f = new Formatter();

View File

@ -1,9 +1,11 @@
import { Dictionary, KeyedSet, Position } from "@preserves/core";
import { Dictionary, KeyedSet, FlexSet, Position, stringify, is } from "@preserves/core";
import { refPosition } from "../reader";
import * as M from "../meta";
import { block, braces, commas, formatItems, Item, keyvalue, seq } from "./block";
import { ANY_TYPE, FieldType, Type } from "./type";
import { anglebrackets, block, braces, commas, formatItems, Item, keyvalue, seq } from "./block";
import { ANY_TYPE, RefType, Type } from "./type";
import { renderType, variantInitFor } from "./rendertype";
import { typeForDefinition } from "./gentype";
import { SchemaSyntaxError } from "../error";
export interface CompilerOptions {
preservesModule?: string;
@ -20,18 +22,36 @@ export const RECURSION_LIMIT = 128;
export class ModuleContext {
readonly env: M.Environment;
readonly modulePath: M.ModulePath;
readonly schema: M.Schema;
readonly options: CompilerOptions;
readonly embeddedType: Item;
readonly literals = new Dictionary<M._embedded, string>();
readonly literals = new Dictionary<M.InputEmbedded, string>();
readonly typedefs: Item[] = [];
readonly functiondefs: Item[] = [];
readonly imports = new KeyedSet<[string, string]>();
constructor(env: M.Environment, schema: M.Schema, options: CompilerOptions) {
constructor(
env: M.Environment,
modulePath: M.ModulePath,
schema: M.Schema,
options: CompilerOptions,
) {
this.env = env;
this.modulePath = modulePath;
this.schema = schema;
this.options = options;
switch (schema.embeddedType._variant) {
case 'false':
this.embeddedType = '_.GenericEmbedded';
break;
case 'Ref': {
const t = this.resolver()(schema.embeddedType.value);
this.embeddedType = t.typeName;
break;
}
}
}
literal(v: M.Input): Item {
@ -51,11 +71,9 @@ export class ModuleContext {
p.value._variant === 'SimplePattern' &&
p.value.value._variant === 'Ref')
{
return M.lookup(refPosition(p.value.value.value),
p.value.value.value,
this.env,
(p) => this.derefPattern(p, refCount + 1),
(_modId, _modPath, pp) => this.derefPattern(pp ?? p, refCount + 1));
return this.lookup(p.value.value.value,
(p, _t) => this.derefPattern(p, refCount + 1),
(_modId, _modPath, pp, _tt) => this.derefPattern(pp ?? p, refCount + 1));
} else {
return p;
}
@ -69,13 +87,101 @@ export class ModuleContext {
this.functiondefs.push(f(new FunctionContext(this)));
}
resolver(): (ref: M.Ref) => FieldType {
return (ref) => M.lookup(refPosition(ref), ref, this.env,
(_p) => Type.ref(ref.name.description!),
(modId, modPath,_p) => {
this.imports.add([modId, modPath]);
return Type.ref(`${modId}.${ref.name.description!}`);
});
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);
}
lookupType(name: M.Ref, modulePath?: M.ModulePath): Type | null {
const t = this.lookup(name, (_p, t) => t, (_modId, _modPath, _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
{
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);
} 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);
} else {
return kLocal(p, t);
}
}
}
}
}
throw new SchemaSyntaxError(`Undefined reference: ${M.formatRef(name)}`, refPosition(name));
}
genericParameters(): Item {
return anglebrackets(seq('_embedded = ', this.embeddedType));
}
genericParametersFor(t: Type): Item {
return this.hasEmbedded(t) ? this.genericParameters() : '';
}
genericArgs(): Item {
return `<_embedded>`;
}
genericArgsFor(t: Type): Item {
return this.hasEmbedded(t) ? this.genericArgs() : '';
}
hasEmbedded(t: Type): boolean {
const self = this;
const state = new WalkState(this.modulePath);
function walk(t: Type): boolean {
switch (t.kind) {
case 'union':
for (const v of t.variants.values()) { if (walk(v)) return true; };
return false;
case 'unit': return false;
case 'array': return walk(t.type);
case 'set': return true; // because ref to _embedded in renderType()
case 'dictionary': return true; // because ref to _embedded in renderType()
case 'ref': {
if (t.ref === null) {
switch (t.typeName) {
case '_embedded': return true;
case '_.Value': return true;
default: return false;
}
} else {
return state.cycleCheck(
t.ref,
ref => self.lookupType(ref, state.modulePath),
t => t ? walk(t) : false,
() => false);
}
}
case 'record':
for (const v of t.fields.values()) { if (walk(v)) return true; };
return false;
}
}
return walk(t);
}
}
@ -97,7 +203,7 @@ export class FunctionContext {
}
gentemp(vartype: Type = ANY_TYPE): string {
const typeitem = renderType(vartype);
const typeitem = renderType(this.mod, vartype);
const typestr = formatItems([typeitem], Infinity);
const varname = this.gentempname();
let e = this.temps.get(typestr);
@ -146,3 +252,40 @@ export class FunctionContext {
return seq(`${dest} = `, fields.length === 0 ? `null` : braces(... fields));
}
}
export class WalkState {
modulePath: M.ModulePath;
readonly seen: FlexSet<M.Ref>;
constructor(modulePath: M.ModulePath) {
this.modulePath = modulePath;
this.seen = new FlexSet(refCanonicalizer);
}
cycleCheck<E, R>(
r0: M.Ref,
step: (ref: M.Ref) => E,
ks: (e: E) => R,
kf: () => R,
): R {
const r = M.Ref({
module: r0.module.length ? r0.module : this.modulePath,
name: r0.name
});
if (this.seen.has(r)) {
return kf();
} else {
this.seen.add(r);
const maybe_e = step(r);
const saved = this.modulePath;
this.modulePath = r.module;
const result = ks(maybe_e);
this.modulePath = saved;
return result;
}
}
}
function refCanonicalizer(r: M.Ref): string {
return stringify([... r.module, r.name]);
}

View File

@ -2,7 +2,6 @@ import { FunctionContext } from "./context";
import * as M from '../meta';
import { Item, seq } from "./block";
import { simpleType, typeFor } from "./gentype";
import { refPosition } from "../reader";
import { ANY_TYPE, Type } from "./type";
export function converterForDefinition(
@ -184,12 +183,12 @@ export function converterForSimple(
seq(`break`)]))];
}))];
case 'Ref':
return M.lookup(refPosition(p.value), p.value, ctx.mod.env,
(_p) => [`${dest} = to${p.value.name.description!}(${src})`],
(modId, modPath,_p) => {
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})`],
(modId, modPath, _p, _t) => {
ctx.mod.imports.add([modId, modPath]);
return [`${dest} = ${modId}.to${p.value.name.description!}(${src})`];
});
default:
((_p: never) => {})(p);
throw new Error("Unreachable");
@ -205,7 +204,7 @@ function converterForCompound(
{
switch (p._variant) {
case 'rec':
return [seq(`if (_.Record.isRecord<_val, _.Tuple<_val>, _embedded>(${src})) `, ctx.block(() =>
return [seq(`if (_.Record.isRecord<_.Value<_embedded>, _.Tuple<_.Value<_embedded>>, _embedded>(${src})) `, ctx.block(() =>
converterFor(ctx, p.label, `${src}.label`, () =>
converterFor(ctx, p.fields, src, ks, true))))];
case 'tuple':

View File

@ -1,14 +1,17 @@
import * as M from '../meta';
import { block, braces, Item, keyvalue, parens, seq } from "./block";
import { FieldType, SimpleType } from "./type";
import { FieldType, SimpleType, Type } from "./type";
import { renderType } from "./rendertype";
import { ModuleContext } from './context';
export function genConstructor(
mod: ModuleContext,
name: string,
variant: string | undefined,
arg: SimpleType,
resultType: Item): Item
{
resultType: Type,
resultTypeItem: Item,
): Item {
const formals: Array<[string, FieldType]> = [];
let simpleValue = false;
@ -32,12 +35,12 @@ export function genConstructor(
const declArgs: Array<Item> = (formals.length > 1)
? [seq(braces(...formals.map(f => M.jsId(f[0]))), ': ',
braces(...formals.map(f => seq(M.jsId(f[0]), ': ', renderType(f[1])))))]
: formals.map(f => seq(M.jsId(f[0]), ': ', renderType(f[1])));
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)}`,
return seq(`export function ${M.jsId(name)}`, mod.genericParametersFor(resultType),
parens(... declArgs),
': ', resultType, ' ', block(
': ', resultTypeItem, ' ', block(
seq(`return `,
((arg.kind === 'unit' && initializers.length === 0)
? 'null'

View File

@ -36,16 +36,16 @@ export function simpleType(resolver: RefResolver, p: M.SimplePattern): FieldType
return ANY_TYPE;
case 'atom':
switch (p.atomKind._variant) {
case 'Boolean': return Type.ref(`boolean`);
case 'Float': return Type.ref(`number`);
case 'Double': return Type.ref(`number`);
case 'SignedInteger': return Type.ref(`number`);
case 'String': return Type.ref(`string`);
case 'ByteString': return Type.ref(`_.Bytes`);
case 'Symbol': return Type.ref(`symbol`);
case 'Boolean': return Type.ref(`boolean`, null);
case 'Float': return Type.ref(`number`, null);
case 'Double': return Type.ref(`number`, null);
case 'SignedInteger': return Type.ref(`number`, null);
case 'String': return Type.ref(`string`, null);
case 'ByteString': return Type.ref(`_.Bytes`, null);
case 'Symbol': return Type.ref(`symbol`, null);
}
case 'embedded':
return Type.ref(`_embedded`);
return Type.ref(`_embedded`, null);
case 'lit':
return Type.unit();
case 'seqof':

View File

@ -1,4 +1,3 @@
import { refPosition } from '../reader';
import * as M from '../meta';
import { block, brackets, Item, parens, seq } from './block';
import { FunctionContext } from "./context";
@ -66,13 +65,12 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string): Item {
unconverterFor(ctx, M.Pattern.SimplePattern(p.value), 'v')),
`)`)));
case 'Ref':
return M.lookup(
refPosition(p.value), p.value, ctx.mod.env,
(_p) => `from${p.value.name.description!}(${src})`,
(modId, modPath, _p) => {
ctx.mod.imports.add([modId, modPath]);
return `${modId}.from${p.value.name.description!}(${src})`;
});
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})`;
});
}
})(p.value);
case 'CompoundPattern':

View File

@ -1,5 +1,6 @@
import { SimpleType, Type } from "./type";
import { anglebrackets, braces, Item, keyvalue, opseq, seq } from "./block";
import { ModuleContext } from "./context";
export function variantInitFor(variantName: string | undefined) : Item[] {
return variantName === void 0 ? [] : [variantFor(variantName)];
@ -9,7 +10,7 @@ export function variantFor(variantName: string): Item {
return keyvalue('_variant', JSON.stringify(variantName));
}
export function renderVariant([variantName, t]: [string, SimpleType]): Item {
export function renderVariant(ctxt: ModuleContext, [variantName, t]: [string, SimpleType]): Item {
let fields: Item[];
switch (t.kind) {
case 'unit':
@ -19,10 +20,10 @@ export function renderVariant([variantName, t]: [string, SimpleType]): Item {
case 'set':
case 'dictionary':
case 'array':
fields = [keyvalue('value', renderType(t))];
fields = [keyvalue('value', renderType(ctxt, t))];
break;
case 'record':
fields = Array.from(t.fields).map(([nn, tt]) => keyvalue(nn, renderType(tt)));
fields = Array.from(t.fields).map(([nn, tt]) => keyvalue(nn, renderType(ctxt, tt)));
break;
default:
((_: never) => {})(t);
@ -31,22 +32,27 @@ export function renderVariant([variantName, t]: [string, SimpleType]): Item {
return braces(variantFor(variantName), ... fields);
}
export function renderType(t: Type): Item {
export function renderType(ctxt: ModuleContext, t: Type): Item {
switch (t.kind) {
case 'union': return opseq('never', ' | ', ...
Array.from(t.variants).flatMap(renderVariant));
Array.from(t.variants).flatMap(entry => renderVariant(ctxt, entry)));
case 'unit': return 'null';
case 'ref': return t.typeName;
case 'ref':
if (t.ref === null && t.typeName === '_embedded') {
return t.typeName;
} else {
return seq(t.typeName, ctxt.genericArgsFor(t));
}
case 'set': return seq('_.KeyedSet', anglebrackets(
renderType(t.type),
renderType(ctxt, t.type),
'_embedded'));
case 'dictionary': return seq('_.KeyedDictionary', anglebrackets(
renderType(t.key),
renderType(t.value),
renderType(ctxt, t.key),
renderType(ctxt, t.value),
'_embedded'));
case 'array': return seq('Array', anglebrackets(renderType(t.type)));
case 'array': return seq('Array', anglebrackets(renderType(ctxt, t.type)));
case 'record': return braces(... Array.from(t.fields).map(([nn, tt]) =>
keyvalue(nn, renderType(tt))));
keyvalue(nn, renderType(ctxt, tt))));
default:
((_: never) => {})(t);
throw new Error("Unreachable");

View File

@ -1,3 +1,5 @@
import * as M from '../meta';
export type Type =
| { kind: 'union', variants: VariantMap } // zero: never
| SimpleType
@ -9,7 +11,10 @@ export type FieldType =
| { kind: 'array', type: FieldType }
| { kind: 'set', type: FieldType }
| { kind: 'dictionary', key: FieldType, value: FieldType }
| { kind: 'ref', typeName: string } // also for base types
| RefType
export type RefType =
| { kind: 'ref', typeName: string, ref: M.Ref | null } // ref === null for base types
export type RecordType =
| { kind: 'record', fields: FieldMap }
@ -20,7 +25,8 @@ export type FieldMap = Map<string, FieldType>;
export namespace Type {
export const union = (variants: VariantMap): Type => ({ kind: 'union', variants });
export const unit = (): FieldType => ({ kind: 'unit' });
export const ref = (typeName: string): FieldType => ({ kind: 'ref', typeName });
export const ref = (typeName: string, ref: M.Ref | null): RefType => (
{ kind: 'ref', typeName, ref });
export const array = (type: FieldType): FieldType => ({ kind: 'array', type });
export const set = (type: FieldType): FieldType => ({ kind: 'set', type });
export const dictionary = (key: FieldType, value: FieldType): FieldType => (
@ -28,4 +34,4 @@ export namespace Type {
export const record = (fields: FieldMap): RecordType => ({ kind: 'record', fields });
}
export const ANY_TYPE: FieldType = Type.ref('_val');
export const ANY_TYPE: FieldType = Type.ref('_.Value', null);

View File

@ -1,8 +1,8 @@
import { Annotated, Bytes, Set, Dictionary, Fold, fold, Record, Tuple, Value, stringify } from "@preserves/core";
import { Annotated, Bytes, Set, Dictionary, Fold, fold, Record, Tuple, Value, stringify, Embedded } from "@preserves/core";
import { brackets, Item, parens, seq } from "./block";
import * as M from '../meta';
export function sourceCodeFor(v: Value<M._embedded>): Item {
export function sourceCodeFor(v: Value<M.InputEmbedded>): Item {
return fold(v, {
boolean(b: boolean): Item { return b.toString(); },
single(f: number): Item { return f.toString(); },
@ -14,25 +14,25 @@ export function sourceCodeFor(v: Value<M._embedded>): Item {
},
symbol(s: symbol): Item { return `Symbol.for(${JSON.stringify(s.description!)})`; },
record(r: Record<Value<M._embedded>, Tuple<Value<M._embedded>>, M._embedded>, k: Fold<M._embedded, Item>): Item {
return seq(`_.Record<_val, _.Tuple<_val>, _embedded>`, parens(k(r.label), brackets(... r.map(k))));
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))));
},
array(a: Array<Value<M._embedded>>, k: Fold<M._embedded, Item>): Item {
array(a: Array<Value<M.InputEmbedded>>, k: Fold<M.InputEmbedded, Item>): Item {
return brackets(... a.map(k));
},
set(s: Set<M._embedded>, k: Fold<M._embedded, Item>): Item {
return seq('new _.Set<_val>', parens(brackets(... Array.from(s).map(k))));
set(s: Set<M.InputEmbedded>, k: Fold<M.InputEmbedded, Item>): Item {
return seq('new _.Set<_.Value<_embedded>>', parens(brackets(... Array.from(s).map(k))));
},
dictionary(d: Dictionary<M._embedded>, k: Fold<M._embedded, Item>): Item {
dictionary(d: Dictionary<M.InputEmbedded>, k: Fold<M.InputEmbedded, Item>): Item {
return seq('new _.Dictionary<_embedded>', parens(brackets(... Array.from(d).map(([kk,vv]) =>
brackets(k(kk), k(vv))))));
},
annotated(a: Annotated<M._embedded>, k: Fold<M._embedded, Item>): Item {
annotated(a: Annotated<M.InputEmbedded>, k: Fold<M.InputEmbedded, Item>): Item {
return seq('_.annotate<_embedded>', parens(k(a.item), ... a.annotations.map(k)));
},
embedded(t: M._embedded, _k: Fold<M._embedded, Item>): Item {
embedded(t: Embedded<M.InputEmbedded>, _k: Fold<M.InputEmbedded, Item>): Item {
throw new Error(`Cannot emit source code for construction of embedded ${stringify(t)}`);
},
});

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
import { is, Position } from '@preserves/core';
import { GenericEmbedded, Value } from '@preserves/core';
import * as M from './gen/schema';
import { SchemaSyntaxError } from './error';
import { isJsKeyword } from './compiler/jskw';
export * from './gen/schema';
export type Input = M._val;
export type Input = Value;
export type InputEmbedded = GenericEmbedded;
export function qidLast(s: string): string {
const m = s.match(/^(.*\.)?([^.]+)$/);
@ -42,51 +42,18 @@ export const EQUALS = Symbol.for('=');
export const INCLUDE = Symbol.for('include');
export const ORSYM = Symbol.for('/');
export type SchemaEnvEntry = { schemaModulePath: M.ModulePath } & (
({
typescriptModulePath: string | null, // null means it's "this module" in disguise
schema: M.Schema,
}) | ({
typescriptModulePath: string,
schema: null,
})
);
export type SchemaEnvEntry = {
schemaModulePath: M.ModulePath,
typescriptModulePath: string,
schema: M.Schema | null, // null means it's an artificial one, not corresponding to an input
};
export type Environment = Array<SchemaEnvEntry>;
function modsymFor(e: SchemaEnvEntry): string {
export function modsymFor(e: SchemaEnvEntry): string {
return '_i_' + e.schemaModulePath.map(s => s.description!).join('$');
}
export function lookup<R>(namePos: Position | null,
name: M.Ref,
env: Environment,
kLocal: (p: M.Definition) => R,
kOther: (modId: string, modPath: string, p: M.Definition | null) => R): R
{
for (const e of env) {
if (is(e.schemaModulePath, name.module) ||
(e.typescriptModulePath === null && name.module.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 = e.schema.definitions.get(name.name);
if (p !== void 0) {
if (e.typescriptModulePath === null) {
return kLocal(p);
} else {
return kOther(modsymFor(e), e.typescriptModulePath, p);
}
}
}
}
}
throw new SchemaSyntaxError(`Undefined reference: ${formatRef(name)}`, namePos);
}
export function formatRef(r: M.Ref): string {
return [... r.module, r.name].map(s => s.description!).join('.');
}

View File

@ -70,7 +70,7 @@ export function parseSchema(toplevelTokens: Array<Input>, options: SchemaReaderO
{
let version: M.Version | undefined = void 0;
let embeddedType: M.EmbeddedTypeName = M.EmbeddedTypeName.$false();
let definitions = new KeyedDictionary<symbol, Definition, M._embedded>();
let definitions = new KeyedDictionary<symbol, Definition, M.InputEmbedded>();
function process(toplevelTokens: Array<Input>): void {
const toplevelClauses = splitBy(peel(toplevelTokens) as Array<Input>, M.DOT);
@ -236,9 +236,9 @@ function parsePattern(name: symbol, body0: Array<Input>): Pattern {
}
}
}
} else if (Record.isRecord<Input, Tuple<Input>, M._embedded>(item)) {
} else if (Record.isRecord<Input, Tuple<Input>, M.InputEmbedded>(item)) {
const label = item.label;
if (Record.isRecord<Input, [], M._embedded>(label)) {
if (Record.isRecord<Input, [], M.InputEmbedded>(label)) {
if (label.length !== 0) complain();
switch (label.label) {
case M.$lit:
@ -252,11 +252,11 @@ function parsePattern(name: symbol, body0: Array<Input>): Pattern {
}
} else if (Array.isArray(item) && item.length === 2 && is(item[1], M.DOTDOTDOT)) {
return ks(M.SimplePattern.seqof(walkSimple(item[0])));
} else if (Set.isSet<M._embedded>(item)) {
} else if (Set.isSet<M.Input>(item)) {
if (item.size !== 1) complain();
const [vp] = item.entries();
return ks(M.SimplePattern.setof(walkSimple(vp)));
} else if (Dictionary.isDictionary<M._embedded, Input>(item)
} else if (Dictionary.isDictionary<M.InputEmbedded, Input>(item)
&& item.size === 2
&& item.has(M.DOTDOTDOT))
{
@ -278,9 +278,9 @@ function parsePattern(name: symbol, body0: Array<Input>): Pattern {
const item = peel(item0);
function complain(): never { invalidPattern(stringify(name), item, pos); }
if (Record.isRecord<Input, Tuple<Input>, M._embedded>(item)) {
if (Record.isRecord<Input, Tuple<Input>, M.InputEmbedded>(item)) {
const label = item.label;
if (Record.isRecord<Input, [], M._embedded>(label)) {
if (Record.isRecord<Input, [], M.InputEmbedded>(label)) {
if (label.length !== 0) complain();
switch (label.label) {
case M.$rec:
@ -308,9 +308,9 @@ function parsePattern(name: symbol, body0: Array<Input>): Pattern {
});
} else if (Array.isArray(item)) {
return M.CompoundPattern.tuple(item.map(maybeNamed));
} else if (Dictionary.isDictionary<M._embedded, Input>(item) && !item.has(M.DOTDOTDOT)) {
} else if (Dictionary.isDictionary<M.InputEmbedded, Input>(item) && !item.has(M.DOTDOTDOT)) {
return M.CompoundPattern.dict(
M.DictionaryEntries(item.mapEntries<M.NamedSimplePattern, Input, M._embedded>(
M.DictionaryEntries(item.mapEntries<M.NamedSimplePattern, Input, M.InputEmbedded>(
([k, vp]) => [
strip(k),
_maybeNamed(