Improve compiler driver

This commit is contained in:
Tony Garnock-Jones 2021-03-11 10:56:49 +01:00
parent ba2c7e9978
commit d932431d83
10 changed files with 282 additions and 81 deletions

View File

@ -13,7 +13,7 @@
"types": "lib/index.d.ts",
"author": "Tony Garnock-Jones <tonyg@leastfixedpoint.com>",
"scripts": {
"regenerate": "./bin/preserves-schema-ts.js ../../../../schema/schema.txt > src/schemaschema.ts",
"regenerate": "rm -rf ./src/gen && ./bin/preserves-schema-ts.js --output ./src/gen ../../../../schema/schema.prs",
"clean": "rm -rf lib dist",
"prepare": "tsc && rollup -c",
"rollupwatch": "rollup -c -w",
@ -26,6 +26,10 @@
"preserves-schema-ts": "./bin/preserves-schema-ts.js"
},
"dependencies": {
"@preserves/core": "^0.8.0"
"@preserves/core": "^0.8.0",
"@types/glob": "^7.1.3",
"@types/yargs": "^16.0.0",
"glob": "^7.1.6",
"yargs": "^16.2.0"
}
}

View File

@ -1,10 +1,9 @@
import { Record, Dictionary, Value } from '@preserves/core';
import { Pattern, Schema } from './meta';
import * as M from './meta';
import * as M from './gen/schema';
export const BASE: Schema = Record(M.$schema, [new Dictionary<Value, never>([
export const BASE: M.Schema = Record(M.$schema, [new Dictionary<Value, never>([
[M.$version, 1],
[M.$definitions, new Dictionary<Pattern, never>([
[M.$definitions, new Dictionary<M.Pattern, never>([
[Symbol.for('any'), Record(M.$and, [[]])],
[Symbol.for('bool'), Record(M.$atom, [M.$Boolean])],
[Symbol.for('float'), Record(M.$atom, [M.$Float])],

View File

@ -1,12 +1,102 @@
import { BASE, compile, readSchema } from '../index';
import { compile, readSchema } from '../index';
import fs from 'fs';
import path from 'path';
import { glob } from 'glob';
import yargs from 'yargs/yargs';
import * as M from '../meta';
export type CommandLineArguments = {
input: string;
base: string | undefined;
output: string | undefined;
core: string;
};
export function computeBase(paths: string[]): string {
if (paths.length === 0) {
return '';
} else if (paths.length === 1) {
return path.dirname(paths[0]) + '/';
} else {
let i = 0;
while (true) {
let ch: string | null = null
for (const p of paths) {
if (i >= p.length) return p.slice(0, i);
if (ch === null) ch = p[i];
if (p[i] !== ch) return p.slice(0, i - 1);
}
i++;
}
}
}
type ToCompile = {
inputFilePath: string,
outputFilePath: string,
schemaPath: M.ModulePath,
schema: M.Schema,
};
function changeExt(p: string, newext: string) {
return p.slice(0, -path.extname(p).length) + newext;
}
export function run(options: CommandLineArguments) {
glob(options.input, (err, matches) => {
if (err) throw err;
const base = options.base ?? computeBase(matches);
const output = options.output ?? base;
const toCompile: Array<ToCompile> = matches.map(inputFilePath => {
if (!inputFilePath.startsWith(base)) {
throw new Error(`Input filename ${inputFilePath} falls outside base ${base}`);
}
const relPath = inputFilePath.slice(base.length);
const outputFilePath = path.join(output, changeExt(relPath, '.ts'));
const src = fs.readFileSync(inputFilePath, 'utf-8');
const schema = readSchema(src);
const schemaPath = relPath.split(path.delimiter).map(p => p.split('.')[0]).map(Symbol.for);
return { inputFilePath, outputFilePath, schemaPath, schema };
});
toCompile.forEach(c => {
const env: M.Environment = toCompile.map(cc => ({
schema: cc.schema,
schemaModulePath: cc.schemaPath,
typescriptModulePath: path.relative(c.outputFilePath, cc.outputFilePath) || null,
}));
fs.mkdirSync(path.dirname(c.outputFilePath), { recursive: true });
fs.writeFileSync(c.outputFilePath, compile(env, c.schema, options.core), 'utf-8');
});
});
}
export function main(argv: Array<string>) {
const [filename] = argv;
const src = fs.readFileSync(filename, 'utf-8');
const sch = readSchema(src);
console.log(compile(
[{ moduleName: 'BASE', modulePath: 'BASE', schema: BASE, inline: true }],
sch,
'@preserves/core'));
const options: CommandLineArguments = yargs(argv)
.command('$0 <input>',
'Compile Preserves schema definitions to TypeScript',
yargs => yargs
.positional('input', {
type: 'string',
description: 'Input filename or glob',
demandOption: true,
})
.option('output', {
type: 'string',
description: 'Output directory for sources (default: next to sources)',
})
.option('base', {
type: 'string',
description: 'Base directory for sources (default: common prefix)',
})
.option('core', {
type: 'string',
description: 'Import path for @preserves/core',
default: '@preserves/core',
}),
argv => argv)
.argv;
run(options);
}

View File

@ -1,50 +1,27 @@
import { Pattern, NamedPattern, Schema, Input } from "./meta";
import { Pattern, NamedPattern, Schema, Input, Environment, Ref, lookup } from "./meta";
import * as M from './meta';
import { Annotated, Bytes, Dictionary, Fold, fold, preserves, Record, Tuple, Value } from "@preserves/core";
import { Annotated, Bytes, Dictionary, Fold, fold, KeyedSet, preserves, Record, Set, Tuple, Value } from "@preserves/core";
import { Formatter, parens, seq, Item, opseq, block, commas, brackets, anglebrackets, braces } from "./block";
function fnblock(... items: Item[]): Item {
return seq('((() => ', block(... items), ')())');
}
export type CompileEnvEntry = {
moduleName: string,
modulePath: string,
schema: Schema,
inline: boolean,
};
export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesModule = '@preserves/core'): string {
export function compile(env: Environment, schema: Schema, preservesModule = '@preserves/core'): 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> = [];
function environmentWith<R>(name: symbol,
kLocal: () => R,
kOther: (e: CompileEnvEntry, p: Pattern) => R): R {
if (Schema._.details(schema).get(M.$definitions).has(name)) {
return kLocal();
}
for (const e of env) {
const p = Schema._.details(e.schema).get(M.$definitions).get(name);
if (p !== void 0) {
return kOther(e, p);
}
}
throw new Error(`Undefined reference: ${name.description!}`);
}
function applyPredicate(name: symbol, v: string): Item {
return environmentWith(name,
() => `is${name.description!}(${v})`,
(e, p) => {
if (e.inline) {
return walk(v, p);
} else {
return `${e.moduleName}.is${name.description!}(${v})`;
}
});
function applyPredicate(name: Ref, v: string): Item {
return lookup(name, env,
(_p) => `is${Ref._.name(name).description!}(${v})`,
(p) => walk(v, p),
(mod, modPath, _p) => {
imports.add([mod, modPath]);
return `${mod}.is${Ref._.name(name).description!}(${v})`;
});
}
function gentemp(): string {
@ -80,16 +57,13 @@ export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesMo
case M.$lit:
return `(typeof ${literal(p[0])})`;
case M.$ref:
return environmentWith(
p[0],
() => p[0].description!,
(e, pp) => {
if (e.inline) {
return typeFor(pp);
} else {
return `${e.moduleName}.${p[0].description!}`;
}
});
return lookup(p, env,
(_p) => p[1].description!,
(p) => typeFor(p),
(mod, modPath,_p) => {
imports.add([mod, modPath]);
return `${mod}.${p[1].description!}`;
});
case M.$or:
return opseq('never', ' | ', ... p[0].map(pp => typeFor(pp)));
case M.$and:
@ -148,7 +122,7 @@ export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesMo
return `_.is(${v}, ${literal(p[0])})`;
}
case M.$ref:
return applyPredicate(p[0], v);
return applyPredicate(p, v);
case M.$or:
return opseq('false', ' || ', ... p[0].map(pp => walk(v, pp)));
case M.$and:

View File

@ -21,6 +21,7 @@ export const $rec = Symbol.for("rec");
export const $ref = Symbol.for("ref");
export const $schema = Symbol.for("schema");
export const $setof = Symbol.for("setof");
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");
@ -69,7 +70,7 @@ export type Pattern = (
> |
_.Record<(typeof $pointer), []> |
_.Record<(typeof $lit), [_.Value]> |
_.Record<(typeof $ref), [symbol]> |
Ref |
_.Record<(typeof $or), [Array<Pattern>]> |
_.Record<(typeof $and), [Array<Pattern>]> |
_.Record<(typeof $rec), [Pattern, Pattern]> |
@ -82,6 +83,14 @@ export type Pattern = (
export type NamedPattern = (_.Record<(typeof $named), [symbol, Pattern]> | Pattern);
export const Ref = _.Record.makeConstructor<{"module": ModuleRef, "name": symbol}>()($ref, ["module","name"]);
export type Ref = _.Record<(typeof $ref), [ModuleRef, symbol]>;
export type ModuleRef = ((typeof $thisModule) | ModulePath);
export type ModulePath = Array<symbol>;
export function isSchema(v: any): v is Schema {
let _tmp0, _tmp1: any;
@ -136,11 +145,7 @@ export function isPattern(v: any): v is Pattern {
) ||
(_.Record.isRecord(v) && v.label === $pointer && ((v.length === 0))) ||
(_.Record.isRecord(v) && v.label === $lit && ((v.length === 1) && true)) ||
(
_.Record.isRecord(v) &&
v.label === $ref &&
((v.length === 1) && typeof v[0] === 'symbol')
) ||
isRef(v) ||
(
_.Record.isRecord(v) &&
v.label === $or &&
@ -243,4 +248,28 @@ export function asNamedPattern(v: any): NamedPattern {
if (!isNamedPattern(v)) {throw new TypeError("NamedPattern");} else {return v;};
}
export function isRef(v: any): v is Ref {
return (
_.Record.isRecord(v) &&
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("Ref");} else {return v;};}
export function isModuleRef(v: any): v is ModuleRef {return (v === $thisModule || isModulePath(v));}
export function asModuleRef(v: any): ModuleRef {if (!isModuleRef(v)) {throw new TypeError("ModuleRef");} else {return v;};}
export function isModulePath(v: any): v is ModulePath {
return (
_.Array.isArray(v) &&
!_.Record.isRecord(v) &&
(v.length >= 0) &&
v.slice(0).every(v => (typeof v === 'symbol'))
);
}
export function asModulePath(v: any): ModulePath {if (!isModulePath(v)) {throw new TypeError("ModulePath");} else {return v;};}

View File

@ -18,7 +18,10 @@ export function validator(env: Environment, p: Pattern): (v: Value<any>) => bool
case M.$lit:
return is(v, p[0]);
case M.$ref:
return walk(lookup(env, p[0]), v);
return lookup(p, env,
(p) => walk(p, v),
(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

@ -1,4 +1,8 @@
import { Value, preserves } from '@preserves/core';
import { Value, preserves, is } from '@preserves/core';
import { ModulePath, Ref, Schema, Pattern, $thisModule, $definitions } from './gen/schema';
import { BASE } from './base';
export * from './gen/schema';
export type Input = Value<never>;
@ -16,15 +20,40 @@ export const DOTDOTDOT = Symbol.for('...');
export const EQUALS = Symbol.for('=');
export const ORSYM = Symbol.for('/');
export * from './schemaschema';
import { Schema, Pattern, $definitions } from './schemaschema';
export type SchemaEnvEntry = {
schemaModulePath: ModulePath,
typescriptModulePath: string | null, // null means it's $thisModule in disguise
schema: Schema,
};
export type Environment = Array<Schema>;
export type Environment = Array<SchemaEnvEntry>;
export function lookup(env: Environment, name: symbol): Pattern {
for (const s of env) {
const p = Schema._.details(s).get($definitions).get(name);
if (p !== void 0) return p;
export function lookup<R>(name: Ref,
env: Environment,
kLocal: (p: Pattern) => R,
kBase: (p: Pattern) => R,
kOther: (mod: string, modPath: string, p: Pattern) => 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);
}
}
}
}
throw new Error(preserves`Schema: unbound name ${name}`);
if (Ref._.module(name) === $thisModule) {
const p = Schema._.details(BASE).get($definitions).get(Ref._.name(name));
if (p !== void 0) return kBase(p);
}
throw new Error(preserves`Undefined reference: ${name}`);
}

View File

@ -115,7 +115,15 @@ function parseBase(name: symbol, body: Array<Input>): Pattern {
const s = item.description;
if (s === void 0) complain();
if (s[0] === '=') return Record(M.$lit, [Symbol.for(s.slice(1))]);
return Record(M.$ref, [item]);
const pieces = s.split('.');
if (pieces.length === 1) {
return Record(M.$ref, [M.$thisModule, item]);
} else {
return Record(M.$ref, [
pieces.slice(0, pieces.length - 1).map(Symbol.for),
Symbol.for(pieces[pieces.length - 1])
]);
}
} else if (Record.isRecord<Input, Tuple<Input>, never>(item)) {
const label = item.label;
if (Record.isRecord<Input, [], never>(label)) {

View File

@ -533,6 +533,14 @@
dependencies:
"@babel/types" "^7.3.0"
"@types/glob@^7.1.3":
version "7.1.3"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==
dependencies:
"@types/minimatch" "*"
"@types/node" "*"
"@types/graceful-fs@^4.1.2":
version "4.1.5"
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@ -567,6 +575,11 @@
jest-diff "^26.0.0"
pretty-format "^26.0.0"
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node@*":
version "14.14.33"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.33.tgz#9e4f8c64345522e4e8ce77b334a8aaa64e2b6c78"
@ -609,6 +622,13 @@
dependencies:
"@types/yargs-parser" "*"
"@types/yargs@^16.0.0":
version "16.0.0"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.0.tgz#0e033b23452da5d61b6c44747612cb80ac528751"
integrity sha512-2nN6AGeMwe8+O6nO9ytQfbMQOJy65oi1yK2y/9oReR08DaXSGtMsrLyCM1ooKqfICpCx4oITaR4LkOmdzz41Ww==
dependencies:
"@types/yargs-parser" "*"
abab@^2.0.3, abab@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
@ -1046,6 +1066,15 @@ cliui@^6.0.0:
strip-ansi "^6.0.0"
wrap-ansi "^6.2.0"
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
dependencies:
string-width "^4.2.0"
strip-ansi "^6.0.0"
wrap-ansi "^7.0.0"
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@ -1587,7 +1616,7 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-caller-file@^2.0.1:
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
@ -1635,7 +1664,7 @@ glob-parent@~5.1.0:
dependencies:
is-glob "^4.0.1"
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@ -4017,6 +4046,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@ -4057,6 +4095,11 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==
y18n@^5.0.5:
version "5.0.5"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.5.tgz#8769ec08d03b1ea2df2500acef561743bbb9ab18"
integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==
yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
@ -4075,6 +4118,11 @@ yargs-parser@^18.1.2:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs-parser@^20.2.2:
version "20.2.7"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
yargs@^15.4.1:
version "15.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
@ -4092,6 +4140,19 @@ yargs@^15.4.1:
y18n "^4.0.0"
yargs-parser "^18.1.2"
yargs@^16.2.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"
get-caller-file "^2.0.5"
require-directory "^2.1.1"
string-width "^4.2.0"
y18n "^5.0.5"
yargs-parser "^20.2.2"
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"

View File

@ -20,8 +20,8 @@ Pattern = <<or> [
; =symbol, <<lit> any>, or plain non-symbol atom
<lit @value any>
; symbol
<ref @name symbol>
; symbol, symbol.symbol, symbol.symbol.symbol, ...
Ref
; Pattern / Pattern / ...
; <<or> [Pattern Pattern ...]>
@ -55,3 +55,7 @@ Pattern = <<or> [
]>.
NamedPattern = <named @name symbol @pattern Pattern> / Pattern .
Ref = <ref @module ModuleRef @name symbol>.
ModuleRef = =thisModule / ModulePath .
ModulePath = [symbol ...].