131 lines
5.2 KiB
TypeScript
131 lines
5.2 KiB
TypeScript
import * as M from './meta';
|
|
|
|
export function checkSchema(schema: M.Schema): (
|
|
{ ok: true, schema: M.Schema } | { ok: false, problems: Array<string> })
|
|
{
|
|
const checker = new Checker();
|
|
schema.definitions.forEach(checker.checkDefinition.bind(checker));
|
|
if (checker.problems.length > 0) {
|
|
return { ok: false, problems: checker.problems };
|
|
} else {
|
|
return { ok: true, schema };
|
|
}
|
|
}
|
|
|
|
enum ValueAvailability {
|
|
AVAILABLE,
|
|
NOT_AVAILABLE,
|
|
};
|
|
|
|
class Checker {
|
|
problems: Array<string> = [];
|
|
|
|
recordProblem(context: string, detail: string): void {
|
|
this.problems.push(`${detail} in ${context}`);
|
|
}
|
|
|
|
checkBinding(scope: Set<string>, sym: symbol, context: string): void {
|
|
const name = sym.description!;
|
|
if (scope.has(name)) {
|
|
this.recordProblem(context, `duplicate binding named ${JSON.stringify(name)}`);
|
|
}
|
|
if (!M.isValidToken(name)) {
|
|
this.recordProblem(context, `invalid binding name ${JSON.stringify(name)}`);
|
|
}
|
|
scope.add(name);
|
|
}
|
|
|
|
checkDefinition(def: M.Definition, name: symbol): void {
|
|
switch (def._variant) {
|
|
case 'or': {
|
|
const labels = new Set<string>();
|
|
[def.pattern0, def.pattern1, ... def.patternN].forEach(({ variantLabel, pattern }) => {
|
|
const context = `variant ${variantLabel} of ${name.description!}`;
|
|
if (labels.has(variantLabel)) {
|
|
this.recordProblem(context, `duplicate variant label`);
|
|
}
|
|
if (!M.isValidToken(variantLabel)) {
|
|
this.recordProblem(context, `invalid variant label`);
|
|
}
|
|
labels.add(variantLabel);
|
|
this.checkPattern(new Set(), pattern, context, ValueAvailability.AVAILABLE);
|
|
});
|
|
break;
|
|
}
|
|
case 'and': {
|
|
const ps = [def.pattern0, def.pattern1, ... def.patternN];
|
|
const scope = new Set<string>();
|
|
ps.forEach((p) => this.checkNamedPattern(scope, p, name.description!));
|
|
break;
|
|
}
|
|
case 'Pattern':
|
|
this.checkPattern(
|
|
new Set(), def.value, name.description!, ValueAvailability.AVAILABLE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
checkNamedPattern(scope: Set<string>, p: M.NamedPattern, context: string): void
|
|
{
|
|
switch (p._variant) {
|
|
case 'named': {
|
|
this.checkBinding(scope, p.value.name, context);
|
|
this.checkPattern(scope,
|
|
M.Pattern.SimplePattern(p.value.pattern),
|
|
`${JSON.stringify(p.value.name.description!)} of ${context}`,
|
|
ValueAvailability.AVAILABLE);
|
|
break;
|
|
}
|
|
case 'anonymous':
|
|
this.checkPattern(scope, p.value, context, ValueAvailability.NOT_AVAILABLE);
|
|
break;
|
|
}
|
|
}
|
|
|
|
checkPattern(scope: Set<string>,
|
|
p: M.Pattern,
|
|
context: string,
|
|
availability: ValueAvailability): void
|
|
{
|
|
switch (p._variant) {
|
|
case 'SimplePattern':
|
|
if (p.value._variant !== 'lit' && availability === ValueAvailability.NOT_AVAILABLE) {
|
|
this.recordProblem(context, 'cannot recover serialization of non-literal pattern');
|
|
}
|
|
if (p.value._variant === 'Ref' &&
|
|
!(M.isValidToken(p.value.value.name.description!) &&
|
|
p.value.value.module.every(n => M.isValidToken(n.description!))))
|
|
{
|
|
this.recordProblem(context, 'invalid reference name');
|
|
}
|
|
break;
|
|
case 'CompoundPattern':
|
|
((p: M.CompoundPattern): void => {
|
|
switch (p._variant) {
|
|
case 'rec':
|
|
this.checkNamedPattern(scope, p.label, `label of ${context}`);
|
|
this.checkNamedPattern(scope, p.fields, `fields of ${context}`);
|
|
break;
|
|
case 'tuple':
|
|
p.patterns.forEach((pp, i) =>
|
|
this.checkNamedPattern(scope, pp, `item ${i} of ${context}`));
|
|
break;
|
|
case 'tuplePrefix':
|
|
p.fixed.forEach((pp, i) =>
|
|
this.checkNamedPattern(scope, pp, `item ${i} of ${context}`));
|
|
this.checkNamedPattern(
|
|
scope, M.promoteNamedSimplePattern(p.variable), `tail of ${context}`);
|
|
break;
|
|
case 'dict':
|
|
p.entries.forEach((np, key) =>
|
|
this.checkNamedPattern(
|
|
scope,
|
|
M.promoteNamedSimplePattern(np),
|
|
`entry ${key.asPreservesText()} in dictionary in ${context}`));
|
|
break;
|
|
}
|
|
})(p.value);
|
|
}
|
|
}
|
|
}
|