UNPKG

plaxtony

Version:

Static code analysis of SC2 Galaxy Script

1,167 lines (999 loc) 46.9 kB
import * as Types from './types'; import { SyntaxKind, Node, NodeArray, MutableNodeArray } from './types'; import { Scanner, tokenToString } from './scanner'; import { getKindName, isModifierKind, isKeywordTypeKind, isLeftHandSideExpression, isAssignmentOperator, fixupParentReferences, isReferenceKeywordKind, isAssignmentExpression, createFileDiagnostic } from './utils'; const enum ParsingContext { SourceElements, BlockStatements, StructMembers, Parameters, TypeArguments, ArgumentExpressions, } export class Parser { private scanner: Scanner; private currentToken: SyntaxKind; private parsingContext: ParsingContext = 0; private sourceFile: Types.SourceFile; private syntaxTokens: Types.Node[][]; private token(): SyntaxKind { return this.currentToken; } private nextToken(): SyntaxKind { this.currentToken = this.scanner.scan(); while (this.currentToken === SyntaxKind.SingleLineCommentTrivia) { const commentToken = <Types.Token<Types.SyntaxKind.SingleLineCommentTrivia>>this.createNode(this.token(), undefined, false); this.currentToken = this.scanner.scan(); this.finishNode(commentToken, undefined, false); this.sourceFile.commentsLineMap.set(commentToken.line, commentToken); } return this.currentToken; } private parseErrorAtCurrentToken(message: string, arg0?: any): void { const start = this.scanner.getStartPos(); const length = this.scanner.getCurrentPos() - start; this.parseErrorAtPosition(start, length, message, arg0); } private parseErrorAtPosition(start: number, length: number, message: string, arg0?: any): void { const diag = createFileDiagnostic( this.sourceFile, start, length, <Types.DiagnosticMessage>{ code: 1001, category: Types.DiagnosticCategory.Error, message: message, }, ); // TODO: line & col should not be here diag.line = this.scanner.getLine(); diag.col = this.scanner.getChar(); this.sourceFile.parseDiagnostics.push(diag); // throw new Error(`${diag.file!.fileName} [${diag.start}]: ${diag.messageText}`); // throw new Error(`${diag.file!.fileName} [${this.scanner.getLine()}:${this.scanner.getCol()}]: ${diag.messageText}`); } private speculationHelper<T>(callback: () => T, isLookAhead: boolean): T { // Keep track of the state we'll need to rollback to if lookahead fails (or if the // caller asked us to always reset our state). const saveToken = this.currentToken; const saveSyntaxTokensLength = this.syntaxTokens.length; const saveSyntaxTokensCurrentLength = this.syntaxTokens[this.syntaxTokens.length - 1].length; const saveParseDiagnosticsLength = this.sourceFile.parseDiagnostics.length; // const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode; // Note: it is not actually necessary to save/restore the context flags here. That's // because the saving/restoring of these flags happens naturally through the recursive // descent nature of our parser. However, we still store this here just so we can // assert that invariant holds. // const saveContextFlags = contextFlags; // If we're only looking ahead, then tell the scanner to only lookahead as well. // Otherwise, if we're actually speculatively parsing, then tell the scanner to do the // same. const result = isLookAhead ? this.scanner.lookAhead(callback) : this.scanner.tryScan(callback); // Debug.assert(saveContextFlags === contextFlags); // If our callback returned something 'falsy' or we're just looking ahead, // then unconditionally restore us to where we were. if (!result || isLookAhead) { this.currentToken = saveToken; if (this.syntaxTokens.length > saveSyntaxTokensLength) { this.syntaxTokens = this.syntaxTokens.slice(0, saveSyntaxTokensLength); } if (this.syntaxTokens[this.syntaxTokens.length - 1].length > saveSyntaxTokensCurrentLength) { this.syntaxTokens[this.syntaxTokens.length - 1] = this.syntaxTokens[this.syntaxTokens.length - 1].slice(0, saveSyntaxTokensCurrentLength); } this.sourceFile.parseDiagnostics.length = saveParseDiagnosticsLength; // parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode; } return result; } private lookAhead<T>(callback: () => T): T { return this.speculationHelper(callback, true); } private parseExpected(kind: SyntaxKind, diagnosticMessage?: string, shouldAdvance = true): boolean { if (this.token() === kind) { if (shouldAdvance) { this.syntaxTokens[this.syntaxTokens.length - 1].push(this.parseTokenNode()); } return true; } if (diagnosticMessage == null) { diagnosticMessage = "Expected " + getKindName(kind) + ", found " + getKindName(this.currentToken); } this.parseErrorAtCurrentToken(diagnosticMessage); return false; } private parseOptional(t: SyntaxKind): boolean { if (this.token() === t) { this.syntaxTokens[this.syntaxTokens.length - 1].push(this.parseTokenNode()); return true; } return false; } private parseTokenNode<T extends Node>(): T { const node = <T>this.createNode(this.token(), undefined, false); this.nextToken(); return this.finishNode(node, undefined, false); } private createNode(kind: SyntaxKind, pos?: number, assignSyntaxTokens: boolean = true): Node { const node = <Node>{}; node.kind = kind; node.pos = pos === undefined ? this.scanner.getTokenPos() : pos; node.end = node.pos; node.line = this.scanner.getLine(); node.char = this.scanner.getChar(); if (process.env.PLAXTONY_DEBUG) { (<any>node).kindName = getKindName(node.kind); } if (assignSyntaxTokens) { this.syntaxTokens.push([]); } return node; } private createNodeArray<T extends Node>(elements?: T[], pos?: number): MutableNodeArray<T> { const array = <MutableNodeArray<T>>(elements || []); if (pos === undefined) { pos = this.scanner.getStartPos(); } array.pos = pos; array.end = pos; return array; } private createMissingNode<T extends Node>(kind: T['kind'], emitErrorMessage = true): T { if (emitErrorMessage) { this.parseErrorAtCurrentToken(`Missing node: ${getKindName(kind)}`); } return this.createNode(SyntaxKind.Unknown, undefined, false) as T; } private createMissingList<T extends Node>(): NodeArray<T> { return this.createNodeArray<T>(); } private finishNode<T extends Node>(node: T, end?: number, assignSyntaxTokens: boolean = true): T { node.end = end === undefined ? this.scanner.getStartPos() : end; if (assignSyntaxTokens) { node.syntaxTokens = this.syntaxTokens.pop(); for (const token of node.syntaxTokens) { token.parent = node; } } return node; } private isListTerminator(kind: ParsingContext): boolean { if (this.token() === SyntaxKind.EndOfFileToken) { // Being at the end of the file ends all lists. return true; } switch (kind) { case ParsingContext.SourceElements: return false; case ParsingContext.BlockStatements: case ParsingContext.StructMembers: return this.token() === SyntaxKind.CloseBraceToken; case ParsingContext.ArgumentExpressions: case ParsingContext.Parameters: return this.token() === SyntaxKind.CloseParenToken; case ParsingContext.TypeArguments: return this.token() === SyntaxKind.GreaterThanToken; } } private parsingContextErrors(context: ParsingContext): string { switch (context) { case ParsingContext.SourceElements: return 'expected declaration'; case ParsingContext.BlockStatements: return 'expected declaration or statement'; case ParsingContext.StructMembers: return 'expected property declaration'; case ParsingContext.TypeArguments: return 'expected type argumnt definition'; case ParsingContext.ArgumentExpressions: return 'expected argumnt expression'; case ParsingContext.Parameters: return 'expected parameter declaration'; } } private isListElement(parsingContext: ParsingContext, inErrorRecovery: boolean): boolean { switch (parsingContext) { case ParsingContext.SourceElements: return this.isStartOfRootStatement(); case ParsingContext.BlockStatements: return this.isStartOfStatement(); case ParsingContext.StructMembers: return this.isStartOfTypeDefinition(); case ParsingContext.TypeArguments: return this.isStartOfTypeDefinition(); case ParsingContext.Parameters: return this.isStartOfParameter(); case ParsingContext.ArgumentExpressions: return this.isStartOfExpression(); } } private parseList<T extends Node>(kind: ParsingContext, parseElement: () => T): NodeArray<T> { const saveParsingContext = this.parsingContext; this.parsingContext |= 1 << kind; const result = this.createNodeArray<T>(); while (!this.isListTerminator(kind)) { if (this.isListElement(kind, false)) { result.push(parseElement()); continue; } const start = this.scanner.getTokenPos(); this.nextToken(); this.parseErrorAtPosition(start, this.scanner.getTokenPos() - start, this.parsingContextErrors(kind)); if (kind !== ParsingContext.SourceElements && kind !== ParsingContext.BlockStatements) { break; } } result.end = this.scanner.getTokenPos(); this.parsingContext = saveParsingContext; return result; } private parseBracketedList<T extends Node>(kind: ParsingContext, parseElement: () => T, open: SyntaxKind, close: SyntaxKind): NodeArray<T> { if (this.parseExpected(open)) { const result = this.parseDelimitedList(kind, parseElement); this.parseExpected(close); return result; } return this.createMissingList<T>(); } private parseDelimitedList<T extends Node>(kind: ParsingContext, parseElement: () => T): NodeArray<T> { const saveParsingContext = this.parsingContext; this.parsingContext |= 1 << kind; const result = this.createNodeArray<T>(); let commaStart = -1; // Meaning the previous token was not a comma while (true) { if (this.isListElement(kind, false)) { const startPos = this.scanner.getTokenPos(); result.push(parseElement()); commaStart = this.scanner.getTokenPos(); if (this.parseOptional(SyntaxKind.CommaToken)) { // No need to check for a zero length node since we know we parsed a comma continue; } commaStart = -1; // Back to the state where the last token was not a comma if (this.isListTerminator(kind)) { break; } // We didn't get a comma, and the list wasn't terminated, explicitly parse // out a comma so we give a good error message. this.parseExpected(SyntaxKind.CommaToken); continue; } else if (this.token() === SyntaxKind.CommaToken) { // If list element was *invalid* it might as well be empty // swallow the comma and try again this.parseErrorAtCurrentToken(this.parsingContextErrors(kind)); this.nextToken(); continue; } if (this.isListTerminator(kind)) { break; } this.parseErrorAtCurrentToken(this.parsingContextErrors(kind)); this.nextToken(); // give up, even if list wasn't properly terminated. because it might never be, if termination character is missing.. break; } if (commaStart >= 0) { this.parseErrorAtPosition(commaStart, 1, 'trailing comma'); } result.end = this.scanner.getTokenPos(); this.parsingContext = saveParsingContext; return result; } private isVariableDeclaration(): boolean { while (this.token() === SyntaxKind.ConstKeyword || this.token() === SyntaxKind.StaticKeyword) { this.nextToken(); } if (!isKeywordTypeKind(this.token()) && this.token() !== SyntaxKind.Identifier) { return false; } this.parseTypeDefinition(); if (this.token() !== SyntaxKind.Identifier) { return false; } this.nextToken(); switch (this.token()) { // we're expecting ";" or "=", but let's allow everything else than "(" for better error tolerance // "(" indicates that it might be CallExpression or FunctionDeclaration case SyntaxKind.OpenParenToken: return false; } return true; } private isFunctionDeclaration(): boolean { while (this.token() === SyntaxKind.NativeKeyword || this.token() === SyntaxKind.StaticKeyword) { this.nextToken(); } if (!isKeywordTypeKind(this.token()) && this.token() !== SyntaxKind.Identifier) { return false; } this.parseTypeDefinition(); if (this.token() !== SyntaxKind.Identifier) { return false; } this.nextToken(); if (this.token() !== SyntaxKind.OpenParenToken) { return false; } return true; } private isParameter(): boolean { this.parseTypeDefinition(); if (this.token() !== SyntaxKind.Identifier) { return false; } return true; } private isStartOfExpression(): boolean { if (this.isStartOfLeftHandSideExpression()) { return true; } switch (this.token()) { case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: case SyntaxKind.TildeToken: case SyntaxKind.ExclamationToken: case SyntaxKind.PlusPlusToken: case SyntaxKind.MinusMinusToken: return true; default: // Error tolerance. If we see the start of some binary operator, we consider // that the start of an expression. That way we'll parse out a missing identifier, // give a good message about an identifier being missing, and then consume the // rest of the binary expression. if (this.isBinaryOperator()) { return true; } return false; } } private isStartOfStatement(): boolean { switch (this.token()) { case SyntaxKind.StructKeyword: case SyntaxKind.IncludeKeyword: case SyntaxKind.TypedefKeyword: return false; } return true; } private isStartOfVariableDeclaration(): boolean { return this.lookAhead(this.isVariableDeclaration.bind(this)); } private isStartOfFunctionDeclaration(): boolean { return this.lookAhead(this.isFunctionDeclaration.bind(this)); } private isStartOfRootStatement(): boolean { switch (this.token()) { case SyntaxKind.IfKeyword: case SyntaxKind.DoKeyword: case SyntaxKind.WhileKeyword: case SyntaxKind.ForKeyword: case SyntaxKind.ContinueKeyword: case SyntaxKind.BreakKeyword: case SyntaxKind.ReturnKeyword: case SyntaxKind.BreakpointKeyword: case SyntaxKind.OpenBraceToken: return false; } return true; } private isStartOfTypeDefinition(): boolean { return isKeywordTypeKind(this.token()) || this.token() === SyntaxKind.Identifier; } private isStartOfParameter(): boolean { return this.lookAhead(this.isParameter.bind(this)); } private parseLiteral(kind: Types.SyntaxKind): Types.Literal { const node = <Types.Literal>this.createNode(kind, void 0, false); node.end = this.scanner.getCurrentPos(); node.value = this.scanner.getTokenValue() || ''; node.text = this.scanner.getTokenText() || ''; if (this.parseExpected(kind, void 0, false)) { this.nextToken(); } return this.finishNode(node, node.end, false); } private parseInclude(): Types.IncludeStatement { const node = <Types.IncludeStatement>this.createNode(SyntaxKind.IncludeStatement); this.parseExpected(SyntaxKind.IncludeKeyword); node.path = <Types.StringLiteral>this.parseLiteral(SyntaxKind.StringLiteral); return this.finishNode(node); } private parseIdentifier(alwaysAdvance: boolean = true): Types.Identifier { const identifier = <Types.Identifier>this.createNode(SyntaxKind.Identifier); this.parseExpected(SyntaxKind.Identifier, null, false); identifier.name = this.scanner.getTokenValue() || ''; if (alwaysAdvance || this.token() === SyntaxKind.Identifier) { this.nextToken(); } return this.finishNode(identifier); } private parseExpectedIdentifier(): Types.Identifier { return this.parseIdentifier(false); } private parseTypeDefinition(): Types.TypeNode { let baseType: Types.TypeNode; if (this.token() === SyntaxKind.Identifier) { baseType = this.parseIdentifier(); } else if (isKeywordTypeKind(this.token())) { baseType = this.parseTokenNode(); } else { this.parseErrorAtCurrentToken('expected identifier or keyword'); baseType = this.createMissingNode(SyntaxKind.Identifier); } if (isReferenceKeywordKind(baseType.kind)) { if (this.token() === SyntaxKind.LessThanToken) { const mappedType = <Types.MappedTypeNode>this.createNode(SyntaxKind.MappedType, baseType.pos); mappedType.returnType = baseType; mappedType.typeArguments = this.parseBracketedList(ParsingContext.TypeArguments, this.parseTypeDefinition.bind(this), SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken); baseType = this.finishNode(mappedType) } } while (this.token() === SyntaxKind.OpenBracketToken) { let arrayType = <Types.ArrayTypeNode>this.createNode(SyntaxKind.ArrayType, baseType.pos); this.parseExpected(SyntaxKind.OpenBracketToken) arrayType.size = this.parseExpectedExpression(); arrayType.elementType = baseType; this.parseExpected(SyntaxKind.CloseBracketToken); baseType = this.finishNode(arrayType) } return baseType; } private parseParameter(): Types.ParameterDeclaration { const param = <Types.ParameterDeclaration>this.createNode(SyntaxKind.ParameterDeclaration); param.type = this.parseTypeDefinition(); param.name = this.parseIdentifier(); return this.finishNode(param) } private parsePropertyDeclaration(): Types.PropertyDeclaration { const property = <Types.PropertyDeclaration>this.createNode(SyntaxKind.PropertyDeclaration); property.type = this.parseTypeDefinition(); property.name = this.parseExpectedIdentifier(); this.parseExpected(SyntaxKind.SemicolonToken); return this.finishNode(property) } private parseStructDeclaration(): Types.StructDeclaration { const node = <Types.StructDeclaration>this.createNode(SyntaxKind.StructDeclaration); this.parseExpected(SyntaxKind.StructKeyword); node.name = this.parseIdentifier(); this.parseExpected(SyntaxKind.OpenBraceToken); node.members = this.parseList(ParsingContext.StructMembers, this.parsePropertyDeclaration.bind(this)); this.parseExpected(SyntaxKind.CloseBraceToken); this.parseExpected(SyntaxKind.SemicolonToken); return this.finishNode(node); } private parseModifiers(): NodeArray<Types.Modifier> { let mods = this.createNodeArray<Types.Modifier>(); while (isModifierKind(this.token())) { mods.push(this.parseTokenNode()); } mods.end = this.scanner.getTokenPos(); return mods; } private parseFunctionDeclaration(): Types.FunctionDeclaration { const func = <Types.FunctionDeclaration>this.createNode(SyntaxKind.FunctionDeclaration); func.modifiers = this.parseModifiers(); func.type = this.parseTypeDefinition(); func.name = this.parseIdentifier(); func.parameters = this.parseBracketedList(ParsingContext.Parameters, this.parseParameter.bind(this), SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken); if (this.token() === SyntaxKind.OpenBraceToken) { func.body = this.parseBlock(true); } else { this.parseExpected(SyntaxKind.SemicolonToken); } return this.finishNode(func); } private parseVariableDeclaration(): Types.VariableDeclaration { const variable = <Types.VariableDeclaration>this.createNode(SyntaxKind.VariableDeclaration); variable.modifiers = this.parseModifiers(); variable.type = this.parseTypeDefinition(); variable.name = this.parseIdentifier(); if (this.token() === SyntaxKind.EqualsToken) { this.parseExpected(SyntaxKind.EqualsToken); variable.initializer = this.parseBinaryExpressionOrHigher(0); } this.parseExpected(SyntaxKind.SemicolonToken); return this.finishNode(variable); } private parseBlock(allowVarDeclarations = false): Types.Block { if (this.parseExpected(SyntaxKind.OpenBraceToken, null, false)) { const node = <Types.Block>this.createNode(SyntaxKind.Block); this.parseExpected(SyntaxKind.OpenBraceToken); node.statements = this.parseList(ParsingContext.BlockStatements, () => { const child = this.parseStatement(); if (child.kind === SyntaxKind.VariableDeclaration) { if (!allowVarDeclarations) { this.parseErrorAtPosition(child.pos, child.end - child.pos, 'Local variables must be declared at the begining of function block'); } } else { allowVarDeclarations = false; } return child; }); this.parseExpected(SyntaxKind.CloseBraceToken); return this.finishNode(node); } else { return this.createMissingNode(SyntaxKind.Block); } } private isUpdateExpression(): boolean { // This function is called inside parseUnaryExpression to decide // whether to call parseSimpleUnaryExpression or call parseUpdateExpression directly switch (this.token()) { case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: case SyntaxKind.TildeToken: case SyntaxKind.ExclamationToken: return false; default: return true; } } private isStartOfLeftHandSideExpression(): boolean { switch (this.token()) { case SyntaxKind.NullKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: case SyntaxKind.OpenParenToken: case SyntaxKind.Identifier: return true; default: return false; } } private makeBinaryExpression(left: Types.Expression, operatorToken: Types.BinaryOperatorToken, right: Types.Expression): Types.BinaryExpression { const node = <Types.BinaryExpression>this.createNode(SyntaxKind.BinaryExpression, left.pos); node.left = left; node.operatorToken = operatorToken; node.right = right; return this.finishNode(node); } private isBinaryOperator() { return this.getBinaryOperatorPrecedence() > 0; } private getBinaryOperatorPrecedence(): number { switch (this.token()) { case SyntaxKind.BarBarToken: return 1; case SyntaxKind.AmpersandAmpersandToken: return 2; case SyntaxKind.BarToken: return 3; case SyntaxKind.CaretToken: return 4; case SyntaxKind.AmpersandToken: return 5; case SyntaxKind.EqualsEqualsToken: case SyntaxKind.ExclamationEqualsToken: return 6; case SyntaxKind.LessThanToken: case SyntaxKind.GreaterThanToken: case SyntaxKind.LessThanEqualsToken: case SyntaxKind.GreaterThanEqualsToken: return 7; case SyntaxKind.LessThanLessThanToken: case SyntaxKind.GreaterThanGreaterThanToken: return 8; case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: return 9; case SyntaxKind.AsteriskToken: case SyntaxKind.SlashToken: case SyntaxKind.PercentToken: return 10; } // -1 is lower than all other precedences. Returning it will cause binary expression // parsing to stop. return -1; } private parsePrimaryExpression(): Types.PrimaryExpression { switch (this.token()) { case SyntaxKind.NumericLiteral: case SyntaxKind.StringLiteral: return this.parseLiteral(this.token()); case SyntaxKind.NullKeyword: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: return this.parseTokenNode<Types.PrimaryExpression>(); case SyntaxKind.OpenParenToken: return this.parseParenthesizedExpression(); case SyntaxKind.Identifier: return this.parseIdentifier(); } this.parseErrorAtCurrentToken(`Invalid expression`); return this.createNode(SyntaxKind.Unknown, undefined, false); } private parseParenthesizedExpression(): Types.ParenthesizedExpression { const node = <Types.ParenthesizedExpression>this.createNode(SyntaxKind.ParenthesizedExpression); this.parseExpected(SyntaxKind.OpenParenToken); node.expression = this.parseExpectedExpression(); this.parseExpected(SyntaxKind.CloseParenToken); return this.finishNode(node); } private parseMemberExpressionOrHigher(): Types.MemberExpression { const expression = this.parsePrimaryExpression(); return this.parseMemberExpressionRest(expression); } private parseMemberExpressionRest(expression: Types.LeftHandSideExpression): Types.MemberExpression { while (true) { if (this.token() === SyntaxKind.DotToken) { const propertyAccess = <Types.PropertyAccessExpression>this.createNode(SyntaxKind.PropertyAccessExpression, expression.pos); this.parseExpected(SyntaxKind.DotToken); propertyAccess.expression = expression; propertyAccess.name = this.parseExpectedIdentifier(); expression = this.finishNode(propertyAccess); continue; } if (this.token() === SyntaxKind.OpenBracketToken) { const indexedAccess = <Types.ElementAccessExpression>this.createNode(SyntaxKind.ElementAccessExpression, expression.pos); this.parseExpected(SyntaxKind.OpenBracketToken); indexedAccess.expression = expression; indexedAccess.argumentExpression = this.parseExpectedExpression(); this.parseExpected(SyntaxKind.CloseBracketToken); expression = this.finishNode(indexedAccess); continue; } return <Types.MemberExpression>expression; } } private parseCallExpressionRest(expression: Types.LeftHandSideExpression): Types.LeftHandSideExpression { while (true) { expression = this.parseMemberExpressionRest(expression); if (this.token() === SyntaxKind.OpenParenToken) { const callExpr = <Types.CallExpression>this.createNode(SyntaxKind.CallExpression, expression.pos); callExpr.expression = expression; this.parseExpected(SyntaxKind.OpenParenToken); callExpr.arguments = this.parseDelimitedList(ParsingContext.ArgumentExpressions, this.parseExpression.bind(this)); this.parseExpected(SyntaxKind.CloseParenToken); expression = this.finishNode(callExpr); continue; } return expression; } } private parseLeftHandSideExpressionOrHigher(): Types.LeftHandSideExpression { let expression: Types.MemberExpression; expression = this.parseMemberExpressionOrHigher(); return this.parseCallExpressionRest(expression); } private parseUpdateExpression(): Types.UpdateExpression { if (this.token() === SyntaxKind.PlusPlusToken || this.token() === SyntaxKind.MinusMinusToken) { this.parseErrorAtCurrentToken('unary increment operators not allowed'); const node = <Types.PrefixUnaryExpression>this.createNode(SyntaxKind.PrefixUnaryExpression); node.operator = this.parseTokenNode(); node.operand = this.parseLeftHandSideExpressionOrHigher(); return this.finishNode(node); } const expression = this.parseLeftHandSideExpressionOrHigher(); if ((this.token() === SyntaxKind.PlusPlusToken || this.token() === SyntaxKind.MinusMinusToken)) { this.parseErrorAtCurrentToken('unary increment operators not supported'); const node = <Types.PostfixUnaryExpression>this.createNode(SyntaxKind.PostfixUnaryExpression, expression.pos); node.operand = expression; node.operator = this.parseTokenNode(); return this.finishNode(node); } return expression; } private parsePrefixUnaryExpression() { const node = <Types.PrefixUnaryExpression>this.createNode(SyntaxKind.PrefixUnaryExpression); node.operator = this.parseTokenNode(); node.operand = this.parseSimpleUnaryExpression(); return this.finishNode(node); } private parseSimpleUnaryExpression(): Types.UnaryExpression { switch (this.token()) { case SyntaxKind.PlusToken: case SyntaxKind.MinusToken: case SyntaxKind.TildeToken: case SyntaxKind.ExclamationToken: return this.parsePrefixUnaryExpression(); default: return this.parseUpdateExpression(); } } private parseUnaryExpressionOrHigher(): Types.UnaryExpression | Types.BinaryExpression { /** * UpdateExpression: * 1) LeftHandSideExpression * 2) LeftHandSideExpression++ * 3) LeftHandSideExpression-- * 4) ++UnaryExpression * 5) --UnaryExpression */ if (this.isUpdateExpression()) { return this.parseUpdateExpression(); } /** * UnaryExpression: * 1) UpdateExpression * 2) + UpdateExpression * 3) - UpdateExpression * 4) ~ UpdateExpression * 5) ! UpdateExpression */ return this.parseSimpleUnaryExpression(); } private parseBinaryExpressionOrHigher(precedence: number): Types.Expression { const leftOperand = this.parseUnaryExpressionOrHigher(); return this.parseBinaryExpressionRest(precedence, leftOperand); } private parseBinaryExpressionRest(precedence: number, leftOperand: Types.Expression): Types.Expression { while (true) { const newPrecedence = this.getBinaryOperatorPrecedence(); // Check the precedence to see if we should "take" this operator // - For left associative operator, consume the operator, // recursively call the function below, and parse binaryExpression as a rightOperand // of the caller if the new precedence of the operator is greater then or equal to the current precedence. // For example: // a - b - c; // ^token; leftOperand = b. Return b to the caller as a rightOperand // a * b - c // ^token; leftOperand = b. Return b to the caller as a rightOperand // a - b * c; // ^token; leftOperand = b. Return b * c to the caller as a rightOperand const consumeCurrentOperator = newPrecedence > precedence; if (!consumeCurrentOperator) { break; } leftOperand = this.makeBinaryExpression(leftOperand, <Types.BinaryOperatorToken>this.parseTokenNode(), this.parseBinaryExpressionOrHigher(newPrecedence)); } return leftOperand; } private parseAssignmentExpressionOrHigher(): Types.Expression { let expr = this.parseBinaryExpressionOrHigher(0); if (isLeftHandSideExpression(expr) && isAssignmentOperator(this.token())) { // multiple assigments in single statement is not allowed // return this.makeBinaryExpression(expr, <Types.BinaryOperatorToken>this.parseTokenNode(), this.parseAssignmentExpressionOrHigher()); return this.makeBinaryExpression(expr, <Types.BinaryOperatorToken>this.parseTokenNode(), this.parseBinaryExpressionOrHigher(0)); } return expr; } private parseExpression(allowAssignment: boolean = false): Types.Expression { const expr = this.parseAssignmentExpressionOrHigher(); if (!allowAssignment && isAssignmentExpression(expr)) { this.parseErrorAtPosition(expr.pos, expr.end - expr.pos, `Assignment expression not allowed in this context`); } return expr; } private parseExpectedExpression(allowAssignment: boolean = false): Types.Expression | null { if (this.isStartOfExpression()) { return this.parseExpression(allowAssignment); } else { this.parseErrorAtCurrentToken('Expected expression'); return this.createNode(SyntaxKind.Unknown, undefined, false); } } private parseTypedefDeclaration(): Types.TypedefDeclaration { const node = <Types.TypedefDeclaration>this.createNode(SyntaxKind.TypedefDeclaration); this.parseExpected(SyntaxKind.TypedefKeyword); node.type = this.parseTypeDefinition(); node.name = this.parseIdentifier(); return this.finishNode(node); } private parseReturnStatement(): Types.ReturnStatement { const node = <Types.ReturnStatement>this.createNode(SyntaxKind.ReturnStatement); this.parseExpected(SyntaxKind.ReturnKeyword); if (this.token() !== SyntaxKind.SemicolonToken) { node.expression = this.parseExpectedExpression(); } this.parseExpected(SyntaxKind.SemicolonToken); return this.finishNode(node); } private parseBreakOrContinueStatement(kind: SyntaxKind): Types.BreakOrContinueStatement { const node = <Types.BreakOrContinueStatement>this.createNode(kind); this.parseExpected(kind === SyntaxKind.BreakStatement ? SyntaxKind.BreakKeyword : SyntaxKind.ContinueKeyword); this.parseExpected(SyntaxKind.SemicolonToken); return this.finishNode(node); } private parseBreakpointStatement(): Types.BreakpointStatement { const node = <Types.BreakpointStatement>this.createNode(SyntaxKind.BreakpointStatement); this.parseExpected(SyntaxKind.BreakpointKeyword); this.parseExpected(SyntaxKind.SemicolonToken); return this.finishNode(node); } private parseExpressionStatement(): Types.ExpressionStatement { const node = <Types.ExpressionStatement>this.createNode(SyntaxKind.ExpressionStatement); node.expression = this.parseAssignmentExpressionOrHigher(); this.parseExpected(SyntaxKind.SemicolonToken); this.finishNode(node); switch (node.expression.kind) { case Types.SyntaxKind.CallExpression: break; case Types.SyntaxKind.BinaryExpression: if (isAssignmentOperator((<Types.BinaryExpression>node.expression).operatorToken.kind)) break; /* falls through */ default: this.parseErrorAtPosition(node.pos, node.end - node.pos, 'Statement has no effect'); } return node; } private parseEmptyStatement(): Types.EmptyStatement { const node = <Types.EmptyStatement>this.createNode(SyntaxKind.EmptyStatement); this.parseExpected(SyntaxKind.SemicolonToken); return this.finishNode(node); } private parseIfStatement(): Types.IfStatement { const node = <Types.IfStatement>this.createNode(SyntaxKind.IfStatement); this.parseExpected(SyntaxKind.IfKeyword); this.parseExpected(SyntaxKind.OpenParenToken); node.expression = this.parseExpectedExpression(); this.parseExpected(SyntaxKind.CloseParenToken); node.thenStatement = this.parseBlock(); if (this.parseOptional(SyntaxKind.ElseKeyword)) { node.elseStatement = this.token() === SyntaxKind.IfKeyword ? this.parseIfStatement() : this.parseBlock(); } return this.finishNode(node); } private parseDoStatement(): Types.DoStatement { const node = <Types.DoStatement>this.createNode(SyntaxKind.DoStatement); this.parseExpected(SyntaxKind.DoKeyword); node.statement = this.parseBlock(); this.parseExpected(SyntaxKind.WhileKeyword); this.parseExpected(SyntaxKind.OpenParenToken); node.expression = this.parseExpectedExpression(); this.parseExpected(SyntaxKind.CloseParenToken); this.parseExpected(SyntaxKind.SemicolonToken); return this.finishNode(node); } private parseWhileStatement(): Types.WhileStatement { const node = <Types.WhileStatement>this.createNode(SyntaxKind.WhileStatement); this.parseExpected(SyntaxKind.WhileKeyword); this.parseExpected(SyntaxKind.OpenParenToken); node.expression = this.parseExpectedExpression(); this.parseExpected(SyntaxKind.CloseParenToken); node.statement = this.parseBlock(); return this.finishNode(node); } private parseForStatement(): Types.ForStatement { const node = <Types.ForStatement>this.createNode(SyntaxKind.ForStatement); this.parseExpected(SyntaxKind.ForKeyword); this.parseExpected(SyntaxKind.OpenParenToken); if (this.token() !== SyntaxKind.SemicolonToken && this.token() !== SyntaxKind.CloseParenToken) { node.initializer = this.parseExpectedExpression(true); } this.parseExpected(SyntaxKind.SemicolonToken); if (this.token() !== SyntaxKind.SemicolonToken && this.token() !== SyntaxKind.CloseParenToken) { node.condition = this.parseExpectedExpression(); } this.parseExpected(SyntaxKind.SemicolonToken); if (this.token() !== SyntaxKind.CloseParenToken) { node.incrementor = this.parseExpectedExpression(true); } this.parseExpected(SyntaxKind.CloseParenToken); node.statement = this.parseBlock(); return this.finishNode(node); } private parseStatement(): Types.Statement { switch (this.token()) { case SyntaxKind.SemicolonToken: return this.parseEmptyStatement(); case SyntaxKind.IncludeKeyword: return this.parseInclude(); case SyntaxKind.StructKeyword: return this.parseStructDeclaration(); case SyntaxKind.IfKeyword: return this.parseIfStatement(); case SyntaxKind.DoKeyword: return this.parseDoStatement(); case SyntaxKind.WhileKeyword: return this.parseWhileStatement(); case SyntaxKind.ForKeyword: return this.parseForStatement(); case SyntaxKind.ContinueKeyword: return this.parseBreakOrContinueStatement(SyntaxKind.ContinueStatement); case SyntaxKind.BreakKeyword: return this.parseBreakOrContinueStatement(SyntaxKind.BreakStatement); case SyntaxKind.BreakpointKeyword: return this.parseBreakpointStatement(); case SyntaxKind.ReturnKeyword: return this.parseReturnStatement(); case SyntaxKind.TypedefKeyword: return this.parseTypedefDeclaration(); case SyntaxKind.OpenBraceToken: return this.parseBlock(false); case SyntaxKind.Identifier: case SyntaxKind.ConstKeyword: case SyntaxKind.StaticKeyword: case SyntaxKind.NativeKeyword: case SyntaxKind.AbilcmdKeyword: case SyntaxKind.ActorKeyword: case SyntaxKind.ActorscopeKeyword: case SyntaxKind.AifilterKeyword: case SyntaxKind.BankKeyword: case SyntaxKind.BitmaskKeyword: case SyntaxKind.BoolKeyword: case SyntaxKind.ByteKeyword: case SyntaxKind.CamerainfoKeyword: case SyntaxKind.CharKeyword: case SyntaxKind.ColorKeyword: case SyntaxKind.DoodadKeyword: case SyntaxKind.DatetimeKeyword: case SyntaxKind.FixedKeyword: case SyntaxKind.HandleKeyword: case SyntaxKind.GenerichandleKeyword: case SyntaxKind.EffecthistoryKeyword: case SyntaxKind.IntKeyword: case SyntaxKind.MarkerKeyword: case SyntaxKind.OrderKeyword: case SyntaxKind.PlayergroupKeyword: case SyntaxKind.PointKeyword: case SyntaxKind.RegionKeyword: case SyntaxKind.RevealerKeyword: case SyntaxKind.SoundKeyword: case SyntaxKind.SoundlinkKeyword: case SyntaxKind.StringKeyword: case SyntaxKind.TextKeyword: case SyntaxKind.TimerKeyword: case SyntaxKind.TransmissionsourceKeyword: case SyntaxKind.TriggerKeyword: case SyntaxKind.UnitKeyword: case SyntaxKind.UnitfilterKeyword: case SyntaxKind.UnitgroupKeyword: case SyntaxKind.UnitrefKeyword: case SyntaxKind.VoidKeyword: case SyntaxKind.WaveKeyword: case SyntaxKind.WaveinfoKeyword: case SyntaxKind.WavetargetKeyword: case SyntaxKind.ArrayrefKeyword: case SyntaxKind.StructrefKeyword: case SyntaxKind.FuncrefKeyword: if (!(this.parsingContext & (1 << ParsingContext.BlockStatements)) && this.isStartOfFunctionDeclaration()) { return this.parseFunctionDeclaration(); } else if (this.isStartOfVariableDeclaration()) { return this.parseVariableDeclaration(); } else if ( ( this.parsingContext & (1 << ParsingContext.SourceElements) || this.parsingContext & (1 << ParsingContext.BlockStatements) || this.parsingContext & (1 << ParsingContext.TypeArguments) || this.parsingContext & (1 << ParsingContext.ArgumentExpressions) ) && this.isStartOfExpression() ) { return this.parseExpressionStatement(); } /* falls through */ default: this.parseErrorAtCurrentToken(`Expected declaration or statement, found ${getKindName(this.token())}`); const node = this.createMissingNode(SyntaxKind.ExpressionStatement, false); this.nextToken(); return node; } } constructor() { this.scanner = new Scanner((message: Types.DiagnosticMessage, pos: number, length: number) => { this.parseErrorAtPosition(pos, length, message.message); }); } public setText(text: string) { this.scanner.setText(text); } public parseFile(fileName: string, text: string): Types.SourceFile { this.scanner.setText(text); this.syntaxTokens = []; this.sourceFile = <Types.SourceFile>this.createNode(SyntaxKind.SourceFile, 0); this.sourceFile.commentsLineMap = new Map<number, Types.Token<SyntaxKind.SingleLineCommentTrivia>>(); this.sourceFile.parseDiagnostics = []; this.sourceFile.bindDiagnostics = []; this.sourceFile.additionalSyntacticDiagnostics = []; this.sourceFile.fileName = fileName; this.nextToken(); this.sourceFile.statements = this.parseList(ParsingContext.SourceElements, this.parseStatement.bind(this)); this.finishNode(this.sourceFile); this.sourceFile.lineMap = this.scanner.getLineMap(); this.sourceFile.text = text; fixupParentReferences(this.sourceFile); return this.sourceFile; } }