preserves/implementations/javascript/packages/schema/src/reader.ts

178 lines
6.4 KiB
TypeScript
Raw Normal View History

2021-03-10 22:15:53 +00:00
import { Reader, Annotated, Dictionary, is, peel, preserves, Record, strip, Tuple, Value } from '@preserves/core';
2021-03-09 14:59:40 +00:00
import { Input, NamedPattern, Pattern, Schema } from './meta';
import * as M from './meta';
function splitBy<T>(items: Array<T>, separator: T): Array<Array<T>> {
const groups: Array<Array<T>> = [];
let group: Array<T> = [];
function finish() {
if (group.length > 0) {
groups.push(group);
group = [];
}
}
for (const item of items) {
if (is(item, separator)) {
finish();
} else {
group.push(item);
}
}
finish();
return groups;
}
function invalidClause(clause: Input): never {
throw new Error(preserves`Invalid Schema clause: ${clause}`);
}
function invalidPattern(name: symbol, item: Input): never {
throw new Error(preserves`Invalid pattern in ${name}: ${item}`);
}
2021-03-09 15:45:57 +00:00
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 {
2021-03-09 14:59:40 +00:00
const toplevelClauses = splitBy(peel(toplevelTokens) as Array<Input>, M.DOT);
2021-03-10 22:15:53 +00:00
let version: M.Version | undefined = void 0;
let definitions = new Dictionary<Pattern, never>();
2021-03-09 14:59:40 +00:00
for (const clause0 of toplevelClauses) {
const clause = clause0.map(peel);
if (!Array.isArray(clause)) {
invalidClause(clause);
} else if (clause.length >= 2 && is(clause[1], M.EQUALS)) {
if (typeof clause[0] !== 'symbol') invalidClause(clause);
const name = clause[0];
2021-03-09 15:45:57 +00:00
if (!M.isValidToken(name.description!)) {
throw new Error(preserves`Invalid definition name: ${name}`);
}
2021-03-09 14:59:40 +00:00
if (definitions.has(name)) {
throw new Error(preserves`Duplicate definition: ${clause}`);
}
definitions.set(name, parseDefinition(name, clause.slice(2).map(peel)));
2021-03-10 22:15:53 +00:00
} else if (clause.length === 2 && is(clause[0], M.$version)) {
2021-03-11 08:25:17 +00:00
version = M.asVersion(clause[1]);
2021-03-09 14:59:40 +00:00
} else {
invalidClause(clause);
}
}
if (version === void 0) {
throw new Error("Schema: missing version declaration.");
}
2021-03-10 22:15:53 +00:00
return Record(M.$schema, [new Dictionary<Value>([
[M.$version, version],
[M.$definitions, definitions],
])]);
2021-03-09 14:59:40 +00:00
}
function parseDefinition(name: symbol, body: Array<Input>): Pattern {
return parseOp(body, M.ORSYM, p => parseOp(p, M.ANDSYM, p => parseBase(name, p)));
}
function parseOp(body: Array<Input>, op: Input, k: (p: Array<Input>) => Pattern): Pattern {
const pieces = splitBy(body, op);
if (pieces.length === 1) return k(pieces[0]);
switch (op) {
2021-03-10 22:15:53 +00:00
case M.ORSYM: return Record(M.$or, [pieces.map(k)]);
case M.ANDSYM: return Record(M.$and, [pieces.map(k)]);
2021-03-09 14:59:40 +00:00
default: throw new Error("Internal error: unexpected operator");
}
}
function findName(x: Input): symbol | false {
if (!Annotated.isAnnotated<never>(x)) return false;
for (const a0 of x.annotations) {
const a = peel(a0);
if (typeof a === 'symbol') return a;
}
return false;
}
function parseBase(name: symbol, body: Array<Input>): Pattern {
body = peel(body) as Array<Input>;
if (body.length !== 1) invalidPattern(name, body);
const item = peel(body[0]);
const walk = (b: Input): Pattern => parseBase(name, [b]);
const namedwalk = (b: Input): NamedPattern => {
const name = findName(b);
if (name === false) return walk(b);
2021-03-10 22:15:53 +00:00
return Record(M.$named, [name, walk(b)]);
2021-03-09 14:59:40 +00:00
};
const walkitems = (b: Input): Pattern[] => {
b = peel(b);
if (!Array.isArray(b)) complain();
return b.map(walk);
};
function complain(): never {
invalidPattern(name, item);
}
if (typeof item === 'symbol') {
const s = item.description;
if (s === void 0) complain();
2021-03-10 22:15:53 +00:00
if (s[0] === '=') return Record(M.$lit, [Symbol.for(s.slice(1))]);
2021-03-11 09:56:49 +00:00
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])
]);
}
2021-03-09 14:59:40 +00:00
} else if (Record.isRecord<Input, Tuple<Input>, never>(item)) {
const label = item.label;
if (Record.isRecord<Input, [], never>(label)) {
if (label.length !== 0) complain();
switch (label.label) {
2021-03-10 22:15:53 +00:00
case M.$lit:
2021-03-09 14:59:40 +00:00
if (item.length !== 1) complain();
2021-03-10 22:15:53 +00:00
return Record(M.$lit, [item[0]]);
case M.$or:
2021-03-09 14:59:40 +00:00
if (item.length !== 1) complain();
2021-03-10 22:15:53 +00:00
return Record(M.$or, [walkitems(item[0])]);
case M.$and:
2021-03-09 14:59:40 +00:00
if (item.length !== 1) complain();
2021-03-10 22:15:53 +00:00
return Record(M.$and, [walkitems(item[0])]);
case M.$rec:
2021-03-09 14:59:40 +00:00
if (item.length !== 2) complain();
2021-03-10 22:15:53 +00:00
return Record(M.$rec, [walk(item[0]), walk(item[1])]);
2021-03-09 14:59:40 +00:00
default:
complain();
}
} else {
2021-03-10 22:15:53 +00:00
return Record(M.$rec, [Record(M.$lit, [label]), Record(M.$tuple, [item.map(namedwalk)])]);
2021-03-09 14:59:40 +00:00
}
} else if (Array.isArray(item)) {
if (is(item[item.length - 1], M.DOTDOTDOT)) {
if (item.length < 2) complain();
2021-03-10 22:15:53 +00:00
return Record(M.$tuple_STAR_, [
2021-03-09 14:59:40 +00:00
item.slice(0, item.length - 2).map(namedwalk),
namedwalk(item[item.length - 2]),
]);
} else {
2021-03-10 22:15:53 +00:00
return Record(M.$tuple, [item.map(namedwalk)]);
2021-03-09 14:59:40 +00:00
}
} else if (Dictionary.isDictionary<Input, never>(item)) {
if (item.size === 2 && item.has(M.DOTDOTDOT)) {
const v = item.clone();
v.delete(M.DOTDOTDOT);
const [[kp, vp]] = v.entries();
2021-03-10 22:15:53 +00:00
return Record(M.$dictof, [walk(kp), walk(vp)]);
2021-03-09 14:59:40 +00:00
} else {
2021-03-10 22:15:53 +00:00
return Record(M.$dict, [item.mapEntries<Pattern, Input, never>(([k, vp]) =>
2021-03-09 14:59:40 +00:00
[strip(k), walk(vp)])]);
}
} else if (Set.isSet<never>(item)) {
if (item.size !== 1) complain();
const [vp] = item.entries();
2021-03-10 22:15:53 +00:00
return Record(M.$setof, [walk(vp)]);
2021-03-09 14:59:40 +00:00
} else {
2021-03-10 22:15:53 +00:00
return Record(M.$lit, [strip(item)]);
2021-03-09 14:59:40 +00:00
}
}