import {BooleanToken, MappingToken, NullToken, NumberToken, ScalarToken, SequenceToken, StringToken} from "."; import {Definition} from "../schema/definition"; import {DefinitionInfo} from "../schema/definition-info"; import {PropertyDefinition} from "../schema/property-definition"; import {SerializedToken} from "./serialization"; import {TokenRange} from "./token-range"; import {TraversalState} from "./traversal-state"; import {TokenType, tokenTypeName} from "./types"; export class TemplateTokenError extends Error { constructor(message: string, public readonly token?: TemplateToken) { super(message); } } export abstract class TemplateToken { // Fields for serialization private readonly type: TokenType; private _description: string | undefined; public readonly file: number | undefined; public readonly range: TokenRange | undefined; public definitionInfo: DefinitionInfo | undefined; public propertyDefinition: PropertyDefinition | undefined; /** * Base class for all template tokens */ public constructor( type: TokenType, file: number | undefined, range: TokenRange | undefined, definitionInfo: DefinitionInfo | undefined ) { this.type = type; this.file = file; this.range = range; this.definitionInfo = definitionInfo; } public get templateTokenType(): number { return this.type; } public get line(): number | undefined { return this.range?.start.line; } public get col(): number | undefined { return this.range?.start.column; } public get definition(): Definition | undefined { return this.definitionInfo?.definition; } get description(): string | undefined { return this._description || this.propertyDefinition?.description || this.definition?.description; } set description(description: string | undefined) { this._description = description; } public abstract get isScalar(): boolean; public abstract get isLiteral(): boolean; public abstract get isExpression(): boolean; public abstract clone(omitSource?: boolean): TemplateToken; public typeName(): string { return tokenTypeName(this.type); } /** * Asserts expected type and throws a good debug message if unexpected */ public assertNull(objectDescription: string): NullToken { if (this.type === TokenType.Null) { return this as unknown as NullToken; } throw new TemplateTokenError( `Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName( TokenType.Null )}' was expected.`, this ); } /** * Asserts expected type and throws a good debug message if unexpected */ public assertBoolean(objectDescription: string): BooleanToken { if (this.type === TokenType.Boolean) { return this as unknown as BooleanToken; } throw new TemplateTokenError( `Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName( TokenType.Boolean )}' was expected.`, this ); } /** * Asserts expected type and throws a good debug message if unexpected */ public assertNumber(objectDescription: string): NumberToken { if (this.type === TokenType.Number) { return this as unknown as NumberToken; } throw new TemplateTokenError( `Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName( TokenType.Number )}' was expected.`, this ); } /** * Asserts expected type and throws a good debug message if unexpected */ public assertString(objectDescription: string): StringToken { if (this.type === TokenType.String) { return this as unknown as StringToken; } throw new TemplateTokenError( `Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName( TokenType.String )}' was expected.`, this ); } /** * Asserts expected type and throws a good debug message if unexpected */ public assertScalar(objectDescription: string): ScalarToken { if ((this as unknown as ScalarToken | undefined)?.isScalar === true) { return this as unknown as ScalarToken; } throw new TemplateTokenError( `Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type 'ScalarToken' was expected.`, this ); } /** * Asserts expected type and throws a good debug message if unexpected */ public assertSequence(objectDescription: string): SequenceToken { if (this.type === TokenType.Sequence) { return this as unknown as SequenceToken; } throw new TemplateTokenError( `Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName( TokenType.Sequence )}' was expected.`, this ); } /** * Asserts expected type and throws a good debug message if unexpected */ public assertMapping(objectDescription: string): MappingToken { if (this.type === TokenType.Mapping) { return this as unknown as MappingToken; } throw new TemplateTokenError( `Unexpected type '${this.typeName()}' encountered while reading '${objectDescription}'. The type '${tokenTypeName( TokenType.Mapping )}' was expected.`, this ); } /** * Returns all tokens (depth first) * @param value The object to travese * @param omitKeys Whether to omit mapping keys */ public static *traverse( value: TemplateToken, omitKeys?: boolean ): Generator<[parent: TemplateToken | undefined, token: TemplateToken, keyToken: TemplateToken | undefined], void> { yield [undefined, value, undefined]; switch (value.templateTokenType) { case TokenType.Sequence: case TokenType.Mapping: { let state: TraversalState | undefined = new TraversalState(undefined, value); state = new TraversalState(state, value); while (state.parent) { if (state.moveNext(omitKeys ?? false)) { value = state.current as TemplateToken; yield [state.parent?.current, value, state.currentKey]; switch (value.type) { case TokenType.Sequence: case TokenType.Mapping: state = new TraversalState(state, value); break; } } else { state = state.parent; } } break; } } } public toJSON(): SerializedToken { return undefined; } }