Includes in schema compiler

This commit is contained in:
Tony Garnock-Jones 2021-03-11 23:02:18 +01:00
parent 55e4222d68
commit c75aaf4b18
6 changed files with 77 additions and 35 deletions

View File

@ -31,6 +31,7 @@
"@types/minimatch": "^3.0.3",
"@types/yargs": "^16.0.0",
"chalk": "^4.1.0",
"chokidar": "^3.5.1",
"glob": "^7.1.6",
"minimatch": "^3.0.4",
"yargs": "^16.2.0"

View File

@ -43,6 +43,7 @@ export default [{
external: [
'@preserves/core',
'chalk',
'chokidar',
'fs',
'glob',
'minimatch',

View File

@ -7,6 +7,7 @@ import yargs from 'yargs/yargs';
import * as M from '../meta';
import chalk from 'chalk';
import { formatPosition, Position } from '@preserves/core';
import chokidar from 'chokidar';
export type CommandLineArguments = {
input: string;
@ -84,8 +85,9 @@ export function run(options: CommandLineArguments): void {
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);
const watcher = chokidar.watch(r.base, {
ignoreInitial: true,
}).on('all', (_event, filename) => {
if (minimatch(filename, options.input)) {
watcher.close();
runWatch();
@ -134,7 +136,14 @@ export function runOnce(options: CommandLineArguments): CompilationResult {
const outputFilePath = path.join(output, changeExt(relPath, '.ts'));
try {
const src = fs.readFileSync(inputFilePath, 'utf-8');
const schema = readSchema(src, { name: inputFilePath });
const schema = readSchema(src, {
name: inputFilePath,
readInclude(includePath: string): string {
return fs.readFileSync(
path.resolve(path.dirname(inputFilePath), includePath),
'utf-8');
},
});
const schemaPath = relPath.split('/').map(p => p.split('.')[0]).map(Symbol.for);
return [{ inputFilePath, outputFilePath, schemaPath, schema }];
} catch (e) {

View File

@ -90,9 +90,9 @@ export function compile(env: Environment, schema: Schema, options: CompilerOptio
seq('... Array<', typeFor(unname(p[1])), '>'));
}
case M.$setof:
return seq('_.KeyedSet<', typeFor(p[0]), '>');
return seq('_.KeyedSet', anglebrackets(typeFor(p[0]), '_ptr'));
case M.$dictof:
return seq('_.KeyedDictionary', anglebrackets(typeFor(p[0]), typeFor(p[1])));
return seq('_.KeyedDictionary', anglebrackets(typeFor(p[0]), typeFor(p[1]), '_ptr'));
case M.$dict:
return parens(seq(
block(

View File

@ -19,6 +19,7 @@ export const ANDSYM = Symbol.for('&');
export const DOT = Symbol.for('.');
export const DOTDOTDOT = Symbol.for('...');
export const EQUALS = Symbol.for('=');
export const INCLUDE = Symbol.for('include');
export const ORSYM = Symbol.for('/');
export type SchemaEnvEntry = { schemaModulePath: ModulePath } & (

View File

@ -44,47 +44,77 @@ 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 {
const toplevelTokens = new Reader<never>(source, { ... options ?? {}, includeAnnotations: true }).readToEnd();
return parseSchema(toplevelTokens);
export type SchemaReaderOptions = {
readInclude?(includePath: string): string;
};
function _readSchema(source: string, options?: ReaderOptions<never>): Array<Input> {
return new Reader<never>(source, {
... options ?? {},
includeAnnotations: true
}).readToEnd();
}
export function parseSchema(toplevelTokens: Array<Input>): Schema {
const toplevelClauses = splitBy(peel(toplevelTokens) as Array<Input>, M.DOT);
export function readSchema(source: string,
options?: ReaderOptions<never> & SchemaReaderOptions): Schema
{
return parseSchema(_readSchema(source, options), options ?? {});
}
export function parseSchema(toplevelTokens: Array<Input>,
options: ReaderOptions<never> & SchemaReaderOptions): Schema
{
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)) {
invalidClause(clause);
} else if (clause.length >= 2 && is(clause[1], M.EQUALS)) {
const name = peel(clause[0]);
if (typeof name !== 'symbol') invalidClause(clause);
if (!M.isValidToken(name.description!)) {
throw new SchemaSyntaxError(preserves`Invalid definition name: ${name}`,
position(clause[0]));
function process(toplevelTokens: Array<Input>): void {
const toplevelClauses = splitBy(peel(toplevelTokens) as Array<Input>, M.DOT);
for (const clause of toplevelClauses) {
if (!Array.isArray(clause)) {
invalidClause(clause);
} else if (clause.length >= 2 && is(clause[1], M.EQUALS)) {
const pos = position(clause[0]);
const name = peel(clause[0]);
if (typeof name !== 'symbol') invalidClause(clause);
if (!M.isValidToken(name.description!)) {
throw new SchemaSyntaxError(preserves`Invalid definition name: ${name}`, pos);
}
if (definitions.has(name)) {
throw new SchemaSyntaxError(preserves`Duplicate definition: ${clause}`, pos);
}
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 if (clause.length === 2 && is(clause[0], M.INCLUDE)) {
const pos = position(clause[1]);
const path = peel(clause[1]);
if (typeof path !== 'string') {
throw new SchemaSyntaxError(preserves`Invalid include: ${clause}`, pos);
}
if (options.readInclude === void 0) {
throw new SchemaSyntaxError(preserves`Cannot include files in schema`, pos);
}
process(_readSchema(options.readInclude(path), options));
} else {
invalidClause(clause);
}
if (definitions.has(name)) {
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);
}
}
process(toplevelTokens);
if (version === void 0) {
throw new SchemaSyntaxError("Schema: missing version declaration.", null);
}
return M.asSchema(Record(M.$schema, [new Dictionary<Value>([
[M.$version, version],
[M.$pointer, pointer],