More
This commit is contained in:
parent
62bab41bed
commit
447380218e
|
@ -2,14 +2,14 @@ import { Record, KeyedDictionary } from '..';
|
||||||
import { AtomKind, Pattern, Schema } from './meta';
|
import { AtomKind, Pattern, Schema } from './meta';
|
||||||
import * as M from './meta';
|
import * as M from './meta';
|
||||||
|
|
||||||
export const BASE: Schema = Record(M.SCHEMA, [1, new KeyedDictionary<symbol, Pattern, never>([
|
export const BASE: Schema = Record(M.___schema, [1, new KeyedDictionary<symbol, Pattern, never>([
|
||||||
[Symbol.for('any'), Record(M.AND, [[]])],
|
[Symbol.for('any'), Record(M.___and, [[]])],
|
||||||
[Symbol.for('bool'), Record(M.ATOM, [M.BOOLEAN as AtomKind])],
|
[Symbol.for('bool'), Record(M.___atom, [M.___Boolean as AtomKind])],
|
||||||
[Symbol.for('float'), Record(M.ATOM, [M.FLOAT as AtomKind])],
|
[Symbol.for('float'), Record(M.___atom, [M.___Float as AtomKind])],
|
||||||
[Symbol.for('double'), Record(M.ATOM, [M.DOUBLE as AtomKind])],
|
[Symbol.for('double'), Record(M.___atom, [M.___Double as AtomKind])],
|
||||||
[Symbol.for('int'), Record(M.ATOM, [M.SIGNEDINTEGER as AtomKind])],
|
[Symbol.for('int'), Record(M.___atom, [M.___SignedInteger as AtomKind])],
|
||||||
[Symbol.for('string'), Record(M.ATOM, [M.STRING as AtomKind])],
|
[Symbol.for('string'), Record(M.___atom, [M.___String as AtomKind])],
|
||||||
[Symbol.for('bytes'), Record(M.ATOM, [M.BYTESTRING as AtomKind])],
|
[Symbol.for('bytes'), Record(M.___atom, [M.___ByteString as AtomKind])],
|
||||||
[Symbol.for('symbol'), Record(M.ATOM, [M.SYMBOL as AtomKind])],
|
[Symbol.for('symbol'), Record(M.___atom, [M.___Symbol as AtomKind])],
|
||||||
[Symbol.for('ref'), Record(M.POINTER, [])],
|
[Symbol.for('ref'), Record(M.___pointer, [])],
|
||||||
])]);
|
])]);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Pattern, NamedPattern, Schema, Input } from "./meta";
|
import { Pattern, NamedPattern, Schema, Input } from "./meta";
|
||||||
import * as M from './meta';
|
import * as M from './meta';
|
||||||
import { Annotated, Bytes, Dictionary, Fold, fold, preserves, Record, Tuple, Value } from "..";
|
import { Annotated, Bytes, Dictionary, Fold, fold, preserves, Record, Tuple, Value } from "..";
|
||||||
import { Formatter, parens, seq, Item, opseq, block, commas, brackets, anglebrackets } from "./block";
|
import { Formatter, parens, seq, Item, opseq, block, commas, brackets, anglebrackets, braces } from "./block";
|
||||||
|
|
||||||
function fnblock(... items: Item[]): Item {
|
function fnblock(... items: Item[]): Item {
|
||||||
return seq('((() => ', block(... items), ')())');
|
return seq('((() => ', block(... items), ')())');
|
||||||
|
@ -56,7 +56,12 @@ export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesMo
|
||||||
function literal(v: Input): Item {
|
function literal(v: Input): Item {
|
||||||
let varname = literals.get(v);
|
let varname = literals.get(v);
|
||||||
if (varname === void 0) {
|
if (varname === void 0) {
|
||||||
varname = '__lit' + literals.size;
|
const s = '___' + (
|
||||||
|
v.asPreservesText()
|
||||||
|
.replace('_', '__')
|
||||||
|
.replace('*', '_STAR_')
|
||||||
|
);
|
||||||
|
varname = M.isValidToken(s, true) ? s : '__lit' + literals.size;
|
||||||
literals.set(v, varname);
|
literals.set(v, varname);
|
||||||
}
|
}
|
||||||
return varname;
|
return varname;
|
||||||
|
@ -64,19 +69,19 @@ export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesMo
|
||||||
|
|
||||||
function typeFor(p: Pattern): Item {
|
function typeFor(p: Pattern): Item {
|
||||||
switch (p.label) {
|
switch (p.label) {
|
||||||
case M.ATOM:
|
case M.___atom:
|
||||||
switch (p[0]) {
|
switch (p[0]) {
|
||||||
case M.BOOLEAN: return `boolean`;
|
case M.___Boolean: return `boolean`;
|
||||||
case M.FLOAT: return `_.SingleFloat`;
|
case M.___Float: return `_.SingleFloat`;
|
||||||
case M.DOUBLE: return `_.DoubleFloat`;
|
case M.___Double: return `_.DoubleFloat`;
|
||||||
case M.SIGNEDINTEGER: return `number`;
|
case M.___SignedInteger: return `number`;
|
||||||
case M.STRING: return `string`;
|
case M.___String: return `string`;
|
||||||
case M.BYTESTRING: return `_.Bytes`;
|
case M.___ByteString: return `_.Bytes`;
|
||||||
case M.SYMBOL: return `symbol`;
|
case M.___Symbol: return `symbol`;
|
||||||
}
|
}
|
||||||
case M.LIT:
|
case M.___lit:
|
||||||
return `(typeof ${literal(p[0])})`;
|
return `(typeof ${literal(p[0])})`;
|
||||||
case M.REF:
|
case M.___ref:
|
||||||
return environmentWith(
|
return environmentWith(
|
||||||
p[0],
|
p[0],
|
||||||
() => p[0].description!,
|
() => p[0].description!,
|
||||||
|
@ -87,31 +92,35 @@ export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesMo
|
||||||
return `${e.moduleName}.${p[0].description!}`;
|
return `${e.moduleName}.${p[0].description!}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
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('_.Value', ' & ', ... p[0].map(pp => typeFor(pp)));
|
||||||
case M.POINTER:
|
case M.___pointer:
|
||||||
return `any`; // TODO: what to do here?
|
return `any`; // TODO: what to do here?
|
||||||
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])));
|
||||||
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.TUPLESTAR:
|
case M.___tuple_STAR_:
|
||||||
if (p[0].length === 0) {
|
if (p[0].length === 0) {
|
||||||
return seq('Array<', typeFor(unname(p[1])), '>');
|
return seq('Array<', typeFor(unname(p[1])), '>');
|
||||||
} else {
|
} else {
|
||||||
return brackets(... p[0].map(pp => typeFor(unname(pp))),
|
return brackets(... p[0].map(pp => typeFor(unname(pp))),
|
||||||
seq('... Array<', typeFor(unname(p[1])), '>'));
|
seq('... Array<', typeFor(unname(p[1])), '>'));
|
||||||
}
|
}
|
||||||
case M.SETOF:
|
case M.___setof:
|
||||||
return seq('_.KeyedSet<', typeFor(p[0]), '>');
|
return seq('_.KeyedSet<', typeFor(p[0]), '>');
|
||||||
case M.DICTOF:
|
case M.___dictof:
|
||||||
return seq('_.KeyedDictionary', anglebrackets(typeFor(p[0]), typeFor(p[1])));
|
return seq('_.KeyedDictionary', anglebrackets(typeFor(p[0]), typeFor(p[1])));
|
||||||
case M.DICT:
|
case M.___dict:
|
||||||
return parens(seq('_.Dictionary<_.Value> & ', block(
|
return parens(seq(
|
||||||
... Array.from(p[0]).map(([k, vp]) =>
|
block(
|
||||||
seq(`get(k: typeof ${literal(k)}): `, typeFor(vp))))));
|
... Array.from(p[0]).map(([k, vp]) =>
|
||||||
|
seq(`get(k: typeof ${literal(k)}): `, typeFor(vp))),
|
||||||
|
... Array.from(p[0]).map(([k, _vp]) =>
|
||||||
|
seq(`has(k: typeof ${literal(k)}): true`))),
|
||||||
|
' & _.Dictionary<_.Value>'));
|
||||||
default:
|
default:
|
||||||
((_p: never) => {})(p);
|
((_p: never) => {})(p);
|
||||||
throw new Error("Unreachable");
|
throw new Error("Unreachable");
|
||||||
|
@ -120,38 +129,38 @@ export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesMo
|
||||||
|
|
||||||
function walk(v: string, p: Pattern, recordOkAsTuple = false): Item {
|
function walk(v: string, p: Pattern, recordOkAsTuple = false): Item {
|
||||||
switch (p.label) {
|
switch (p.label) {
|
||||||
case M.ATOM:
|
case M.___atom:
|
||||||
switch (p[0]) {
|
switch (p[0]) {
|
||||||
case M.BOOLEAN: return `typeof ${v} === 'boolean'`;
|
case M.___Boolean: return `typeof ${v} === 'boolean'`;
|
||||||
case M.FLOAT: return `_.Float.isSingle(${v})`;
|
case M.___Float: return `_.Float.isSingle(${v})`;
|
||||||
case M.DOUBLE: return `_.Float.isDouble(${v})`;
|
case M.___Double: return `_.Float.isDouble(${v})`;
|
||||||
case M.SIGNEDINTEGER: return `typeof ${v} === 'number'`;
|
case M.___SignedInteger: return `typeof ${v} === 'number'`;
|
||||||
case M.STRING: return `typeof ${v} === 'string'`;
|
case M.___String: return `typeof ${v} === 'string'`;
|
||||||
case M.BYTESTRING: return `_.Bytes.isBytes(${v})`;
|
case M.___ByteString: return `_.Bytes.isBytes(${v})`;
|
||||||
case M.SYMBOL: return `typeof ${v} === 'symbol'`;
|
case M.___Symbol: return `typeof ${v} === 'symbol'`;
|
||||||
}
|
}
|
||||||
case M.LIT:
|
case M.___lit:
|
||||||
return `_.is(${v}, ${literal(p[0])})`;
|
return `_.is(${v}, ${literal(p[0])})`;
|
||||||
case M.REF:
|
case M.___ref:
|
||||||
return applyPredicate(p[0], v);
|
return applyPredicate(p[0], v);
|
||||||
case M.OR:
|
case M.___or:
|
||||||
return opseq('false', ' || ', ... p[0].map(pp => walk(v, pp)));
|
return opseq('false', ' || ', ... p[0].map(pp => walk(v, pp)));
|
||||||
case M.AND:
|
case M.___and:
|
||||||
return opseq('true', ' && ', ... p[0].map(pp => walk(v, pp)));
|
return opseq('true', ' && ', ... p[0].map(pp => walk(v, pp)));
|
||||||
case M.POINTER:
|
case M.___pointer:
|
||||||
return `_.isPointer(${v})`;
|
return `_.isPointer(${v})`;
|
||||||
case M.REC:
|
case M.___rec:
|
||||||
return opseq('true', ' && ',
|
return opseq('true', ' && ',
|
||||||
`_.Record.isRecord(${v})`,
|
`_.Record.isRecord(${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(${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.TUPLESTAR:
|
case M.___tuple_STAR_:
|
||||||
return opseq('true', ' && ',
|
return opseq('true', ' && ',
|
||||||
... (recordOkAsTuple ? []
|
... (recordOkAsTuple ? []
|
||||||
: [`_.Array.isArray(${v})`, `!_.Record.isRecord(${v})`]),
|
: [`_.Array.isArray(${v})`, `!_.Record.isRecord(${v})`]),
|
||||||
|
@ -161,14 +170,14 @@ export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesMo
|
||||||
parens(walk('v', unname(p[1]))),
|
parens(walk('v', unname(p[1]))),
|
||||||
`)`),
|
`)`),
|
||||||
... 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(${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(${v})`,
|
||||||
fnblock(
|
fnblock(
|
||||||
|
@ -176,7 +185,7 @@ export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesMo
|
||||||
seq('if (!(', walk('e[0]', p[0]), ')) return false'),
|
seq('if (!(', walk('e[0]', p[0]), ')) return false'),
|
||||||
seq('if (!(', walk('e[1]', p[1]), ')) return false'))),
|
seq('if (!(', walk('e[1]', p[1]), ')) return false'))),
|
||||||
seq('return true')));
|
seq('return true')));
|
||||||
case M.DICT:
|
case M.___dict:
|
||||||
return opseq('true', ' && ',
|
return opseq('true', ' && ',
|
||||||
`_.Dictionary.isDictionary(${v})`,
|
`_.Dictionary.isDictionary(${v})`,
|
||||||
... Array.from(p[0]).map(([k, vp]) => {
|
... Array.from(p[0]).map(([k, vp]) => {
|
||||||
|
@ -192,15 +201,33 @@ export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesMo
|
||||||
}
|
}
|
||||||
|
|
||||||
function unname(p: NamedPattern): Pattern {
|
function unname(p: NamedPattern): Pattern {
|
||||||
return (p.label === M.NAMED) ? p[1] : p;
|
return (p.label === M.___named) ? p[1] : p;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fieldName(np: NamedPattern, index: number): string {
|
||||||
|
return (np.label === M.___named) ? np[0].description! : `_field${index}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fieldEntry(np: NamedPattern, index: number): Item {
|
||||||
|
return seq(JSON.stringify(fieldName(np, index)), ': ', typeFor(unname(np)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [name0, pattern] of Schema._.definitions(schema)) {
|
for (const [name0, pattern] of Schema._.definitions(schema)) {
|
||||||
const name = name0 as symbol;
|
const name = name0 as symbol;
|
||||||
temps = [];
|
temps = [];
|
||||||
const recognizer = walk('v', pattern);
|
const recognizer = walk('v', pattern);
|
||||||
|
if (pattern.label === M.___rec &&
|
||||||
|
pattern[0].label === M.___lit &&
|
||||||
|
pattern[1].label === M.___tuple)
|
||||||
|
{
|
||||||
|
types.push(
|
||||||
|
seq(`export const ${name.description!} = _.Record.makeConstructor<`,
|
||||||
|
braces(... pattern[1][0].map(fieldEntry)),
|
||||||
|
`>()(${literal(pattern[0][0])}, `,
|
||||||
|
JSON.stringify(pattern[1][0].map(fieldName)), `);`));
|
||||||
|
}
|
||||||
types.push(
|
types.push(
|
||||||
seq(`export type ${name.description!} = `, typeFor(pattern)));
|
seq(`export type ${name.description!} = `, typeFor(pattern), `;`));
|
||||||
predicates.push(
|
predicates.push(
|
||||||
seq('export function ', `is${name.description!}`,
|
seq('export function ', `is${name.description!}`,
|
||||||
'(v: any): v is ', name.description!, ' ',
|
'(v: any): v is ', name.description!, ' ',
|
||||||
|
@ -213,14 +240,17 @@ export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesMo
|
||||||
f.write(`import * as _ from ${JSON.stringify(preservesModule)};\n`);
|
f.write(`import * as _ from ${JSON.stringify(preservesModule)};\n`);
|
||||||
f.newline();
|
f.newline();
|
||||||
|
|
||||||
for (const [lit, varname] of literals) {
|
const sortedLiterals = Array.from(literals);
|
||||||
f.write(seq(`const ${varname} = `, sourceCodeFor(lit), `;\n`));
|
sortedLiterals.sort((a, b) => a[1] < b[1] ? -1 : a[1] === b[1] ? 0 : 1);
|
||||||
|
for (const [lit, varname] of sortedLiterals) {
|
||||||
|
f.write(seq(`export const ${varname} = `, sourceCodeFor(lit), `;\n`));
|
||||||
}
|
}
|
||||||
f.newline();
|
f.newline();
|
||||||
|
|
||||||
types.forEach(t => {
|
types.forEach(t => {
|
||||||
f.write(t);
|
f.write(t);
|
||||||
f.newline();
|
f.newline();
|
||||||
|
f.newline();
|
||||||
});
|
});
|
||||||
f.newline();
|
f.newline();
|
||||||
|
|
||||||
|
@ -275,11 +305,10 @@ export function sourceCodeFor(v: Value<any>): Item {
|
||||||
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { readSchema } from "./reader";
|
import { readSchema } from "./reader";
|
||||||
import { Reader } from "../reader";
|
|
||||||
import { BASE } from "./base";
|
import { BASE } from "./base";
|
||||||
function main() {
|
function main() {
|
||||||
const src = fs.readFileSync(__dirname + '/../../../../schema/schema.txt', 'utf-8');
|
const src = fs.readFileSync(__dirname + '/../../../../schema/schema.txt', 'utf-8');
|
||||||
const sch = readSchema(new Reader<never>(src, { includeAnnotations: true }).readToEnd());
|
const sch = readSchema(src);
|
||||||
console.log(compile([{ moduleName: 'BASE', modulePath: 'BASE', schema: BASE, inline: true }], sch, '..'));
|
console.log(compile([{ moduleName: 'BASE', modulePath: 'BASE', schema: BASE, inline: true }], sch, '..'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,37 +5,37 @@ import { Value, Float, Bytes, is, isPointer, Record, Dictionary, Set } from ".."
|
||||||
export function validator(env: Environment, p: Pattern): (v: Value<any>) => boolean {
|
export function validator(env: Environment, p: Pattern): (v: Value<any>) => boolean {
|
||||||
function walk(p: Pattern, v: Value<any>, recordOkAsTuple = false): boolean {
|
function walk(p: Pattern, v: Value<any>, recordOkAsTuple = false): boolean {
|
||||||
switch (p.label) {
|
switch (p.label) {
|
||||||
case M.ATOM:
|
case M.___atom:
|
||||||
switch (p[0]) {
|
switch (p[0]) {
|
||||||
case M.BOOLEAN: return typeof v === 'boolean';
|
case M.___Boolean: return typeof v === 'boolean';
|
||||||
case M.FLOAT: return Float.isSingle(v);
|
case M.___Float: return Float.isSingle(v);
|
||||||
case M.DOUBLE: return Float.isDouble(v);
|
case M.___Double: return Float.isDouble(v);
|
||||||
case M.SIGNEDINTEGER: return typeof v === 'number';
|
case M.___SignedInteger: return typeof v === 'number';
|
||||||
case M.STRING: return typeof v === 'string';
|
case M.___String: return typeof v === 'string';
|
||||||
case M.BYTESTRING: return Bytes.isBytes(v);
|
case M.___ByteString: return Bytes.isBytes(v);
|
||||||
case M.SYMBOL: return typeof v === 'symbol';
|
case M.___Symbol: return typeof v === 'symbol';
|
||||||
}
|
}
|
||||||
case M.LIT:
|
case M.___lit:
|
||||||
return is(v, p[0]);
|
return is(v, p[0]);
|
||||||
case M.REF:
|
case M.___ref:
|
||||||
return walk(lookup(env, p[0]), v);
|
return walk(lookup(env, p[0]), 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;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
case M.AND:
|
case M.___and:
|
||||||
for (const pp of p[0]) {
|
for (const pp of p[0]) {
|
||||||
if (!walk(pp, v)) return false;
|
if (!walk(pp, v)) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case M.POINTER:
|
case M.___pointer:
|
||||||
return isPointer(v);
|
return isPointer(v);
|
||||||
case M.REC:
|
case M.___rec:
|
||||||
if (!Record.isRecord(v)) return false;
|
if (!Record.isRecord(v)) return false;
|
||||||
if (!walk(p[0], v.label)) return false;
|
if (!walk(p[0], v.label)) return false;
|
||||||
return walk(p[1], v, true);
|
return walk(p[1], v, true);
|
||||||
case M.TUPLE:
|
case M.___tuple:
|
||||||
if (!Array.isArray(v)) return false;
|
if (!Array.isArray(v)) return false;
|
||||||
if (!recordOkAsTuple && Record.isRecord(v)) return false;
|
if (!recordOkAsTuple && Record.isRecord(v)) return false;
|
||||||
if (p[0].length !== v.length) return false;
|
if (p[0].length !== v.length) return false;
|
||||||
|
@ -43,7 +43,7 @@ export function validator(env: Environment, p: Pattern): (v: Value<any>) => bool
|
||||||
if (!walknamed(p[0][i], v[i])) return false;
|
if (!walknamed(p[0][i], v[i])) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case M.TUPLESTAR:
|
case M.___tuple_STAR_:
|
||||||
if (!Array.isArray(v)) return false;
|
if (!Array.isArray(v)) return false;
|
||||||
if (!recordOkAsTuple && Record.isRecord(v)) return false;
|
if (!recordOkAsTuple && Record.isRecord(v)) return false;
|
||||||
if (p[0].length > v.length) return false;
|
if (p[0].length > v.length) return false;
|
||||||
|
@ -54,20 +54,20 @@ export function validator(env: Environment, p: Pattern): (v: Value<any>) => bool
|
||||||
if (!walknamed(p[1], v[i])) return false;
|
if (!walknamed(p[1], v[i])) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case M.SETOF:
|
case M.___setof:
|
||||||
if (!Set.isSet(v)) return false;
|
if (!Set.isSet(v)) return false;
|
||||||
for (const vv of v) {
|
for (const vv of v) {
|
||||||
if (!walk(p[0], vv)) return false;
|
if (!walk(p[0], vv)) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case M.DICTOF:
|
case M.___dictof:
|
||||||
if (!Dictionary.isDictionary<Value<any>>(v)) return false;
|
if (!Dictionary.isDictionary<Value<any>>(v)) return false;
|
||||||
for (const e of v) {
|
for (const e of v) {
|
||||||
if (!walk(p[0], e[0])) return false;
|
if (!walk(p[0], e[0])) return false;
|
||||||
if (!walk(p[1], e[1])) return false;
|
if (!walk(p[1], e[1])) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
case M.DICT:
|
case M.___dict:
|
||||||
if (!Dictionary.isDictionary<Value<any>>(v)) return false;
|
if (!Dictionary.isDictionary<Value<any>>(v)) return false;
|
||||||
for (const e of p[0]) {
|
for (const e of p[0]) {
|
||||||
const vv = v.get(e[0]);
|
const vv = v.get(e[0]);
|
||||||
|
@ -82,7 +82,7 @@ export function validator(env: Environment, p: Pattern): (v: Value<any>) => bool
|
||||||
}
|
}
|
||||||
|
|
||||||
function walknamed(p: NamedPattern, v: Value<any>): boolean {
|
function walknamed(p: NamedPattern, v: Value<any>): boolean {
|
||||||
return (p.label === M.NAMED) ? walk(p[1], v) : walk(p, v);
|
return (p.label === M.___named) ? walk(p[1], v) : walk(p, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
return v => walk(p, v);
|
return v => walk(p, v);
|
||||||
|
|
|
@ -2,33 +2,41 @@ import { Dictionary, KeyedDictionary, Record, Value, preserves } from '..';
|
||||||
|
|
||||||
export type Input = Value<never>;
|
export type Input = Value<never>;
|
||||||
|
|
||||||
export const AND = Symbol.for('and');
|
export function isValidToken(s: string, allowLeadingUnderscore = false): boolean {
|
||||||
|
if (allowLeadingUnderscore) {
|
||||||
|
return /^[a-zA-Z_][a-zA-Z_0-9]*$/.test(s);
|
||||||
|
} else {
|
||||||
|
return /^[a-zA-Z][a-zA-Z_0-9]*$/.test(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const ANDSYM = Symbol.for('&');
|
export const ANDSYM = Symbol.for('&');
|
||||||
export const ATOM = Symbol.for('atom');
|
|
||||||
export const BOOLEAN = Symbol.for('Boolean');
|
|
||||||
export const BYTESTRING = Symbol.for('ByteString');
|
|
||||||
export const DICT = Symbol.for('dict');
|
|
||||||
export const DICTOF = Symbol.for('dictof');
|
|
||||||
export const DOT = Symbol.for('.');
|
export const DOT = Symbol.for('.');
|
||||||
export const DOTDOTDOT = Symbol.for('...');
|
export const DOTDOTDOT = Symbol.for('...');
|
||||||
export const DOUBLE = Symbol.for('Double');
|
|
||||||
export const EQUALS = Symbol.for('=');
|
export const EQUALS = Symbol.for('=');
|
||||||
export const FLOAT = Symbol.for('Float');
|
|
||||||
export const LIT = Symbol.for('lit');
|
|
||||||
export const NAMED = Symbol.for('named');
|
|
||||||
export const OR = Symbol.for('or');
|
|
||||||
export const ORSYM = Symbol.for('/');
|
export const ORSYM = Symbol.for('/');
|
||||||
export const POINTER = Symbol.for('pointer');
|
export const ___Boolean = Symbol.for('Boolean');
|
||||||
export const REC = Symbol.for('rec');
|
export const ___ByteString = Symbol.for('ByteString');
|
||||||
export const REF = Symbol.for('ref');
|
export const ___Double = Symbol.for('Double');
|
||||||
export const SCHEMA = Symbol.for('schema');
|
export const ___Float = Symbol.for('Float');
|
||||||
export const SETOF = Symbol.for('setof');
|
export const ___SignedInteger = Symbol.for('SignedInteger');
|
||||||
export const SIGNEDINTEGER = Symbol.for('SignedInteger');
|
export const ___String = Symbol.for('String');
|
||||||
export const STRING = Symbol.for('String');
|
export const ___Symbol = Symbol.for('Symbol');
|
||||||
export const SYMBOL = Symbol.for('Symbol');
|
export const ___and = Symbol.for('and');
|
||||||
export const TUPLE = Symbol.for('tuple');
|
export const ___atom = Symbol.for('atom');
|
||||||
export const TUPLESTAR = Symbol.for('tuple*');
|
export const ___dict = Symbol.for('dict');
|
||||||
export const VERSION = Symbol.for('version');
|
export const ___dictof = Symbol.for('dictof');
|
||||||
|
export const ___lit = Symbol.for('lit');
|
||||||
|
export const ___named = Symbol.for('named');
|
||||||
|
export const ___or = Symbol.for('or');
|
||||||
|
export const ___pointer = Symbol.for('pointer');
|
||||||
|
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 ___tuple = Symbol.for('tuple');
|
||||||
|
export const ___tuple_STAR_ = Symbol.for('tuple*');
|
||||||
|
export const ___version = Symbol.for('version');
|
||||||
|
|
||||||
export type Environment = Array<Schema>;
|
export type Environment = Array<Schema>;
|
||||||
|
|
||||||
|
@ -39,29 +47,29 @@ export const Schema = Record.makeConstructor<{
|
||||||
export type Schema = ReturnType<typeof Schema>;
|
export type Schema = ReturnType<typeof Schema>;
|
||||||
|
|
||||||
export type AtomKind =
|
export type AtomKind =
|
||||||
| typeof BOOLEAN
|
| typeof ___Boolean
|
||||||
| typeof FLOAT
|
| typeof ___Float
|
||||||
| typeof DOUBLE
|
| typeof ___Double
|
||||||
| typeof SIGNEDINTEGER
|
| typeof ___SignedInteger
|
||||||
| typeof STRING
|
| typeof ___String
|
||||||
| typeof BYTESTRING
|
| typeof ___ByteString
|
||||||
| typeof SYMBOL;
|
| typeof ___Symbol;
|
||||||
|
|
||||||
export type Pattern =
|
export type Pattern =
|
||||||
| Record<typeof ATOM, [AtomKind], never>
|
| Record<typeof ___atom, [AtomKind], never>
|
||||||
| Record<typeof LIT, [Input], never>
|
| Record<typeof ___lit, [Input], never>
|
||||||
| Record<typeof REF, [symbol], never>
|
| Record<typeof ___ref, [symbol], never>
|
||||||
| Record<typeof OR, [Array<Pattern>], never>
|
| Record<typeof ___or, [Array<Pattern>], never>
|
||||||
| Record<typeof AND, [Array<Pattern>], never>
|
| Record<typeof ___and, [Array<Pattern>], never>
|
||||||
| Record<typeof POINTER, [], never>
|
| Record<typeof ___pointer, [], never>
|
||||||
| Record<typeof REC, [Pattern, Pattern], never>
|
| Record<typeof ___rec, [Pattern, Pattern], never>
|
||||||
| Record<typeof TUPLE, [Array<NamedPattern>], never>
|
| Record<typeof ___tuple, [Array<NamedPattern>], never>
|
||||||
| Record<typeof TUPLESTAR, [Array<NamedPattern>, NamedPattern], never>
|
| Record<typeof ___tuple_STAR_, [Array<NamedPattern>, NamedPattern], never>
|
||||||
| Record<typeof SETOF, [Pattern], never>
|
| Record<typeof ___setof, [Pattern], never>
|
||||||
| Record<typeof DICTOF, [Pattern, Pattern], never>
|
| Record<typeof ___dictof, [Pattern, Pattern], never>
|
||||||
| Record<typeof DICT, [Dictionary<Pattern, never>], never>;
|
| Record<typeof ___dict, [Dictionary<Pattern, never>], never>;
|
||||||
|
|
||||||
export type NamedPattern = Record<typeof NAMED, [symbol, Pattern], never> | Pattern;
|
export type NamedPattern = Record<typeof ___named, [symbol, Pattern], never> | Pattern;
|
||||||
|
|
||||||
export function lookup(env: Environment, name: symbol): Pattern {
|
export function lookup(env: Environment, name: symbol): Pattern {
|
||||||
for (const s of env) {
|
for (const s of env) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Annotated, Dictionary, is, KeyedDictionary, peel, preserves, Record, st
|
||||||
|
|
||||||
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 { Reader } from '../reader';
|
||||||
|
|
||||||
function splitBy<T>(items: Array<T>, separator: T): Array<Array<T>> {
|
function splitBy<T>(items: Array<T>, separator: T): Array<Array<T>> {
|
||||||
const groups: Array<Array<T>> = [];
|
const groups: Array<Array<T>> = [];
|
||||||
|
@ -31,7 +32,12 @@ function invalidPattern(name: symbol, item: Input): never {
|
||||||
throw new Error(preserves`Invalid pattern in ${name}: ${item}`);
|
throw new Error(preserves`Invalid pattern in ${name}: ${item}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readSchema(toplevelTokens: Array<Input>): Schema {
|
export function readSchema(source: string): Schema {
|
||||||
|
const toplevelTokens = new Reader<never>(source, { includeAnnotations: true }).readToEnd();
|
||||||
|
return parseSchema(toplevelTokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
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: number | undefined = void 0;
|
let version: number | undefined = void 0;
|
||||||
let definitions = new KeyedDictionary<symbol, Pattern, never>();
|
let definitions = new KeyedDictionary<symbol, Pattern, never>();
|
||||||
|
@ -42,11 +48,14 @@ export function readSchema(toplevelTokens: Array<Input>): Schema {
|
||||||
} else if (clause.length >= 2 && is(clause[1], M.EQUALS)) {
|
} else if (clause.length >= 2 && is(clause[1], M.EQUALS)) {
|
||||||
if (typeof clause[0] !== 'symbol') invalidClause(clause);
|
if (typeof clause[0] !== 'symbol') invalidClause(clause);
|
||||||
const name = clause[0];
|
const name = clause[0];
|
||||||
|
if (!M.isValidToken(name.description!)) {
|
||||||
|
throw new Error(preserves`Invalid definition name: ${name}`);
|
||||||
|
}
|
||||||
if (definitions.has(name)) {
|
if (definitions.has(name)) {
|
||||||
throw new Error(preserves`Duplicate definition: ${clause}`);
|
throw new Error(preserves`Duplicate definition: ${clause}`);
|
||||||
}
|
}
|
||||||
definitions.set(name, parseDefinition(name, clause.slice(2).map(peel)));
|
definitions.set(name, parseDefinition(name, clause.slice(2).map(peel)));
|
||||||
} else if (clause.length === 2 && is(clause[0], M.VERSION)) {
|
} else if (clause.length === 2 && is(clause[0], M.___version)) {
|
||||||
if (typeof clause[1] !== 'number') invalidClause(clause);
|
if (typeof clause[1] !== 'number') invalidClause(clause);
|
||||||
version = clause[1];
|
version = clause[1];
|
||||||
} else {
|
} else {
|
||||||
|
@ -59,7 +68,7 @@ export function readSchema(toplevelTokens: Array<Input>): Schema {
|
||||||
if (version !== 1) {
|
if (version !== 1) {
|
||||||
throw new Error("Schema: unsupported version " + version);
|
throw new Error("Schema: unsupported version " + version);
|
||||||
}
|
}
|
||||||
return Record(M.SCHEMA, [version, definitions]);
|
return Record(M.___schema, [version, definitions]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseDefinition(name: symbol, body: Array<Input>): Pattern {
|
function parseDefinition(name: symbol, body: Array<Input>): Pattern {
|
||||||
|
@ -70,8 +79,8 @@ function parseOp(body: Array<Input>, op: Input, k: (p: Array<Input>) => Pattern)
|
||||||
const pieces = splitBy(body, op);
|
const pieces = splitBy(body, op);
|
||||||
if (pieces.length === 1) return k(pieces[0]);
|
if (pieces.length === 1) return k(pieces[0]);
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case M.ORSYM: return Record(M.OR, [pieces.map(k)]);
|
case M.ORSYM: return Record(M.___or, [pieces.map(k)]);
|
||||||
case M.ANDSYM: return Record(M.AND, [pieces.map(k)]);
|
case M.ANDSYM: return Record(M.___and, [pieces.map(k)]);
|
||||||
default: throw new Error("Internal error: unexpected operator");
|
default: throw new Error("Internal error: unexpected operator");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +102,7 @@ function parseBase(name: symbol, body: Array<Input>): Pattern {
|
||||||
const namedwalk = (b: Input): NamedPattern => {
|
const namedwalk = (b: Input): NamedPattern => {
|
||||||
const name = findName(b);
|
const name = findName(b);
|
||||||
if (name === false) return walk(b);
|
if (name === false) return walk(b);
|
||||||
return Record(M.NAMED, [name, walk(b)]);
|
return Record(M.___named, [name, walk(b)]);
|
||||||
};
|
};
|
||||||
const walkitems = (b: Input): Pattern[] => {
|
const walkitems = (b: Input): Pattern[] => {
|
||||||
b = peel(b);
|
b = peel(b);
|
||||||
|
@ -108,56 +117,56 @@ function parseBase(name: symbol, body: Array<Input>): Pattern {
|
||||||
if (typeof item === 'symbol') {
|
if (typeof item === 'symbol') {
|
||||||
const s = item.description;
|
const s = item.description;
|
||||||
if (s === void 0) complain();
|
if (s === void 0) complain();
|
||||||
if (s[0] === '=') return Record(M.LIT, [Symbol.for(s.slice(1))]);
|
if (s[0] === '=') return Record(M.___lit, [Symbol.for(s.slice(1))]);
|
||||||
return Record(M.REF, [item]);
|
return Record(M.___ref, [item]);
|
||||||
} 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)) {
|
||||||
if (label.length !== 0) complain();
|
if (label.length !== 0) complain();
|
||||||
switch (label.label) {
|
switch (label.label) {
|
||||||
case M.LIT:
|
case M.___lit:
|
||||||
if (item.length !== 1) complain();
|
if (item.length !== 1) complain();
|
||||||
return Record(M.LIT, [item[0]]);
|
return Record(M.___lit, [item[0]]);
|
||||||
case M.OR:
|
case M.___or:
|
||||||
if (item.length !== 1) complain();
|
if (item.length !== 1) complain();
|
||||||
return Record(M.OR, [walkitems(item[0])]);
|
return Record(M.___or, [walkitems(item[0])]);
|
||||||
case M.AND:
|
case M.___and:
|
||||||
if (item.length !== 1) complain();
|
if (item.length !== 1) complain();
|
||||||
return Record(M.AND, [walkitems(item[0])]);
|
return Record(M.___and, [walkitems(item[0])]);
|
||||||
case M.REC:
|
case M.___rec:
|
||||||
if (item.length !== 2) complain();
|
if (item.length !== 2) complain();
|
||||||
return Record(M.REC, [walk(item[0]), walk(item[1])]);
|
return Record(M.___rec, [walk(item[0]), walk(item[1])]);
|
||||||
default:
|
default:
|
||||||
complain();
|
complain();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Record(M.REC, [Record(M.LIT, [label]), Record(M.TUPLE, [item.map(namedwalk)])]);
|
return Record(M.___rec, [Record(M.___lit, [label]), Record(M.___tuple, [item.map(namedwalk)])]);
|
||||||
}
|
}
|
||||||
} else if (Array.isArray(item)) {
|
} else if (Array.isArray(item)) {
|
||||||
if (is(item[item.length - 1], M.DOTDOTDOT)) {
|
if (is(item[item.length - 1], M.DOTDOTDOT)) {
|
||||||
if (item.length < 2) complain();
|
if (item.length < 2) complain();
|
||||||
return Record(M.TUPLESTAR, [
|
return Record(M.___tuple_STAR_, [
|
||||||
item.slice(0, item.length - 2).map(namedwalk),
|
item.slice(0, item.length - 2).map(namedwalk),
|
||||||
namedwalk(item[item.length - 2]),
|
namedwalk(item[item.length - 2]),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
return Record(M.TUPLE, [item.map(namedwalk)]);
|
return Record(M.___tuple, [item.map(namedwalk)]);
|
||||||
}
|
}
|
||||||
} else if (Dictionary.isDictionary<Input, never>(item)) {
|
} else if (Dictionary.isDictionary<Input, never>(item)) {
|
||||||
if (item.size === 2 && item.has(M.DOTDOTDOT)) {
|
if (item.size === 2 && item.has(M.DOTDOTDOT)) {
|
||||||
const v = item.clone();
|
const v = item.clone();
|
||||||
v.delete(M.DOTDOTDOT);
|
v.delete(M.DOTDOTDOT);
|
||||||
const [[kp, vp]] = v.entries();
|
const [[kp, vp]] = v.entries();
|
||||||
return Record(M.DICTOF, [walk(kp), walk(vp)]);
|
return Record(M.___dictof, [walk(kp), walk(vp)]);
|
||||||
} else {
|
} else {
|
||||||
return Record(M.DICT, [item.mapEntries<Pattern, Input, never>(([k, vp]) =>
|
return Record(M.___dict, [item.mapEntries<Pattern, Input, never>(([k, vp]) =>
|
||||||
[strip(k), walk(vp)])]);
|
[strip(k), walk(vp)])]);
|
||||||
}
|
}
|
||||||
} else if (Set.isSet<never>(item)) {
|
} else if (Set.isSet<never>(item)) {
|
||||||
if (item.size !== 1) complain();
|
if (item.size !== 1) complain();
|
||||||
const [vp] = item.entries();
|
const [vp] = item.entries();
|
||||||
return Record(M.SETOF, [walk(vp)]);
|
return Record(M.___setof, [walk(vp)]);
|
||||||
} else {
|
} else {
|
||||||
return Record(M.LIT, [strip(item)]);
|
return Record(M.___lit, [strip(item)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
version 1 .
|
version 1 .
|
||||||
|
|
||||||
Schema = <schema {
|
Schema = <schema @details {
|
||||||
version: Version
|
version: Version
|
||||||
definitions: { symbol: Pattern ...:... }
|
definitions: { symbol: Pattern ...:... }
|
||||||
}>.
|
}>.
|
||||||
|
@ -14,6 +14,9 @@ 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]>>
|
||||||
|
|
||||||
|
; matches a pointer in the input
|
||||||
|
<pointer>
|
||||||
|
|
||||||
; =symbol, <<lit> any>, or plain non-symbol atom
|
; =symbol, <<lit> any>, or plain non-symbol atom
|
||||||
<lit @value any>
|
<lit @value any>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue