Pointers; improved diagnostics
This commit is contained in:
parent
c09032f609
commit
8d7e7c6d95
|
@ -5,9 +5,8 @@ import { glob } from 'glob';
|
|||
import minimatch from 'minimatch';
|
||||
import yargs from 'yargs/yargs';
|
||||
import * as M from '../meta';
|
||||
import { SchemaSyntaxError } from '../error';
|
||||
import chalk from 'chalk';
|
||||
import { formatPosition } from '@preserves/core';
|
||||
import { formatPosition, Position } from '@preserves/core';
|
||||
|
||||
export type CommandLineArguments = {
|
||||
input: string;
|
||||
|
@ -15,12 +14,20 @@ export type CommandLineArguments = {
|
|||
output: string | undefined;
|
||||
core: string;
|
||||
watch: boolean;
|
||||
traceback: boolean;
|
||||
module: string[];
|
||||
};
|
||||
|
||||
export interface Diagnostic {
|
||||
type: 'warn' | 'error';
|
||||
file: string;
|
||||
detail: Error | { message: string, pos: Position | null };
|
||||
};
|
||||
|
||||
export type CompilationResult = {
|
||||
options: CommandLineArguments,
|
||||
inputFiles: Array<InputFile>,
|
||||
failures: Array<[string, Error]>,
|
||||
failures: Array<Diagnostic>,
|
||||
base: string,
|
||||
output: string,
|
||||
};
|
||||
|
@ -51,22 +58,32 @@ export function computeBase(paths: string[]): string {
|
|||
}
|
||||
}
|
||||
|
||||
function failureCount(type: 'warn' | 'error', r: CompilationResult): number {
|
||||
return r.failures.filter(f => f.type === type).length;
|
||||
}
|
||||
|
||||
export function run(options: CommandLineArguments): void {
|
||||
if (!options.watch) {
|
||||
if (runOnce(options).failures.length !== 0) {
|
||||
if (failureCount('error', runOnce(options)) > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
function runWatch() {
|
||||
console.clear();
|
||||
console.log(chalk.yellow(new Date().toISOString()) +
|
||||
console.log(chalk.gray(new Date().toISOString()) +
|
||||
' Compiling Schemas in watch mode...\n');
|
||||
const r = runOnce(options);
|
||||
const hasErrors = r.failures.length > 0;
|
||||
const errorSummary = (hasErrors ? chalk.redBright : chalk.greenBright)(
|
||||
`${r.failures.length} error(s)`);
|
||||
console.log(chalk.yellow(new Date().toISOString()) +
|
||||
` Processed ${r.inputFiles.length} file(s) with ${errorSummary}. Waiting for changes.`);
|
||||
const warningCount = failureCount('warn', r);
|
||||
const errorCount = failureCount('error', r);
|
||||
const wMsg = (warningCount > 0) && chalk.yellowBright(`${warningCount} warning(s)`);
|
||||
const eMsg = (errorCount > 0) && chalk.redBright(`${errorCount} error(s)`);
|
||||
const errorSummary =
|
||||
(wMsg && eMsg) ? `with ${eMsg} and ${wMsg}` :
|
||||
(wMsg) ? `with ${wMsg}` :
|
||||
(eMsg) ? `with ${eMsg}` :
|
||||
chalk.greenBright('successfully');
|
||||
console.log(chalk.gray(new Date().toISOString()) +
|
||||
` Processed ${r.inputFiles.length} file(s) ${errorSummary}. Waiting for changes.`);
|
||||
const watcher = fs.watch(r.base, { recursive: true }, (_event, filename) => {
|
||||
filename = path.join(r.base, filename);
|
||||
if (minimatch(filename, options.input)) {
|
||||
|
@ -92,11 +109,23 @@ export function modulePathTo(file1: string, file2: string): string | null {
|
|||
|
||||
export function runOnce(options: CommandLineArguments): CompilationResult {
|
||||
const matches = glob.sync(options.input);
|
||||
const failures: Array<[string, Error]> = [];
|
||||
const failures: Array<Diagnostic> = [];
|
||||
|
||||
const base = options.base ?? computeBase(matches);
|
||||
const output = options.output ?? base;
|
||||
|
||||
const extensionEnv: M.Environment = options.module.map(arg => {
|
||||
const i = arg.indexOf('=');
|
||||
if (i === -1) throw new Error(`--module argument must be Namespace=path: ${arg}`);
|
||||
const ns = arg.slice(0, i);
|
||||
const path = arg.slice(i + 1);
|
||||
return {
|
||||
schema: null,
|
||||
schemaModulePath: ns.split('.').map(Symbol.for),
|
||||
typescriptModulePath: path,
|
||||
};
|
||||
});
|
||||
|
||||
const inputFiles: Array<InputFile> = matches.flatMap(inputFilePath => {
|
||||
if (!inputFilePath.startsWith(base)) {
|
||||
throw new Error(`Input filename ${inputFilePath} falls outside base ${base}`);
|
||||
|
@ -109,31 +138,47 @@ export function runOnce(options: CommandLineArguments): CompilationResult {
|
|||
const schemaPath = relPath.split('/').map(p => p.split('.')[0]).map(Symbol.for);
|
||||
return [{ inputFilePath, outputFilePath, schemaPath, schema }];
|
||||
} catch (e) {
|
||||
failures.push([inputFilePath, e]);
|
||||
failures.push({ type: 'error', file: inputFilePath, detail: e });
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
inputFiles.forEach(c => {
|
||||
const env: M.Environment = inputFiles.map(cc => ({
|
||||
schema: cc.schema,
|
||||
schemaModulePath: cc.schemaPath,
|
||||
typescriptModulePath: modulePathTo(c.outputFilePath, cc.outputFilePath),
|
||||
}));
|
||||
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}];
|
||||
}),
|
||||
... inputFiles.map(cc => ({
|
||||
schema: cc.schema,
|
||||
schemaModulePath: cc.schemaPath,
|
||||
typescriptModulePath: modulePathTo(c.outputFilePath, cc.outputFilePath),
|
||||
})),
|
||||
];
|
||||
fs.mkdirSync(path.dirname(c.outputFilePath), { recursive: true });
|
||||
try {
|
||||
fs.writeFileSync(c.outputFilePath, compile(env, c.schema, options.core), 'utf-8');
|
||||
fs.writeFileSync(c.outputFilePath, compile(env, c.schema, {
|
||||
preservesModule: options.core,
|
||||
warn: (message: string, pos: Position | null) =>
|
||||
failures.push({ type: 'warn', file: c.inputFilePath, detail: { message, pos } }),
|
||||
}), 'utf-8');
|
||||
} catch (e) {
|
||||
failures.push([c.inputFilePath, e]);
|
||||
failures.push({ type: 'error', file: c.inputFilePath, detail: e });
|
||||
}
|
||||
});
|
||||
|
||||
for (const [inputFile, err] of failures) {
|
||||
if (err instanceof SchemaSyntaxError) {
|
||||
console.log(chalk.blueBright(formatPosition(err.pos ?? inputFile)) + ': ' + err.message);
|
||||
} else {
|
||||
console.log(chalk.blueBright(inputFile) + ': ' + err.message);
|
||||
}
|
||||
for (const d of failures) {
|
||||
console.log(
|
||||
(d.type === 'error' ? chalk.redBright('[ERROR]') : chalk.yellowBright('[WARNING]'))
|
||||
+ ' '
|
||||
+ chalk.blueBright(formatPosition((d.detail as any).pos ?? d.file))
|
||||
+ ': '
|
||||
+ d.detail.message
|
||||
+ (options.traceback && (d.detail instanceof Error)
|
||||
? '\n' + d.detail.stack
|
||||
: ''));
|
||||
}
|
||||
if (failures.length > 0) {
|
||||
console.log();
|
||||
|
@ -169,6 +214,17 @@ export function main(argv: Array<string>) {
|
|||
type: 'boolean',
|
||||
descripion: 'Watch base directory for changes',
|
||||
default: false,
|
||||
})
|
||||
.option('traceback', {
|
||||
type: 'boolean',
|
||||
description: 'Include stack traces in compiler errors',
|
||||
default: false,
|
||||
})
|
||||
.option('module', {
|
||||
type: 'string',
|
||||
array: true,
|
||||
description: 'Additional Namespace=path imports',
|
||||
default: [],
|
||||
}),
|
||||
argv => argv)
|
||||
.argv;
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
import { Pattern, NamedPattern, Schema, Input, Environment, Ref, lookup } from "./meta";
|
||||
import * as M from './meta';
|
||||
import { Annotated, Bytes, Dictionary, Fold, fold, KeyedSet, preserves, Record, Set, Tuple, Value } from "@preserves/core";
|
||||
import { Annotated, Bytes, Dictionary, Fold, fold, KeyedSet, Position, preserves, Record, Set, Tuple, Value } from "@preserves/core";
|
||||
import { Formatter, parens, seq, Item, opseq, block, commas, brackets, anglebrackets, braces } from "./block";
|
||||
import { refPosition } from "./reader";
|
||||
|
||||
export interface CompilerOptions {
|
||||
preservesModule?: string;
|
||||
defaultPointer?: Ref;
|
||||
warn?(message: string, pos: Position | null): void;
|
||||
};
|
||||
|
||||
function fnblock(... items: Item[]): Item {
|
||||
return seq('((() => ', block(... items), ')())');
|
||||
}
|
||||
|
||||
export function compile(env: Environment, schema: Schema, preservesModule = '@preserves/core'): string {
|
||||
export function compile(env: Environment, schema: Schema, options: CompilerOptions = {}): string {
|
||||
const literals = new Dictionary<string, never>();
|
||||
const types: Array<Item> = [];
|
||||
const functions: Array<Item> = [];
|
||||
const imports = new KeyedSet<[string, string]>();
|
||||
let temps: Array<string> = [];
|
||||
const pointerName = Schema._.details(schema).get(M.$pointer);
|
||||
|
||||
function applyPredicate(name: Ref, v: string): Item {
|
||||
return lookup(refPosition(name), name, env,
|
||||
|
@ -68,11 +75,11 @@ export function compile(env: Environment, schema: Schema, preservesModule = '@pr
|
|||
case M.$or:
|
||||
return opseq('never', ' | ', ... p[0].map(pp => typeFor(pp)));
|
||||
case M.$and:
|
||||
return opseq('_.Value', ' & ', ... p[0].map(pp => typeFor(pp)));
|
||||
return opseq('_val', ' & ', ... p[0].map(pp => typeFor(pp)));
|
||||
case M.$pointer:
|
||||
return `any`; // TODO: what to do here?
|
||||
return `_ptr`;
|
||||
case M.$rec:
|
||||
return seq('_.Record', anglebrackets(typeFor(p[0]), typeFor(p[1])));
|
||||
return seq('_.Record', anglebrackets(typeFor(p[0]), typeFor(p[1]), '_ptr'));
|
||||
case M.$tuple:
|
||||
return brackets(... p[0].map(pp => typeFor(unname(pp))));
|
||||
case M.$tuple_STAR_:
|
||||
|
@ -93,7 +100,7 @@ export function compile(env: Environment, schema: Schema, preservesModule = '@pr
|
|||
seq(`get(k: typeof ${literal(k)}): `, typeFor(vp))),
|
||||
... Array.from(p[0]).map(([k, _vp]) =>
|
||||
seq(`has(k: typeof ${literal(k)}): true`))),
|
||||
' & _.Dictionary<_.Value>'));
|
||||
' & _.Dictionary<_val, _ptr>'));
|
||||
default:
|
||||
((_p: never) => {})(p);
|
||||
throw new Error("Unreachable");
|
||||
|
@ -124,19 +131,19 @@ export function compile(env: Environment, schema: Schema, preservesModule = '@pr
|
|||
return `_.isPointer(${v})`;
|
||||
case M.$rec:
|
||||
return opseq('true', ' && ',
|
||||
`_.Record.isRecord(${v})`,
|
||||
`_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`,
|
||||
walk(`${v}.label`, p[0]),
|
||||
walk(v, p[1], true));
|
||||
case M.$tuple:
|
||||
return opseq('true', ' && ',
|
||||
... (recordOkAsTuple ? []
|
||||
: [`_.Array.isArray(${v})`, `!_.Record.isRecord(${v})`]),
|
||||
: [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]),
|
||||
`(${v}.length === ${p[0].length})`,
|
||||
... p[0].map((pp, i) => walk(`${v}[${i}]`, unname(pp))));
|
||||
case M.$tuple_STAR_:
|
||||
return opseq('true', ' && ',
|
||||
... (recordOkAsTuple ? []
|
||||
: [`_.Array.isArray(${v})`, `!_.Record.isRecord(${v})`]),
|
||||
: [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]),
|
||||
`(${v}.length >= ${p[0].length})`,
|
||||
seq(`${v}.slice(${p[0].length})`,
|
||||
`.every(v => `,
|
||||
|
@ -145,14 +152,14 @@ export function compile(env: Environment, schema: Schema, preservesModule = '@pr
|
|||
... p[0].map((pp, i) => walk(`${v}[${i}]`, unname(pp))));
|
||||
case M.$setof:
|
||||
return opseq('true', ' && ',
|
||||
`_.Set.isSet(${v})`,
|
||||
`_.Set.isSet<_val>(${v})`,
|
||||
fnblock(
|
||||
seq(`for (const vv of ${v}) `, block(
|
||||
seq('if (!(', walk('vv', p[0]), ')) return false'))),
|
||||
seq('return true')));
|
||||
case M.$dictof:
|
||||
return opseq('true', ' && ',
|
||||
`_.Dictionary.isDictionary(${v})`,
|
||||
`_.Dictionary.isDictionary<_val, _ptr>(${v})`,
|
||||
fnblock(
|
||||
seq(`for (const e of ${v}) `, block(
|
||||
seq('if (!(', walk('e[0]', p[0]), ')) return false'),
|
||||
|
@ -160,7 +167,7 @@ export function compile(env: Environment, schema: Schema, preservesModule = '@pr
|
|||
seq('return true')));
|
||||
case M.$dict:
|
||||
return opseq('true', ' && ',
|
||||
`_.Dictionary.isDictionary(${v})`,
|
||||
`_.Dictionary.isDictionary<_val, _ptr>(${v})`,
|
||||
... Array.from(p[0]).map(([k, vp]) => {
|
||||
const tmp = gentemp();
|
||||
return parens(seq(
|
||||
|
@ -196,7 +203,7 @@ export function compile(env: Environment, schema: Schema, preservesModule = '@pr
|
|||
types.push(
|
||||
seq(`export const ${name.description!} = _.Record.makeConstructor<`,
|
||||
braces(... pattern[1][0].map(fieldEntry)),
|
||||
`>()(${literal(pattern[0][0])}, `,
|
||||
`, _ptr>()(${literal(pattern[0][0])}, `,
|
||||
JSON.stringify(pattern[1][0].map(fieldName)), `);`));
|
||||
}
|
||||
types.push(
|
||||
|
@ -212,13 +219,16 @@ export function compile(env: Environment, schema: Schema, preservesModule = '@pr
|
|||
'(v: any): ', name.description!, ' ',
|
||||
block(
|
||||
seq(`if (!is${name.description!}(v)) `,
|
||||
block(`throw new TypeError("Invalid ${name.description!}")`),
|
||||
block(`throw new TypeError(\`Invalid ${name.description!}: \${_.stringify(v)}\`)`),
|
||||
' else ',
|
||||
block(`return v`)))));
|
||||
}
|
||||
|
||||
types.push(seq('export type _ptr = ', pointerName === false ? 'never' : typeFor(pointerName), `;`));
|
||||
types.push(`export type _val = _.Value<_ptr>;`);
|
||||
|
||||
const f = new Formatter();
|
||||
f.write(`import * as _ from ${JSON.stringify(preservesModule)};\n`);
|
||||
f.write(`import * as _ from ${JSON.stringify(options.preservesModule ?? '@preserves/core')};\n`);
|
||||
imports.forEach(([identifier, path]) => {
|
||||
f.write(`import * as ${identifier} from ${JSON.stringify(path)};\n`);
|
||||
});
|
||||
|
@ -264,21 +274,21 @@ export function sourceCodeFor(v: Value<any>): Item {
|
|||
symbol(s: symbol): Item { return `Symbol.for(${stringSource(s.description!)})`; },
|
||||
|
||||
record(r: Record<Value<any>, Tuple<Value<any>>, any>, k: Fold<any, Item>): Item {
|
||||
return seq(`_.Record`, parens(k(r.label), brackets(... r.map(k))));
|
||||
return seq(`_.Record<_val, _.Tuple<_val>, _ptr>`, parens(k(r.label), brackets(... r.map(k))));
|
||||
},
|
||||
array(a: Array<Value<any>>, k: Fold<any, Item>): Item {
|
||||
return brackets(... a.map(k));
|
||||
},
|
||||
set(s: Set<any>, k: Fold<any, Item>): Item {
|
||||
return seq('new _.Set', parens(brackets(... Array.from(s).map(k))));
|
||||
return seq('new _.Set<_val>', parens(brackets(... Array.from(s).map(k))));
|
||||
},
|
||||
dictionary(d: Dictionary<Value<any>, any>, k: Fold<any, Item>): Item {
|
||||
return seq('new _.Dictionary', parens(brackets(... Array.from(d).map(([kk,vv]) =>
|
||||
return seq('new _.Dictionary<_val, _ptr>', parens(brackets(... Array.from(d).map(([kk,vv]) =>
|
||||
brackets(k(kk), k(vv))))));
|
||||
},
|
||||
|
||||
annotated(a: Annotated<any>, k: Fold<any, Item>): Item {
|
||||
return seq('_.annotate', parens(k(a.item), ... a.annotations.map(k)));
|
||||
return seq('_.annotate<_ptr>', parens(k(a.item), ... a.annotations.map(k)));
|
||||
},
|
||||
|
||||
pointer(t: any, _k: Fold<any, Item>): Item {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Position, formatPosition } from '@preserves/core';
|
||||
import { Position } from '@preserves/core';
|
||||
|
||||
export class SchemaSyntaxError extends Error {
|
||||
readonly pos: Position | null;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import * as _ from "@preserves/core";
|
||||
|
||||
export type _ptr = never;
|
||||
export type _val = _.Value<_ptr>;
|
||||
|
||||
export const $1 = 1;
|
||||
export const $Boolean = Symbol.for("Boolean");
|
||||
export const $ByteString = Symbol.for("ByteString");
|
||||
|
@ -25,17 +28,20 @@ export const $thisModule = Symbol.for("thisModule");
|
|||
export const $tuple = Symbol.for("tuple");
|
||||
export const $tuple_STAR_ = Symbol.for("tuple*");
|
||||
export const $version = Symbol.for("version");
|
||||
export const __lit5 = false;
|
||||
|
||||
export const Schema = _.Record.makeConstructor<{
|
||||
"details": (
|
||||
{
|
||||
get(k: typeof $version): Version;
|
||||
get(k: typeof $pointer): PointerName;
|
||||
get(k: typeof $definitions): _.KeyedDictionary<symbol, Pattern>;
|
||||
has(k: typeof $version): true;
|
||||
has(k: typeof $pointer): true;
|
||||
has(k: typeof $definitions): true;
|
||||
} & _.Dictionary<_.Value>
|
||||
} & _.Dictionary<_val, _ptr>
|
||||
)
|
||||
}>()($schema, ["details"]);
|
||||
}, _ptr>()($schema, ["details"]);
|
||||
|
||||
export type Schema = _.Record<
|
||||
(typeof $schema),
|
||||
|
@ -43,16 +49,21 @@ export type Schema = _.Record<
|
|||
(
|
||||
{
|
||||
get(k: typeof $version): Version;
|
||||
get(k: typeof $pointer): PointerName;
|
||||
get(k: typeof $definitions): _.KeyedDictionary<symbol, Pattern>;
|
||||
has(k: typeof $version): true;
|
||||
has(k: typeof $pointer): true;
|
||||
has(k: typeof $definitions): true;
|
||||
} & _.Dictionary<_.Value>
|
||||
} & _.Dictionary<_val, _ptr>
|
||||
)
|
||||
]
|
||||
],
|
||||
_ptr
|
||||
>;
|
||||
|
||||
export type Version = (typeof $1);
|
||||
|
||||
export type PointerName = (Ref | (typeof __lit5));
|
||||
|
||||
export type Pattern = (
|
||||
_.Record<
|
||||
(typeof $atom),
|
||||
|
@ -66,26 +77,27 @@ export type Pattern = (
|
|||
(typeof $ByteString) |
|
||||
(typeof $Symbol)
|
||||
)
|
||||
]
|
||||
],
|
||||
_ptr
|
||||
> |
|
||||
_.Record<(typeof $pointer), []> |
|
||||
_.Record<(typeof $lit), [_.Value]> |
|
||||
_.Record<(typeof $pointer), [], _ptr> |
|
||||
_.Record<(typeof $lit), [_val], _ptr> |
|
||||
Ref |
|
||||
_.Record<(typeof $or), [Array<Pattern>]> |
|
||||
_.Record<(typeof $and), [Array<Pattern>]> |
|
||||
_.Record<(typeof $rec), [Pattern, Pattern]> |
|
||||
_.Record<(typeof $tuple), [Array<NamedPattern>]> |
|
||||
_.Record<(typeof $tuple_STAR_), [Array<NamedPattern>, NamedPattern]> |
|
||||
_.Record<(typeof $setof), [Pattern]> |
|
||||
_.Record<(typeof $dictof), [Pattern, Pattern]> |
|
||||
_.Record<(typeof $dict), [_.KeyedDictionary<_.Value, Pattern>]>
|
||||
_.Record<(typeof $or), [Array<Pattern>], _ptr> |
|
||||
_.Record<(typeof $and), [Array<Pattern>], _ptr> |
|
||||
_.Record<(typeof $rec), [Pattern, Pattern], _ptr> |
|
||||
_.Record<(typeof $tuple), [Array<NamedPattern>], _ptr> |
|
||||
_.Record<(typeof $tuple_STAR_), [Array<NamedPattern>, NamedPattern], _ptr> |
|
||||
_.Record<(typeof $setof), [Pattern], _ptr> |
|
||||
_.Record<(typeof $dictof), [Pattern, Pattern], _ptr> |
|
||||
_.Record<(typeof $dict), [_.KeyedDictionary<_val, Pattern>], _ptr>
|
||||
);
|
||||
|
||||
export type NamedPattern = (_.Record<(typeof $named), [symbol, Pattern]> | Pattern);
|
||||
export type NamedPattern = (_.Record<(typeof $named), [symbol, Pattern], _ptr> | Pattern);
|
||||
|
||||
export const Ref = _.Record.makeConstructor<{"module": ModuleRef, "name": symbol}>()($ref, ["module","name"]);
|
||||
export const Ref = _.Record.makeConstructor<{"module": ModuleRef, "name": symbol}, _ptr>()($ref, ["module","name"]);
|
||||
|
||||
export type Ref = _.Record<(typeof $ref), [ModuleRef, symbol]>;
|
||||
export type Ref = _.Record<(typeof $ref), [ModuleRef, symbol], _ptr>;
|
||||
|
||||
export type ModuleRef = ((typeof $thisModule) | ModulePath);
|
||||
|
||||
|
@ -93,20 +105,21 @@ export type ModulePath = Array<symbol>;
|
|||
|
||||
|
||||
export function isSchema(v: any): v is Schema {
|
||||
let _tmp0, _tmp1: any;
|
||||
let _tmp0, _tmp1, _tmp2: any;
|
||||
return (
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $schema) &&
|
||||
(
|
||||
(v.length === 1) &&
|
||||
(
|
||||
_.Dictionary.isDictionary(v[0]) &&
|
||||
_.Dictionary.isDictionary<_val, _ptr>(v[0]) &&
|
||||
((_tmp0 = v[0].get($version)) !== void 0 && isVersion(_tmp0)) &&
|
||||
((_tmp1 = v[0].get($pointer)) !== void 0 && isPointerName(_tmp1)) &&
|
||||
(
|
||||
(_tmp1 = v[0].get($definitions)) !== void 0 && (
|
||||
_.Dictionary.isDictionary(_tmp1) &&
|
||||
(_tmp2 = v[0].get($definitions)) !== void 0 && (
|
||||
_.Dictionary.isDictionary<_val, _ptr>(_tmp2) &&
|
||||
((() => {
|
||||
for (const e of _tmp1) {
|
||||
for (const e of _tmp2) {
|
||||
if (!(typeof e[0] === 'symbol')) return false;
|
||||
if (!(isPattern(e[1]))) return false;
|
||||
};
|
||||
|
@ -119,16 +132,26 @@ export function isSchema(v: any): v is Schema {
|
|||
);
|
||||
}
|
||||
|
||||
export function asSchema(v: any): Schema {if (!isSchema(v)) {throw new TypeError("Invalid Schema");} else {return v;};}
|
||||
export function asSchema(v: any): Schema {
|
||||
if (!isSchema(v)) {throw new TypeError(`Invalid Schema: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function isVersion(v: any): v is Version {return _.is(v, $1);}
|
||||
|
||||
export function asVersion(v: any): Version {if (!isVersion(v)) {throw new TypeError("Invalid Version");} else {return v;};}
|
||||
export function asVersion(v: any): Version {
|
||||
if (!isVersion(v)) {throw new TypeError(`Invalid Version: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function isPointerName(v: any): v is PointerName {return (isRef(v) || _.is(v, __lit5));}
|
||||
|
||||
export function asPointerName(v: any): PointerName {
|
||||
if (!isPointerName(v)) {throw new TypeError(`Invalid PointerName: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function isPattern(v: any): v is Pattern {
|
||||
return (
|
||||
(
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $atom) &&
|
||||
(
|
||||
(v.length === 1) &&
|
||||
|
@ -143,61 +166,69 @@ export function isPattern(v: any): v is Pattern {
|
|||
)
|
||||
)
|
||||
) ||
|
||||
(_.Record.isRecord(v) && _.is(v.label, $pointer) && ((v.length === 0))) ||
|
||||
(_.Record.isRecord(v) && _.is(v.label, $lit) && ((v.length === 1) && true)) ||
|
||||
(
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $pointer) &&
|
||||
((v.length === 0))
|
||||
) ||
|
||||
(
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $lit) &&
|
||||
((v.length === 1) && true)
|
||||
) ||
|
||||
isRef(v) ||
|
||||
(
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $or) &&
|
||||
(
|
||||
(v.length === 1) &&
|
||||
(
|
||||
_.Array.isArray(v[0]) &&
|
||||
!_.Record.isRecord(v[0]) &&
|
||||
!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0]) &&
|
||||
(v[0].length >= 0) &&
|
||||
v[0].slice(0).every(v => (isPattern(v)))
|
||||
)
|
||||
)
|
||||
) ||
|
||||
(
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $and) &&
|
||||
(
|
||||
(v.length === 1) &&
|
||||
(
|
||||
_.Array.isArray(v[0]) &&
|
||||
!_.Record.isRecord(v[0]) &&
|
||||
!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0]) &&
|
||||
(v[0].length >= 0) &&
|
||||
v[0].slice(0).every(v => (isPattern(v)))
|
||||
)
|
||||
)
|
||||
) ||
|
||||
(
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $rec) &&
|
||||
((v.length === 2) && isPattern(v[0]) && isPattern(v[1]))
|
||||
) ||
|
||||
(
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $tuple) &&
|
||||
(
|
||||
(v.length === 1) &&
|
||||
(
|
||||
_.Array.isArray(v[0]) &&
|
||||
!_.Record.isRecord(v[0]) &&
|
||||
!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0]) &&
|
||||
(v[0].length >= 0) &&
|
||||
v[0].slice(0).every(v => (isNamedPattern(v)))
|
||||
)
|
||||
)
|
||||
) ||
|
||||
(
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $tuple_STAR_) &&
|
||||
(
|
||||
(v.length === 2) &&
|
||||
(
|
||||
_.Array.isArray(v[0]) &&
|
||||
!_.Record.isRecord(v[0]) &&
|
||||
!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v[0]) &&
|
||||
(v[0].length >= 0) &&
|
||||
v[0].slice(0).every(v => (isNamedPattern(v)))
|
||||
) &&
|
||||
|
@ -205,22 +236,22 @@ export function isPattern(v: any): v is Pattern {
|
|||
)
|
||||
) ||
|
||||
(
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $setof) &&
|
||||
((v.length === 1) && isPattern(v[0]))
|
||||
) ||
|
||||
(
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $dictof) &&
|
||||
((v.length === 2) && isPattern(v[0]) && isPattern(v[1]))
|
||||
) ||
|
||||
(
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $dict) &&
|
||||
(
|
||||
(v.length === 1) &&
|
||||
(
|
||||
_.Dictionary.isDictionary(v[0]) &&
|
||||
_.Dictionary.isDictionary<_val, _ptr>(v[0]) &&
|
||||
((() => {
|
||||
for (const e of v[0]) {if (!(true)) return false; if (!(isPattern(e[1]))) return false;};
|
||||
return true;
|
||||
|
@ -231,12 +262,14 @@ export function isPattern(v: any): v is Pattern {
|
|||
);
|
||||
}
|
||||
|
||||
export function asPattern(v: any): Pattern {if (!isPattern(v)) {throw new TypeError("Invalid Pattern");} else {return v;};}
|
||||
export function asPattern(v: any): Pattern {
|
||||
if (!isPattern(v)) {throw new TypeError(`Invalid Pattern: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function isNamedPattern(v: any): v is NamedPattern {
|
||||
return (
|
||||
(
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $named) &&
|
||||
((v.length === 2) && typeof v[0] === 'symbol' && isPattern(v[1]))
|
||||
) ||
|
||||
|
@ -245,35 +278,37 @@ export function isNamedPattern(v: any): v is NamedPattern {
|
|||
}
|
||||
|
||||
export function asNamedPattern(v: any): NamedPattern {
|
||||
if (!isNamedPattern(v)) {throw new TypeError("Invalid NamedPattern");} else {return v;};
|
||||
if (!isNamedPattern(v)) {throw new TypeError(`Invalid NamedPattern: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function isRef(v: any): v is Ref {
|
||||
return (
|
||||
_.Record.isRecord(v) &&
|
||||
_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
_.is(v.label, $ref) &&
|
||||
((v.length === 2) && isModuleRef(v[0]) && typeof v[1] === 'symbol')
|
||||
);
|
||||
}
|
||||
|
||||
export function asRef(v: any): Ref {if (!isRef(v)) {throw new TypeError("Invalid Ref");} else {return v;};}
|
||||
export function asRef(v: any): Ref {
|
||||
if (!isRef(v)) {throw new TypeError(`Invalid Ref: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function isModuleRef(v: any): v is ModuleRef {return (_.is(v, $thisModule) || isModulePath(v));}
|
||||
|
||||
export function asModuleRef(v: any): ModuleRef {
|
||||
if (!isModuleRef(v)) {throw new TypeError("Invalid ModuleRef");} else {return v;};
|
||||
if (!isModuleRef(v)) {throw new TypeError(`Invalid ModuleRef: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
export function isModulePath(v: any): v is ModulePath {
|
||||
return (
|
||||
_.Array.isArray(v) &&
|
||||
!_.Record.isRecord(v) &&
|
||||
!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(v) &&
|
||||
(v.length >= 0) &&
|
||||
v.slice(0).every(v => (typeof v === 'symbol'))
|
||||
);
|
||||
}
|
||||
|
||||
export function asModulePath(v: any): ModulePath {
|
||||
if (!isModulePath(v)) {throw new TypeError("Invalid ModulePath");} else {return v;};
|
||||
if (!isModulePath(v)) {throw new TypeError(`Invalid ModulePath: ${_.stringify(v)}`);} else {return v;};
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ export function validator(env: Environment, p: Pattern): (v: Value<any>) => bool
|
|||
return lookup(refPosition(p), p, env,
|
||||
(p) => walk(p, v),
|
||||
(p) => walk(p, v),
|
||||
(_mod, _modPath, p) => walk(p, v));
|
||||
(_mod, _modPath, p) => walk(p!, v));
|
||||
case M.$or:
|
||||
for (const pp of p[0]) {
|
||||
if (walk(pp, v)) return true;
|
||||
|
|
|
@ -21,32 +21,44 @@ export const DOTDOTDOT = Symbol.for('...');
|
|||
export const EQUALS = Symbol.for('=');
|
||||
export const ORSYM = Symbol.for('/');
|
||||
|
||||
export type SchemaEnvEntry = {
|
||||
schemaModulePath: ModulePath,
|
||||
typescriptModulePath: string | null, // null means it's $thisModule in disguise
|
||||
schema: Schema,
|
||||
};
|
||||
export type SchemaEnvEntry = { schemaModulePath: ModulePath } & (
|
||||
({
|
||||
typescriptModulePath: string | null, // null means it's $thisModule in disguise
|
||||
schema: Schema,
|
||||
}) | ({
|
||||
typescriptModulePath: string,
|
||||
schema: null,
|
||||
})
|
||||
);
|
||||
|
||||
export type Environment = Array<SchemaEnvEntry>;
|
||||
|
||||
function modsymFor(e: SchemaEnvEntry): string {
|
||||
return '_i_' + e.schemaModulePath.map(s => s.description!).join('$');
|
||||
}
|
||||
|
||||
export function lookup<R>(namePos: Position | null,
|
||||
name: Ref,
|
||||
env: Environment,
|
||||
kLocal: (p: Pattern) => R,
|
||||
kBase: (p: Pattern) => R,
|
||||
kOther: (mod: string, modPath: string, p: Pattern) => R): R
|
||||
kOther: (mod: string, modPath: string, p: Pattern | null) => R): R
|
||||
{
|
||||
for (const e of env) {
|
||||
if (is(e.schemaModulePath, Ref._.module(name)) ||
|
||||
(e.typescriptModulePath === null && Ref._.module(name) === $thisModule))
|
||||
{
|
||||
const p = Schema._.details(e.schema).get($definitions).get(Ref._.name(name));
|
||||
if (p !== void 0) {
|
||||
if (e.typescriptModulePath === null) {
|
||||
return kLocal(p);
|
||||
} else {
|
||||
const modsym = '_i_' + e.schemaModulePath.map(s => s.description!).join('$');
|
||||
return kOther(modsym, e.typescriptModulePath, p);
|
||||
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 = Schema._.details(e.schema).get($definitions).get(Ref._.name(name));
|
||||
if (p !== void 0) {
|
||||
if (e.typescriptModulePath === null) {
|
||||
return kLocal(p);
|
||||
} else {
|
||||
return kOther(modsymFor(e), e.typescriptModulePath, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Reader, Annotated, Dictionary, is, peel, preserves, Record, strip, Tuple, Value, Position, position, formatPosition, ReaderOptions } from '@preserves/core';
|
||||
import { Reader, Annotated, Dictionary, is, peel, preserves, Record, strip, Tuple, Value, Position, position, ReaderOptions, stringify } from '@preserves/core';
|
||||
import { Input, NamedPattern, Pattern, Schema } from './meta';
|
||||
import * as M from './meta';
|
||||
import { SchemaSyntaxError } from './error';
|
||||
|
||||
const positionTable = new WeakMap<Input & object, Position>();
|
||||
|
||||
|
@ -35,11 +36,12 @@ function splitBy<T>(items: Array<T>, separator: T): Array<Array<T>> {
|
|||
}
|
||||
|
||||
function invalidClause(clause: Array<Input>): never {
|
||||
throw new Error(formatPosition(position(clause[0] ?? false)) + preserves`: Invalid Schema clause: ${clause}`);
|
||||
throw new SchemaSyntaxError(preserves`Invalid Schema clause: ${clause}`,
|
||||
position(clause[0] ?? false));
|
||||
}
|
||||
|
||||
function invalidPattern(name: symbol, item: Input, pos: Position | null): never {
|
||||
throw new Error(formatPosition(pos) + preserves`: Invalid pattern in ${name}: ${item}`);
|
||||
function invalidPattern(name: string, item: Input, pos: Position | null): never {
|
||||
throw new SchemaSyntaxError(`Invalid pattern in ${name}: ${stringify(item)}`, pos);
|
||||
}
|
||||
|
||||
export function readSchema(source: string, options?: ReaderOptions<never>): Schema {
|
||||
|
@ -50,6 +52,7 @@ export function readSchema(source: string, options?: ReaderOptions<never>): Sche
|
|||
export function parseSchema(toplevelTokens: Array<Input>): Schema {
|
||||
const toplevelClauses = splitBy(peel(toplevelTokens) as Array<Input>, M.DOT);
|
||||
let version: M.Version | undefined = void 0;
|
||||
let pointer: M.PointerName = false;
|
||||
let definitions = new Dictionary<Pattern, never>();
|
||||
for (const clause of toplevelClauses) {
|
||||
if (!Array.isArray(clause)) {
|
||||
|
@ -58,25 +61,35 @@ export function parseSchema(toplevelTokens: Array<Input>): Schema {
|
|||
const name = peel(clause[0]);
|
||||
if (typeof name !== 'symbol') invalidClause(clause);
|
||||
if (!M.isValidToken(name.description!)) {
|
||||
throw new Error(preserves`Invalid definition name: ${name}`);
|
||||
throw new SchemaSyntaxError(preserves`Invalid definition name: ${name}`,
|
||||
position(clause[0]));
|
||||
}
|
||||
if (definitions.has(name)) {
|
||||
throw new Error(preserves`Duplicate definition: ${clause}`);
|
||||
throw new SchemaSyntaxError(preserves`Duplicate definition: ${clause}`,
|
||||
position(clause[0]));
|
||||
}
|
||||
definitions.set(name, parseDefinition(name, clause.slice(2)));
|
||||
} else if (clause.length === 2 && is(clause[0], M.$version)) {
|
||||
version = M.asVersion(peel(clause[1]));
|
||||
} else if (clause.length === 2 && is(clause[0], M.$pointer)) {
|
||||
const pos = position(clause[1]);
|
||||
const stx = peel(clause[1]);
|
||||
const quasiName = 'pointer name specification';
|
||||
pointer = M.asPointerName((stx === false) ? stx
|
||||
: (typeof stx === 'symbol') ? parseRef(quasiName, pos, stx)
|
||||
: invalidPattern(quasiName, stx, pos));
|
||||
} else {
|
||||
invalidClause(clause);
|
||||
}
|
||||
}
|
||||
if (version === void 0) {
|
||||
throw new Error("Schema: missing version declaration.");
|
||||
throw new SchemaSyntaxError("Schema: missing version declaration.", null);
|
||||
}
|
||||
return Record(M.$schema, [new Dictionary<Value>([
|
||||
return M.asSchema(Record(M.$schema, [new Dictionary<Value>([
|
||||
[M.$version, version],
|
||||
[M.$pointer, pointer],
|
||||
[M.$definitions, definitions],
|
||||
])]);
|
||||
])]));
|
||||
}
|
||||
|
||||
function parseDefinition(name: symbol, body: Array<Input>): Pattern {
|
||||
|
@ -102,10 +115,25 @@ function findName(x: Input): symbol | false {
|
|||
return false;
|
||||
}
|
||||
|
||||
function parseRef(name: string, pos: Position | null, item: symbol): Pattern {
|
||||
const s = item.description;
|
||||
if (s === void 0) invalidPattern(name, item, pos);
|
||||
if (s[0] === '=') return Record(M.$lit, [Symbol.for(s.slice(1))]);
|
||||
const pieces = s.split('.');
|
||||
if (pieces.length === 1) {
|
||||
return recordPosition(Record(M.$ref, [M.$thisModule, item]), pos);
|
||||
} else {
|
||||
return recordPosition(Record(M.$ref, [
|
||||
pieces.slice(0, pieces.length - 1).map(Symbol.for),
|
||||
Symbol.for(pieces[pieces.length - 1])
|
||||
]), pos);
|
||||
}
|
||||
}
|
||||
|
||||
function parseBase(name: symbol, body: Array<Input>): Pattern {
|
||||
body = peel(body) as Array<Input>;
|
||||
if (body.length !== 1) {
|
||||
invalidPattern(name, body, body.length > 0 ? position(body[0]) : position(body));
|
||||
invalidPattern(stringify(name), body, body.length > 0 ? position(body[0]) : position(body));
|
||||
}
|
||||
|
||||
const pos = position(body[0]);
|
||||
|
@ -124,23 +152,11 @@ function parseBase(name: symbol, body: Array<Input>): Pattern {
|
|||
};
|
||||
|
||||
function complain(): never {
|
||||
invalidPattern(name, item, pos);
|
||||
invalidPattern(stringify(name), item, pos);
|
||||
}
|
||||
|
||||
if (typeof item === 'symbol') {
|
||||
const pos = position(body[0]);
|
||||
const s = item.description;
|
||||
if (s === void 0) complain();
|
||||
if (s[0] === '=') return Record(M.$lit, [Symbol.for(s.slice(1))]);
|
||||
const pieces = s.split('.');
|
||||
if (pieces.length === 1) {
|
||||
return recordPosition(Record(M.$ref, [M.$thisModule, item]), pos);
|
||||
} else {
|
||||
return recordPosition(Record(M.$ref, [
|
||||
pieces.slice(0, pieces.length - 1).map(Symbol.for),
|
||||
Symbol.for(pieces[pieces.length - 1])
|
||||
]), pos);
|
||||
}
|
||||
return parseRef(stringify(name), position(body[0]), item);
|
||||
} else if (Record.isRecord<Input, Tuple<Input>, never>(item)) {
|
||||
const label = item.label;
|
||||
if (Record.isRecord<Input, [], never>(label)) {
|
||||
|
|
|
@ -4,12 +4,15 @@ version 1 .
|
|||
|
||||
Schema = <schema @details {
|
||||
version: Version
|
||||
pointer: PointerName
|
||||
definitions: { symbol: Pattern ...:... }
|
||||
}>.
|
||||
|
||||
; version 1 .
|
||||
Version = 1 .
|
||||
|
||||
PointerName = Ref / #f.
|
||||
|
||||
Pattern = <<or> [
|
||||
; special builtins or <<atom> symbol>
|
||||
<atom @atomKind <<or> [=Boolean =Float =Double =SignedInteger =String =ByteString =Symbol]>>
|
||||
|
|
Loading…
Reference in New Issue