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

121 lines
3.9 KiB
TypeScript

import { Value, is, Position, stringify } from '@preserves/core';
import * as M from './gen/schema';
import { BASE } from './base';
import { SchemaSyntaxError } from './error';
export * from './gen/schema';
export type Input = Value<never>;
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 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: M.ModulePath } & (
({
typescriptModulePath: string | null, // null means it's "this module" in disguise
schema: M.Schema,
}) | ({
typescriptModulePath: string,
schema: null,
})
);
export type Environment = Array<SchemaEnvEntry>;
function modsymFor(e: SchemaEnvEntry): string {
return '_i_' + e.schemaModulePath.map(s => s.description!).join('$');
}
export function lookup<R>(namePos: Position | null,
name: M.Ref,
env: Environment,
kLocal: (p: M.Definition) => R,
kBase: (p: M.Alternative) => R,
kOther: (modId: string, modPath: string, p: M.Definition | null) => R): R
{
for (const e of env) {
if (is(e.schemaModulePath, M.Ref._.module(name)) ||
(e.typescriptModulePath === null && M.Ref._.module(name).length === 0))
{
if (e.schema === null) {
// It's an artificial module, not from a schema. Assume the identifier is present.
return kOther(modsymFor(e), e.typescriptModulePath, null);
} else {
const p = M.Schema._._field0(e.schema).get(M.$definitions).get(M.Ref._.name(name));
if (p !== void 0) {
if (e.typescriptModulePath === null) {
return kLocal(p);
} else {
return kOther(modsymFor(e), e.typescriptModulePath, p);
}
}
}
}
}
if (M.Ref._.module(name).length === 0) {
const p = M.Schema._._field0(BASE).get(M.$definitions).get(M.Ref._.name(name));
if (p !== void 0) return kBase(p as M.Alternative);
}
throw new SchemaSyntaxError(`Undefined reference: ${formatRef(name)}`, namePos);
}
export function formatRef(r: M.Ref): string {
return [... r[0], r[1]].map(s => s.description!).join('.');
}
export function unname<R extends M.Pattern | M.SimplePattern>(
p: M.NamedSimplePattern_ | R): M.SimplePattern | R
{
return (p.label === M.$named) ? p[1] : p;
}
export function nameFor<R extends M.Pattern | M.SimplePattern>(
p: M.NamedSimplePattern_ | R): string | undefined
{
return (p.label === M.$named) ? stringify(p[0]) : void 0;
}
export function addNameIfAbsent(p: M.NamedSimplePattern, k: Input): M.NamedSimplePattern {
if (p.label === M.$named) {
return p;
} else {
const s = namelike(k);
if (s !== void 0) {
return M.NamedSimplePattern_(Symbol.for(s), p);
} else {
return p;
}
}
}
// Simple arrays at toplevel for convenience
//
export function simpleArray(p: M.CompoundPattern): M.SimplePattern | undefined {
if (p.label === M.$tuple_STAR_ && p[0].length === 0 && p[1].label !== M.$named) {
return p[1];
} else {
return void 0;
}
}
export function namelike(x: Input): string | undefined {
if (typeof x === 'string') return x;
if (typeof x === 'symbol') return stringify(x);
if (typeof x === 'number') return '' + x;
return void 0;
}