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