import { Value, is, Position } from '@preserves/core'; import * as M from './gen/schema'; import { SchemaSyntaxError } from './error'; import type { AtomicType } from './compiler/type'; import { JS_KEYWORDS } from './compiler/jskw'; export * from './gen/schema'; export type Builtin = { type: AtomicType, pattern: M.Alternative }; export type Input = Value; export function isValidToken(s: string): boolean { return /^[a-zA-Z][a-zA-Z_0-9]*$/.test(s); } export function isValidJsId(s: string): boolean { return /^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(s) && !JS_KEYWORDS.has(s); } export function jsId(v: string, kf?: () => string): string { const s = v .replace('_', '__') .replace('*', '_STAR_'); if (isValidJsId(s)) return s; if (isValidJsId('$' + s)) return '$' + s; if (kf !== void 0) return kf(); throw new Error(`Internal error: idForLiteral needs to be completed (${v})`); } 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; function modsymFor(e: SchemaEnvEntry): string { return '_i_' + e.schemaModulePath.map(s => s.description!).join('$'); } export function lookup(namePos: Position | null, name: M.Ref, env: Environment, kLocal: (p: M.Definition) => 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); } } } } } 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( p: M.NamedSimplePattern_ | R): M.SimplePattern | R { return (p.label === M.$named) ? p[1] : p; } export function nameFor( p: M.NamedSimplePattern_ | R): string | undefined { return (p.label === M.$named) ? p[0].description! : 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 x.description!; if (typeof x === 'number') return '' + x; if (typeof x === 'boolean') return '' + x; return void 0; }