226 lines
6.5 KiB
TypeScript
226 lines
6.5 KiB
TypeScript
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;
|
|
}
|
|
}
|