UNPKG

plaxtony

Version:

Static code analysis of SC2 Galaxy Script

749 lines (686 loc) 30.6 kB
import { CharacterCodes, SyntaxKind, Token, DiagnosticMessage, DiagnosticCategory } from './types'; export interface ErrorCallback { (message: DiagnosticMessage, pos: number, length: number): void; } const textToTokenTable: ReadonlyMap<string, SyntaxKind> = new Map([ ["include", SyntaxKind.IncludeKeyword], ["struct", SyntaxKind.StructKeyword], ["static", SyntaxKind.StaticKeyword], ["const", SyntaxKind.ConstKeyword], ["native", SyntaxKind.NativeKeyword], ["break", SyntaxKind.BreakKeyword], ["continue", SyntaxKind.ContinueKeyword], ["breakpoint", SyntaxKind.BreakpointKeyword], ["return", SyntaxKind.ReturnKeyword], ["switch", SyntaxKind.SwitchKeyword], // ["case", SyntaxKind.CaseKeyword], ["default", SyntaxKind.DefaultKeyword], ["new", SyntaxKind.NewKeyword], ["do", SyntaxKind.DoKeyword], ["for", SyntaxKind.ForKeyword], ["while", SyntaxKind.WhileKeyword], ["if", SyntaxKind.IfKeyword], ["else", SyntaxKind.ElseKeyword], ["true", SyntaxKind.TrueKeyword], ["false", SyntaxKind.FalseKeyword], ["null", SyntaxKind.NullKeyword], ["typedef", SyntaxKind.TypedefKeyword], ["abilcmd", SyntaxKind.AbilcmdKeyword], ["actor", SyntaxKind.ActorKeyword], ["actorscope", SyntaxKind.ActorscopeKeyword], ["aifilter", SyntaxKind.AifilterKeyword], ["bank", SyntaxKind.BankKeyword], ["bool", SyntaxKind.BoolKeyword], ["bitmask", SyntaxKind.BitmaskKeyword], ["byte", SyntaxKind.ByteKeyword], ["camerainfo", SyntaxKind.CamerainfoKeyword], ["char", SyntaxKind.CharKeyword], ["color", SyntaxKind.ColorKeyword], ["doodad", SyntaxKind.DoodadKeyword], ["datetime", SyntaxKind.DatetimeKeyword], ["fixed", SyntaxKind.FixedKeyword], ["handle", SyntaxKind.HandleKeyword], ["generichandle", SyntaxKind.GenerichandleKeyword], ["effecthistory", SyntaxKind.EffecthistoryKeyword], ["int", SyntaxKind.IntKeyword], ["marker", SyntaxKind.MarkerKeyword], ["order", SyntaxKind.OrderKeyword], ["playergroup", SyntaxKind.PlayergroupKeyword], ["point", SyntaxKind.PointKeyword], ["region", SyntaxKind.RegionKeyword], ["revealer", SyntaxKind.RevealerKeyword], ["sound", SyntaxKind.SoundKeyword], ["soundlink", SyntaxKind.SoundlinkKeyword], ["string", SyntaxKind.StringKeyword], ["text", SyntaxKind.TextKeyword], ["timer", SyntaxKind.TimerKeyword], ["transmissionsource", SyntaxKind.TransmissionsourceKeyword], ["trigger", SyntaxKind.TriggerKeyword], ["unit", SyntaxKind.UnitKeyword], ["unitfilter", SyntaxKind.UnitfilterKeyword], ["unitgroup", SyntaxKind.UnitgroupKeyword], ["unitref", SyntaxKind.UnitrefKeyword], ["void", SyntaxKind.VoidKeyword], ["wave", SyntaxKind.WaveKeyword], ["waveinfo", SyntaxKind.WaveinfoKeyword], ["wavetarget", SyntaxKind.WavetargetKeyword], ["arrayref", SyntaxKind.ArrayrefKeyword], ["structref", SyntaxKind.StructrefKeyword], ["funcref", SyntaxKind.FuncrefKeyword], ["{", SyntaxKind.OpenBraceToken], ["}", SyntaxKind.CloseBraceToken], ["(", SyntaxKind.OpenParenToken], [")", SyntaxKind.CloseParenToken], ["[", SyntaxKind.OpenBracketToken], ["]", SyntaxKind.CloseBracketToken], [".", SyntaxKind.DotToken], [";", SyntaxKind.SemicolonToken], [",", SyntaxKind.CommaToken], ["<", SyntaxKind.LessThanToken], [">", SyntaxKind.GreaterThanToken], ["<=", SyntaxKind.LessThanEqualsToken], [">=", SyntaxKind.GreaterThanEqualsToken], ["==", SyntaxKind.EqualsEqualsToken], ["!=", SyntaxKind.ExclamationEqualsToken], ["=>", SyntaxKind.EqualsGreaterThanToken], ["+", SyntaxKind.PlusToken], ["-", SyntaxKind.MinusToken], ["*", SyntaxKind.AsteriskToken], ["/", SyntaxKind.SlashToken], ["%", SyntaxKind.PercentToken], ["++", SyntaxKind.PlusPlusToken], ["--", SyntaxKind.MinusMinusToken], ["<<", SyntaxKind.LessThanLessThanToken], [">>", SyntaxKind.GreaterThanGreaterThanToken], ["&", SyntaxKind.AmpersandToken], ["|", SyntaxKind.BarToken], ["^", SyntaxKind.CaretToken], ["!", SyntaxKind.ExclamationToken], ["~", SyntaxKind.TildeToken], ["&&", SyntaxKind.AmpersandAmpersandToken], ["||", SyntaxKind.BarBarToken], ["?", SyntaxKind.QuestionToken], [":", SyntaxKind.ColonToken], ["=", SyntaxKind.EqualsToken], ["+=", SyntaxKind.PlusEqualsToken], ["-=", SyntaxKind.MinusEqualsToken], ["*=", SyntaxKind.AsteriskEqualsToken], ["/=", SyntaxKind.SlashEqualsToken], ["%=", SyntaxKind.PercentEqualsToken], ["<<=", SyntaxKind.LessThanLessThanEqualsToken], [">>=", SyntaxKind.GreaterThanGreaterThanEqualsToken], ["&=", SyntaxKind.AmpersandEqualsToken], ["|=", SyntaxKind.BarEqualsToken], ["^=", SyntaxKind.CaretEqualsToken], ]); // const tokenStrings = new Map(Array.from(textToTokenTable).reverse()); function makeReverseMap(source: ReadonlyMap<string, SyntaxKind>): string[] { const result: string[] = []; source.forEach((value, name) => { result[value] = name; }); return result; } const tokenStrings = makeReverseMap(textToTokenTable); export function stringToToken(s: string): SyntaxKind | undefined { return textToTokenTable.get(s); } export function tokenToString(t: SyntaxKind): string | undefined { return tokenStrings[t]; } export function isIdentifierStart(ch: number): boolean { return (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) || (ch >= CharacterCodes.a && ch <= CharacterCodes.z); } export function isIdentifierPart(ch: number): boolean { return (ch >= CharacterCodes.A && ch <= CharacterCodes.Z) || (ch >= CharacterCodes.a && ch <= CharacterCodes.z) || (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) || (ch === CharacterCodes._) ; } export function isLineBreak(ch: number): boolean { return ch === CharacterCodes.lineFeed || ch === CharacterCodes.carriageReturn || ch === CharacterCodes.lineSeparator || ch === CharacterCodes.paragraphSeparator ; } export function isDigit(ch: number): boolean { return ch >= CharacterCodes._0 && ch <= CharacterCodes._9; } export function isOctalDigit(ch: number): boolean { return ch >= CharacterCodes._0 && ch <= CharacterCodes._7; } export class Scanner { private line: number; private char: number; // Current position (end position of text of current token) private pos: number; // end of text private end: number; // Start position of whitespace before current token private startPos: number; // Start position of text of current token private tokenPos: number; private text: string; private token: SyntaxKind; private tokenValue: string; // private precedingLineBreak: boolean; // private hasExtendedUnicodeEscape: boolean; // private tokenIsUnterminated: boolean; // private numericLiteralFlags: NumericLiteralFlags; private onError?: ErrorCallback; private lineMap: number[]; constructor(onError?: ErrorCallback) { this.onError = onError; } private error(msg: string): void { if (this.onError) { this.onError(<DiagnosticMessage>{ category: DiagnosticCategory.Error, code: 0, message: msg, }, this.pos, 1); } } private speculationHelper<T>(callback: () => T, isLookahead: boolean): T { const saveLine = this.line; const saveCol = this.char; const savePos = this.pos; const saveStartPos = this.startPos; const saveTokenPos = this.tokenPos; const saveToken = this.token; const saveTokenValue = this.tokenValue; const saveLineMapLength = this.lineMap.length; // const savePrecedingLineBreak = this.precedingLineBreak; const result = callback(); // If our callback returned something 'falsy' or we're just looking ahead, // then unconditionally restore us to where we were. if (!result || isLookahead) { this.line = saveLine; this.char = saveCol; this.pos = savePos; this.startPos = saveStartPos; this.tokenPos = saveTokenPos; this.token = saveToken; this.tokenValue = saveTokenValue; if (this.lineMap.length !== saveLineMapLength) { this.lineMap = this.lineMap.slice(0, saveLineMapLength); } // this.precedingLineBreak = savePrecedingLineBreak; } return result; } public lookAhead<T>(callback: () => T): T { return this.speculationHelper(callback, true); } public tryScan<T>(callback: () => T): T { return this.speculationHelper(callback, false); } private scanHexDigits(minCount: number, scanAsManyAsPossible: boolean): number { let digits = 0; let value = 0; while (digits < minCount || scanAsManyAsPossible) { const ch = this.text.charCodeAt(this.pos); if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { value = value * 16 + ch - CharacterCodes._0; } else if (ch >= CharacterCodes.A && ch <= CharacterCodes.F) { value = value * 16 + ch - CharacterCodes.A + 10; } else if (ch >= CharacterCodes.a && ch <= CharacterCodes.f) { value = value * 16 + ch - CharacterCodes.a + 10; } else { break; } this.pos++; digits++; } if (digits < minCount) { value = -1; } return value; } private scanEscapeSequence(): string { this.pos++; if (this.pos >= this.end) { this.error("Diagnostics.Unexpected_end_of_text"); return ""; } const ch = this.text.charCodeAt(this.pos); this.pos++; switch (ch) { case CharacterCodes._0: return "\0"; case CharacterCodes.b: return "\b"; case CharacterCodes.t: return "\t"; case CharacterCodes.n: return "\n"; case CharacterCodes.v: return "\v"; case CharacterCodes.f: return "\f"; case CharacterCodes.r: return "\r"; case CharacterCodes.singleQuote: return "\'"; case CharacterCodes.doubleQuote: return "\""; case CharacterCodes.x: // '\xDD' const escapedValue = this.scanHexDigits(2, false); if (escapedValue >= 0) { return String.fromCharCode(escapedValue); } else { this.error("Diagnostics.Hexadecimal_digit_expected"); return ""; } // when encountering a LineContinuation (i.e. a backslash and a line terminator sequence), // the line terminator is interpreted to be "the empty code unit sequence". case CharacterCodes.carriageReturn: if (this.pos < this.end && this.text.charCodeAt(this.pos) === CharacterCodes.lineFeed) { this.pos++; } // falls through case CharacterCodes.lineFeed: case CharacterCodes.lineSeparator: case CharacterCodes.paragraphSeparator: this.error('Multiline strings not supported'); return ""; default: return String.fromCharCode(ch); } } private scanString(allowEscapes = true): string { const quote = this.text.charCodeAt(this.pos); this.pos++; let result = ""; let start = this.pos; while (true) { if (this.pos >= this.end) { result += this.text.substring(start, this.pos); // tokenIsUnterminated = true; this.error("Diagnostics.Unterminated_string_literal"); break; } const ch = this.text.charCodeAt(this.pos); if (ch === quote) { result += this.text.substring(start, this.pos); this.pos++; break; } if (ch === CharacterCodes.backslash && allowEscapes) { result += this.text.substring(start, this.pos); result += this.scanEscapeSequence() start = this.pos; continue; } if (isLineBreak(ch)) { result += this.text.substring(start, this.pos); // tokenIsUnterminated = true; this.error("Diagnostics.Unterminated_string_literal"); break; } this.pos++; } return result; } private scanNumber(): string { const start = this.pos; while (isDigit(this.text.charCodeAt(this.pos))) this.pos++; if (this.text.charCodeAt(this.pos) === CharacterCodes.dot) { this.pos++; while (isDigit(this.text.charCodeAt(this.pos))) this.pos++; } let end = this.pos; if (this.text.charCodeAt(this.pos) === CharacterCodes.E || this.text.charCodeAt(this.pos) === CharacterCodes.e) { this.pos++; // numericLiteralFlags = NumericLiteralFlags.Scientific; if (this.text.charCodeAt(this.pos) === CharacterCodes.plus || this.text.charCodeAt(this.pos) === CharacterCodes.minus) this.pos++; if (isDigit(this.text.charCodeAt(this.pos))) { this.pos++; while (isDigit(this.text.charCodeAt(this.pos))) this.pos++; end = this.pos; } else { this.error("Diagnostics.Digit_expected"); } } return "" + +(this.text.substring(start, end)); } private scanBinaryOrOctalDigits(base: number): number { console.assert(base === 2 || base === 8, "Expected either base 2 or base 8"); let value = 0; // For counting number of digits; Valid binaryIntegerLiteral must have at least one binary digit following B or b. // Similarly valid octalIntegerLiteral must have at least one octal digit following o or O. let numberOfDigits = 0; while (true) { const ch = this.text.charCodeAt(this.pos); const valueOfCh = ch - CharacterCodes._0; if (!isDigit(ch) || valueOfCh >= base) { break; } value = value * base + valueOfCh; this.pos++; numberOfDigits++; } // Invalid binaryIntegerLiteral or octalIntegerLiteral if (numberOfDigits === 0) { return -1; } return value; } private scanOctalDigits(): number { const start = this.pos; while (isOctalDigit(this.text.charCodeAt(this.pos))) { this.pos++; } return +(this.text.substring(start, this.pos)); } private getIdentifierToken(): SyntaxKind { // Reserved words are between 2 and 11 characters long and start with a lowercase letter let token: SyntaxKind | undefined; const len = this.tokenValue.length; const ch = this.tokenValue.charCodeAt(0); if (ch >= CharacterCodes.a && ch <= CharacterCodes.z) { token = stringToToken(this.tokenValue); if (token !== undefined) { return token; } } return SyntaxKind.Identifier; } public setText(text: string): void { this.text = text; this.pos = 0; this.end = this.text.length; this.line = 0; this.char = 0; this.lineMap = [0]; } scan(): SyntaxKind { this.startPos = this.pos; while (true) { this.tokenPos = this.pos; this.tokenValue = null; if (this.pos >= this.end) { return SyntaxKind.EndOfFileToken; } let ch = this.text.charCodeAt(this.pos); switch (ch) { case CharacterCodes.tab: case CharacterCodes.verticalTab: case CharacterCodes.formFeed: case CharacterCodes.space: ++this.pos; break; case CharacterCodes.semicolon: ++this.pos; return this.token = SyntaxKind.SemicolonToken; case CharacterCodes.openParen: this.pos++; return this.token = SyntaxKind.OpenParenToken; case CharacterCodes.closeParen: this.pos++; return this.token = SyntaxKind.CloseParenToken; case CharacterCodes.doubleQuote: case CharacterCodes.singleQuote: this.tokenValue = this.scanString(); return this.token = SyntaxKind.StringLiteral; case CharacterCodes.exclamation: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.ExclamationEqualsToken; } this.pos++; return this.token = SyntaxKind.ExclamationToken; case CharacterCodes.percent: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.PercentEqualsToken; } this.pos++; return this.token = SyntaxKind.PercentToken; case CharacterCodes.ampersand: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.ampersand) { return this.pos += 2, this.token = SyntaxKind.AmpersandAmpersandToken; } if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.AmpersandEqualsToken; } this.pos++; return this.token = SyntaxKind.AmpersandToken; case CharacterCodes.openParen: this.pos++; return this.token = SyntaxKind.OpenParenToken; case CharacterCodes.closeParen: this.pos++; return this.token = SyntaxKind.CloseParenToken; case CharacterCodes.asterisk: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.AsteriskEqualsToken; } this.pos++; return this.token = SyntaxKind.AsteriskToken; case CharacterCodes.plus: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.plus) { return this.pos += 2, this.token = SyntaxKind.PlusPlusToken; } if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.PlusEqualsToken; } this.pos++; return this.token = SyntaxKind.PlusToken; case CharacterCodes.comma: this.pos++; return this.token = SyntaxKind.CommaToken; case CharacterCodes.minus: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.minus) { return this.pos += 2, this.token = SyntaxKind.MinusMinusToken; } if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.MinusEqualsToken; } this.pos++; return this.token = SyntaxKind.MinusToken; case CharacterCodes.dot: if (isDigit(this.text.charCodeAt(this.pos + 1))) { this.tokenValue = this.scanNumber(); return this.token = SyntaxKind.NumericLiteral; } this.pos++; return this.token = SyntaxKind.DotToken; case CharacterCodes.slash: // Single-line comment if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.slash) { this.pos += 2; while (this.pos < this.end) { const char = this.text.charCodeAt(this.pos); if (isLineBreak(char)) { break; } this.pos++; } return this.token = SyntaxKind.SingleLineCommentTrivia; } if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.SlashEqualsToken; } this.pos++; return this.token = SyntaxKind.SlashToken; case CharacterCodes._0: if (this.pos + 2 < this.end && (this.text.charCodeAt(this.pos + 1) === CharacterCodes.X || this.text.charCodeAt(this.pos + 1) === CharacterCodes.x)) { this.pos += 2; let value = this.scanHexDigits(1, true); if (value < 0) { this.error("Diagnostics.Hexadecimal_digit_expected"); value = 0; } this.tokenValue = "" + value; // numericLiteralFlags = NumericLiteralFlags.HexSpecifier; return this.token = SyntaxKind.NumericLiteral; } else if (this.pos + 2 < this.end && (this.text.charCodeAt(this.pos + 1) === CharacterCodes.B || this.text.charCodeAt(this.pos + 1) === CharacterCodes.b)) { this.pos += 2; let value = this.scanBinaryOrOctalDigits(/* base */ 2); if (value < 0) { this.error("Diagnostics.Binary_digit_expected"); value = 0; } this.tokenValue = "" + value; // numericLiteralFlags = NumericLiteralFlags.BinarySpecifier; return this.token = SyntaxKind.NumericLiteral; } else if (this.pos + 2 < this.end && (this.text.charCodeAt(this.pos + 1) === CharacterCodes.O || this.text.charCodeAt(this.pos + 1) === CharacterCodes.o)) { this.pos += 2; let value = this.scanBinaryOrOctalDigits(/* base */ 8); if (value < 0) { this.error("Diagnostics.Octal_digit_expected"); value = 0; } this.tokenValue = "" + value; // numericLiteralFlags = NumericLiteralFlags.OctalSpecifier; return this.token = SyntaxKind.NumericLiteral; } // Try to parse as an octal if (this.pos + 1 < this.end && isOctalDigit(this.text.charCodeAt(this.pos + 1))) { this.tokenValue = "" + this.scanOctalDigits(); // numericLiteralFlags = NumericLiteralFlags.Octal; return this.token = SyntaxKind.NumericLiteral; } // This fall-through is a deviation from the EcmaScript grammar. The grammar says that a leading zero // can only be followed by an octal digit, a dot, or the end of the number literal. However, we are being // permissive and allowing decimal digits of the form 08* and 09* (which many browsers also do). // falls through case CharacterCodes._1: case CharacterCodes._2: case CharacterCodes._3: case CharacterCodes._4: case CharacterCodes._5: case CharacterCodes._6: case CharacterCodes._7: case CharacterCodes._8: case CharacterCodes._9: this.tokenValue = this.scanNumber(); return this.token = SyntaxKind.NumericLiteral; case CharacterCodes.lessThan: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.lessThan) { if (this.text.charCodeAt(this.pos + 2) === CharacterCodes.equals) { return this.pos += 3, this.token = SyntaxKind.LessThanLessThanEqualsToken; } return this.pos += 2, this.token = SyntaxKind.LessThanLessThanToken; } if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.LessThanEqualsToken; } this.pos++; return this.token = SyntaxKind.LessThanToken; case CharacterCodes.equals: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.EqualsEqualsToken; } if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.greaterThan) { return this.pos += 2, this.token = SyntaxKind.EqualsGreaterThanToken; } this.pos++; return this.token = SyntaxKind.EqualsToken; case CharacterCodes.greaterThan: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.greaterThan) { if (this.text.charCodeAt(this.pos + 2) === CharacterCodes.equals) { return this.pos += 3, this.token = SyntaxKind.GreaterThanGreaterThanEqualsToken; } return this.pos += 2, this.token = SyntaxKind.GreaterThanGreaterThanToken; } if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.GreaterThanEqualsToken; } this.pos++; return this.token = SyntaxKind.GreaterThanToken; case CharacterCodes.openBracket: this.pos++; return this.token = SyntaxKind.OpenBracketToken; case CharacterCodes.closeBracket: this.pos++; return this.token = SyntaxKind.CloseBracketToken; case CharacterCodes.caret: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.CaretEqualsToken; } this.pos++; return this.token = SyntaxKind.CaretToken; case CharacterCodes.openBrace: this.pos++; return this.token = SyntaxKind.OpenBraceToken; case CharacterCodes.bar: if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.bar) { return this.pos += 2, this.token = SyntaxKind.BarBarToken; } if (this.text.charCodeAt(this.pos + 1) === CharacterCodes.equals) { return this.pos += 2, this.token = SyntaxKind.BarEqualsToken; } this.pos++; return this.token = SyntaxKind.BarToken; case CharacterCodes.closeBrace: this.pos++; return this.token = SyntaxKind.CloseBraceToken; case CharacterCodes.tilde: this.pos++; return this.token = SyntaxKind.TildeToken; default: if (isIdentifierStart(ch)) { this.pos++; while (this.pos < this.end && isIdentifierPart(ch = this.text.charCodeAt(this.pos))) this.pos++; this.tokenValue = this.text.substring(this.tokenPos, this.pos); return this.token = this.getIdentifierToken(); } else if (isLineBreak(ch)) { if (ch === CharacterCodes.lineFeed) { this.char = this.pos; this.line++; } this.pos++; if (ch === CharacterCodes.lineFeed) { this.lineMap.push(this.pos); } continue; } this.error(`Encountered invalid character: 0x${ch.toString(16)}`); this.pos++; return this.token = SyntaxKind.Unknown; } } } public getLine(): number { return this.line; } public getChar(): number { return this.pos - this.char; } public getCurrentPos(): number { return this.pos; } /** * Absolute start position - including adjacent whitespace */ public getStartPos(): number { return this.startPos; } /** * Actual start position - excluding adjacent whitespace */ public getTokenPos(): number { return this.tokenPos; } public getTokenValue(): string { return this.tokenValue; } public getTokenText(): string { return this.text.substring(this.tokenPos, this.pos); } public getLineMap(): number[] { return this.lineMap; } }