plaxtony
Version:
Static code analysis of SC2 Galaxy Script
749 lines (686 loc) • 30.6 kB
text/typescript
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;
}
}