Pointers; improved diagnostics

This commit is contained in:
Tony Garnock-Jones 2021-03-11 17:59:40 +01:00
parent c09032f609
commit 8d7e7c6d95
8 changed files with 265 additions and 133 deletions

View File

@ -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;

View File

@ -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 {

View File

@ -1,4 +1,4 @@
import { Position, formatPosition } from '@preserves/core';
import { Position } from '@preserves/core';
export class SchemaSyntaxError extends Error {
readonly pos: Position | null;

View File

@ -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;};
}

View File

@ -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;

View File

@ -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);
}
}
}
}

View File

@ -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)) {

View File

@ -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]>>