Code review comment: Since there is only one usage, no need to define a separate variable. ```suggestion         switch (nextToken.kind) { ```

Code under review:
private boolean isServiceDeclStart(ParserRuleContext currentContext, int lookahead) {
        
        switch (peek(lookahead + 1).kind) {
            case IDENTIFIER_TOKEN:
                SyntaxKind tokenAfterIdentifier = peek(lookahead + 2).kind;
                switch (tokenAfterIdentifier) {
                    case ON_KEYWORD: 
                    case OPEN_BRACE_TOKEN: 
                        return true;
                    case EQUAL_TOKEN: 
                    case SEMICOLON_TOKEN: 
                    case QUESTION_MARK_TOKEN: 
                        return false;
                    default:
                        
                        return false;
                }
            case ON_KEYWORD:
                
                
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse listener declaration, given the qualifier.
     * <p>
     * <code>
     * listener-decl := metadata [public] listener [type-descriptor] variable-name = expression ;
     * </code>
     *
     * @param metadata  Metadata
     * @param qualifier Qualifier that precedes the listener declaration
     * @return Parsed node
     */
    private STNode parseListenerDeclaration(STNode metadata, STNode qualifier) {
        startContext(ParserRuleContext.LISTENER_DECL);
        STNode listenerKeyword = parseListenerKeyword();

        if (peek().kind == SyntaxKind.IDENTIFIER_TOKEN) {
            STNode listenerDecl =
                    parseConstantOrListenerDeclWithOptionalType(metadata, qualifier, listenerKeyword, true);
            endContext();
            return listenerDecl;
        }

        STNode typeDesc = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_BEFORE_IDENTIFIER);
        STNode variableName = parseVariableName();
        STNode equalsToken = parseAssignOp();
        STNode initializer = parseExpression();
        STNode semicolonToken = parseSemicolon();
        endContext();
        return STNodeFactory.createListenerDeclarationNode(metadata, qualifier, listenerKeyword, typeDesc, variableName,
                equalsToken, initializer, semicolonToken);
    }

    /**
     * Parse listener keyword.
     *
     * @return Parsed node
     */
    private STNode parseListenerKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.LISTENER_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.LISTENER_KEYWORD);
            return parseListenerKeyword();
        }
    }

    /**
     * Parse constant declaration, given the qualifier.
     * <p>
     * <code>module-const-decl := metadata [public] const [type-descriptor] identifier = const-expr ;</code>
     *
     * @param metadata  Metadata
     * @param qualifier Qualifier that precedes the listener declaration
     * @return Parsed node
     */
    private STNode parseConstantDeclaration(STNode metadata, STNode qualifier) {
        startContext(ParserRuleContext.CONSTANT_DECL);
        STNode constKeyword = parseConstantKeyword();
        
        return parseConstDecl(metadata, qualifier, constKeyword);
    }

    /**
     * Parse the components that follows after the const keyword of a constant declaration.
     *
     * @param metadata     Metadata
     * @param qualifier    Qualifier that precedes the constant decl
     * @param constKeyword Const keyword
     * @return Parsed node
     */
    private STNode parseConstDecl(STNode metadata, STNode qualifier, STNode constKeyword) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case ANNOTATION_KEYWORD:
                endContext();
                return parseAnnotationDeclaration(metadata, qualifier, constKeyword);
            case IDENTIFIER_TOKEN:
                STNode constantDecl =
                        parseConstantOrListenerDeclWithOptionalType(metadata, qualifier, constKeyword, false);
                endContext();
                return constantDecl;
            default:
                if (isTypeStartingToken(nextToken.kind)) {
                    break;
                }

                recover(peek(), ParserRuleContext.CONST_DECL_TYPE);
                return parseConstDecl(metadata, qualifier, constKeyword);
        }

        STNode typeDesc = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_BEFORE_IDENTIFIER);
        STNode variableName = parseVariableName();
        STNode equalsToken = parseAssignOp();
        STNode initializer = parseExpression();
        STNode semicolonToken = parseSemicolon();
        endContext();
        return STNodeFactory.createConstantDeclarationNode(metadata, qualifier, constKeyword, typeDesc, variableName,
                equalsToken, initializer, semicolonToken);
    }

    private STNode parseConstantOrListenerDeclWithOptionalType(STNode metadata, STNode qualifier, STNode constKeyword,
                                                               boolean isListener) {
        STNode varNameOrTypeName = parseStatementStartIdentifier();
        return parseConstantOrListenerDeclRhs(metadata, qualifier, constKeyword, varNameOrTypeName, isListener);
    }

    /**
     * Parse the component that follows the first identifier in a const decl. The identifier
     * can be either the type-name (a user defined type) or the var-name there the type-name
     * is not present.
     *
     * @param qualifier     Qualifier that precedes the constant decl
     * @param keyword       Keyword
     * @param typeOrVarName Identifier that follows the const-keywoord
     * @return Parsed node
     */
    private STNode parseConstantOrListenerDeclRhs(STNode metadata, STNode qualifier, STNode keyword,
                                                  STNode typeOrVarName, boolean isListener) {
        if (typeOrVarName.kind == SyntaxKind.QUALIFIED_NAME_REFERENCE) {
            STNode type = typeOrVarName;
            STNode variableName = parseVariableName();
            return parseListenerOrConstRhs(metadata, qualifier, keyword, isListener, type, variableName);
        }

        STNode type;
        STNode variableName;
        switch (peek().kind) {
            case IDENTIFIER_TOKEN:
                type = typeOrVarName;
                variableName = parseVariableName();
                break;
            case EQUAL_TOKEN:
                variableName = ((STSimpleNameReferenceNode) typeOrVarName).name; 
                type = STNodeFactory.createEmptyNode();
                break;
            default:
                recover(peek(), ParserRuleContext.CONST_DECL_RHS);
                return parseConstantOrListenerDeclRhs(metadata, qualifier, keyword, typeOrVarName, isListener);
        }

        return parseListenerOrConstRhs(metadata, qualifier, keyword, isListener, type, variableName);
    }

    private STNode parseListenerOrConstRhs(STNode metadata, STNode qualifier, STNode keyword, boolean isListener,
                                           STNode type, STNode variableName) {
        STNode equalsToken = parseAssignOp();
        STNode initializer = parseExpression();
        STNode semicolonToken = parseSemicolon();

        if (isListener) {
            return STNodeFactory.createListenerDeclarationNode(metadata, qualifier, keyword, type, variableName,
                    equalsToken, initializer, semicolonToken);
        }

        return STNodeFactory.createConstantDeclarationNode(metadata, qualifier, keyword, type, variableName,
                equalsToken, initializer, semicolonToken);
    }

    /**
     * Parse const keyword.
     *
     * @return Parsed node
     */
    private STNode parseConstantKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.CONST_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.CONST_KEYWORD);
            return parseConstantKeyword();
        }
    }

    /**
     * Parse typeof expression.
     * <p>
     * <code>
     * typeof-expr := typeof expression
     * </code>
     *
     * @param isRhsExpr
     * @return Typeof expression node
     */
    private STNode parseTypeofExpression(boolean isRhsExpr, boolean isInConditionalExpr) {
        STNode typeofKeyword = parseTypeofKeyword();

        
        
        STNode expr = parseExpression(OperatorPrecedence.UNARY, isRhsExpr, false, isInConditionalExpr);
        return STNodeFactory.createTypeofExpressionNode(typeofKeyword, expr);
    }

    /**
     * Parse typeof-keyword.
     *
     * @return Typeof-keyword node
     */
    private STNode parseTypeofKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.TYPEOF_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.TYPEOF_KEYWORD);
            return parseTypeofKeyword();
        }
    }

    /**
     * Parse optional type descriptor given the type.
     * <p>
     * <code>optional-type-descriptor := type-descriptor `?`</code>
     * </p>
     *
     * @param typeDescriptorNode Preceding type descriptor
     * @return Parsed node
     */
    private STNode parseOptionalTypeDescriptor(STNode typeDescriptorNode) {
        startContext(ParserRuleContext.OPTIONAL_TYPE_DESCRIPTOR);
        STNode questionMarkToken = parseQuestionMark();
        endContext();
        return createOptionalTypeDesc(typeDescriptorNode, questionMarkToken);
    }

    private STNode createOptionalTypeDesc(STNode typeDescNode, STNode questionMarkToken) {
        if (typeDescNode.kind == SyntaxKind.UNION_TYPE_DESC) {
            STUnionTypeDescriptorNode unionTypeDesc = (STUnionTypeDescriptorNode) typeDescNode;
            STNode middleTypeDesc = createOptionalTypeDesc(unionTypeDesc.rightTypeDesc, questionMarkToken);
            typeDescNode = mergeTypesWithUnion(unionTypeDesc.leftTypeDesc, unionTypeDesc.pipeToken, middleTypeDesc);
        } else if (typeDescNode.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
            STIntersectionTypeDescriptorNode intersectionTypeDesc = (STIntersectionTypeDescriptorNode) typeDescNode;
            STNode middleTypeDesc = createOptionalTypeDesc(intersectionTypeDesc.rightTypeDesc, questionMarkToken);
            typeDescNode = mergeTypesWithIntersection(intersectionTypeDesc.leftTypeDesc,
                    intersectionTypeDesc.bitwiseAndToken, middleTypeDesc);
        } else {
            typeDescNode = validateForUsageOfVar(typeDescNode);
            typeDescNode = STNodeFactory.createOptionalTypeDescriptorNode(typeDescNode, questionMarkToken);
        }

        return typeDescNode;
    }

    /**
     * Parse unary expression.
     * <p>
     * <code>
     * unary-expr := + expression | - expression | ~ expression | ! expression
     * </code>
     *
     * @param isRhsExpr
     * @return Unary expression node
     */
    private STNode parseUnaryExpression(boolean isRhsExpr, boolean isInConditionalExpr) {
        STNode unaryOperator = parseUnaryOperator();

        
        
        STNode expr = parseExpression(OperatorPrecedence.UNARY, isRhsExpr, false, isInConditionalExpr);
        return STNodeFactory.createUnaryExpressionNode(unaryOperator, expr);
    }

    /**
     * Parse unary operator.
     * <code>UnaryOperator := + | - | ~ | !</code>
     *
     * @return Parsed node
     */
    private STNode parseUnaryOperator() {
        STToken token = peek();
        if (isUnaryOperator(token.kind)) {
            return consume();
        } else {
            recover(token, ParserRuleContext.UNARY_OPERATOR);
            return parseUnaryOperator();
        }
    }

    /**
     * Check whether the given token kind is a unary operator.
     *
     * @param kind STToken kind
     * @return <code>true</code> if the token kind refers to a unary operator. <code>false</code> otherwise
     */
    private boolean isUnaryOperator(SyntaxKind kind) {
        switch (kind) {
            case PLUS_TOKEN:
            case MINUS_TOKEN:
            case NEGATION_TOKEN:
            case EXCLAMATION_MARK_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse array type descriptor.
     * <p>
     * <code>
     * array-type-descriptor := array-member-type-descriptor [ [ array-length ] ]
     * array-member-type-descriptor := type-descriptor
     * array-length :=
     * int-literal
     * | constant-reference-expr
     * | inferred-array-length
     * inferred-array-length := *
     * </code>
     * </p>
     *
     * @param memberTypeDesc
     * @return Parsed Node
     */
    private STNode parseArrayTypeDescriptor(STNode memberTypeDesc) {
        startContext(ParserRuleContext.ARRAY_TYPE_DESCRIPTOR);
        STNode openBracketToken = parseOpenBracket();
        STNode arrayLengthNode = parseArrayLength();
        STNode closeBracketToken = parseCloseBracket();
        endContext();
        return createArrayTypeDesc(memberTypeDesc, openBracketToken, arrayLengthNode, closeBracketToken);
    }

    private STNode createArrayTypeDesc(STNode memberTypeDesc, STNode openBracketToken, STNode arrayLengthNode,
                                       STNode closeBracketToken) {
        memberTypeDesc = validateForUsageOfVar(memberTypeDesc);
        if (arrayLengthNode != null) {
            switch (arrayLengthNode.kind) {
                case ASTERISK_LITERAL:
                case SIMPLE_NAME_REFERENCE:
                case QUALIFIED_NAME_REFERENCE:
                    break;
                case NUMERIC_LITERAL:
                    SyntaxKind numericLiteralKind = arrayLengthNode.childInBucket(0).kind;
                    if (numericLiteralKind == SyntaxKind.DECIMAL_INTEGER_LITERAL_TOKEN ||
                            numericLiteralKind == SyntaxKind.HEX_INTEGER_LITERAL_TOKEN) {
                        break;
                    }
                    
                default:
                    openBracketToken = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(openBracketToken,
                            arrayLengthNode, DiagnosticErrorCode.ERROR_INVALID_ARRAY_LENGTH);
                    arrayLengthNode = STNodeFactory.createEmptyNode();
            }
        }
        
        
        List<STNode> arrayDimensions = new ArrayList();
        if (memberTypeDesc.kind == SyntaxKind.ARRAY_TYPE_DESC) {
            STArrayTypeDescriptorNode innerArrayType = (STArrayTypeDescriptorNode) memberTypeDesc;
            STNode innerArrayDimensions = innerArrayType.dimensions;
            int dimensionCount = innerArrayDimensions.bucketCount();
            
            for (int i = 0; i < dimensionCount; i++) {
                arrayDimensions.add(innerArrayDimensions.childInBucket(i));
            }
            memberTypeDesc = innerArrayType.memberTypeDesc;
        }
        
        STNode arrayDimension = STNodeFactory.createArrayDimensionNode(openBracketToken, arrayLengthNode, 
                closeBracketToken);
        arrayDimensions.add(arrayDimension);
        STNode arrayDimensionNodeList = STNodeFactory.createNodeList(arrayDimensions);
        
        return STNodeFactory.createArrayTypeDescriptorNode(memberTypeDesc, arrayDimensionNodeList);
    }

    /**
     * Parse array length.
     * <p>
     * <code>
     * array-length :=
     * int-literal
     * | constant-reference-expr
     * | inferred-array-length
     * constant-reference-expr := variable-reference-expr
     * </code>
     * </p>
     *
     * @return Parsed array length
     */
    private STNode parseArrayLength() {
        STToken token = peek();
        switch (token.kind) {
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case ASTERISK_TOKEN:
                return parseBasicLiteral();
            case CLOSE_BRACKET_TOKEN:
                return STNodeFactory.createEmptyNode();
            
            case IDENTIFIER_TOKEN:
                return parseQualifiedIdentifier(ParserRuleContext.ARRAY_LENGTH);
            default:
                recover(token, ParserRuleContext.ARRAY_LENGTH);
                return parseArrayLength();
        }
    }

    /**
     * Parse annotations.
     * <p>
     * <i>Note: In the <a href="https:
     * annotations-list is specified as one-or-more annotations. And the usage is marked as
     * optional annotations-list. However, for the consistency of the tree, here we make the
     * annotation-list as zero-or-more annotations, and the usage is not-optional.</i>
     * <p>
     * <code>annots := annotation*</code>
     *
     * @return Parsed node
     */
    private STNode parseOptionalAnnotations() {
        startContext(ParserRuleContext.ANNOTATIONS);
        List<STNode> annotList = new ArrayList<>();
        STToken nextToken = peek();
        while (nextToken.kind == SyntaxKind.AT_TOKEN) {
            annotList.add(parseAnnotation());
            nextToken = peek();
        }

        endContext();
        return STNodeFactory.createNodeList(annotList);
    }

    /**
     * Parse annotation list with at least one annotation.
     *
     * @return Annotation list
     */
    private STNode parseAnnotations() {
        startContext(ParserRuleContext.ANNOTATIONS);
        List<STNode> annotList = new ArrayList<>();
        annotList.add(parseAnnotation());
        while (peek().kind == SyntaxKind.AT_TOKEN) {
            annotList.add(parseAnnotation());
        }

        endContext();
        return STNodeFactory.createNodeList(annotList);
    }

    /**
     * Parse annotation attachment.
     * <p>
     * <code>annotation := @ annot-tag-reference annot-value</code>
     *
     * @return Parsed node
     */
    private STNode parseAnnotation() {
        STNode atToken = parseAtToken();
        STNode annotReference;
        if (isPredeclaredIdentifier(peek().kind)) {
            annotReference = parseQualifiedIdentifier(ParserRuleContext.ANNOT_REFERENCE);
        } else {
            annotReference = STNodeFactory.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
            annotReference = STNodeFactory.createSimpleNameReferenceNode(annotReference);
        }

        STNode annotValue;
        if (peek().kind == SyntaxKind.OPEN_BRACE_TOKEN) {
            annotValue = parseMappingConstructorExpr();
        } else {
            annotValue = STNodeFactory.createEmptyNode();
        }
        return STNodeFactory.createAnnotationNode(atToken, annotReference, annotValue);
    }

    /**
     * Parse '@' token.
     *
     * @return Parsed node
     */
    private STNode parseAtToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.AT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.AT);
            return parseAtToken();
        }
    }

    /**
     * Parse metadata. Meta data consist of optional doc string and
     * an annotations list.
     * <p>
     * <code>metadata := [DocumentationString] annots</code>
     *
     * @return Parse node
     */
    private STNode parseMetaData() {
        STNode docString;
        STNode annotations;
        switch (peek().kind) {
            case DOCUMENTATION_STRING:
                docString = parseMarkdownDocumentation();
                annotations = parseOptionalAnnotations();
                break;
            case AT_TOKEN:
                docString = STNodeFactory.createEmptyNode();
                annotations = parseOptionalAnnotations();
                break;
            default:
                return STNodeFactory.createEmptyNode();
        }

        return createMetadata(docString, annotations);
    }

    /**
     * Create metadata node.
     *
     * @return A metadata node
     */
    private STNode createMetadata(STNode docString, STNode annotations) {
        if (annotations == null && docString == null) {
            return STNodeFactory.createEmptyNode();
        } else {
            return STNodeFactory.createMetadataNode(docString, annotations);
        }
    }

    /**
     * Parse type test expression.
     * <code>
     * type-test-expr := expression (is | !is) type-descriptor
     * </code>
     *
     * @param lhsExpr Preceding expression of the is expression
     * @return Is expression node
     */
    private STNode parseTypeTestExpression(STNode lhsExpr, boolean isInConditionalExpr) {
        STNode isOrNotIsKeyword = parseIsOrNotIsKeyword();
        STNode typeDescriptor = parseTypeDescriptorInExpression(isInConditionalExpr);
        return STNodeFactory.createTypeTestExpressionNode(lhsExpr, isOrNotIsKeyword, typeDescriptor);
    }

    /**
     * Parse `is` keyword or `!is` keyword.
     *
     * @return is-keyword or not-is-keyword node
     */
    private STNode parseIsOrNotIsKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.IS_KEYWORD ||
                token.kind == SyntaxKind.NOT_IS_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.IS_KEYWORD);
            return parseIsOrNotIsKeyword();
        }
    }

    /**
     * Parse local type definition statement statement.
     * <code>ocal-type-defn-stmt := [annots] type identifier type-descriptor ;</code>
     *
     * @return local type definition statement statement
     */
    private STNode parseLocalTypeDefinitionStatement(STNode annots) {
        startContext(ParserRuleContext.LOCAL_TYPE_DEFINITION_STMT);
        STNode typeKeyword = parseTypeKeyword();
        STNode typeName = parseTypeName();
        STNode typeDescriptor = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TYPE_DEF);
        STNode semicolon = parseSemicolon();
        endContext();
        return STNodeFactory.createLocalTypeDefinitionStatementNode(annots, typeKeyword, typeName, typeDescriptor,
                semicolon);
    }

    /**
     * Parse statement which is only consists of an action or expression.
     *
     * @param annots        Annotations
     * @return Statement node
     */
    private STNode parseExpressionStatement(STNode annots) {
        startContext(ParserRuleContext.EXPRESSION_STATEMENT);
        STNode expression = parseActionOrExpressionInLhs(annots);
        return getExpressionAsStatement(expression);
    }

    /**
     * Parse statements that starts with an expression.
     *
     * @return Statement node
     */
    private STNode parseStatementStartWithExpr(STNode annots) {
        startContext(ParserRuleContext.AMBIGUOUS_STMT);
        STNode expr = parseActionOrExpressionInLhs(annots);
        return parseStatementStartWithExprRhs(expr);
    }

    /**
     * Parse the component followed by the expression, at the beginning of a statement.
     *
     * @param expression Action or expression in LHS
     * @return Statement node
     */
    private STNode parseStatementStartWithExprRhs(STNode expression) {
        SyntaxKind nextTokenKind = peek().kind;
        if (isAction(expression) || nextTokenKind == SyntaxKind.SEMICOLON_TOKEN) {
            return getExpressionAsStatement(expression);
        }

        switch (nextTokenKind) {
            case EQUAL_TOKEN:
                switchContext(ParserRuleContext.ASSIGNMENT_STMT);
                return parseAssignmentStmtRhs(expression);
            case IDENTIFIER_TOKEN:
            default:
                
                if (isCompoundAssignment(nextTokenKind)) {
                    return parseCompoundAssignmentStmtRhs(expression);
                }

                ParserRuleContext context;
                if (isPossibleExpressionStatement(expression)) {
                    context = ParserRuleContext.EXPR_STMT_RHS;
                } else {
                    context = ParserRuleContext.STMT_START_WITH_EXPR_RHS;
                }

                recover(peek(), context);
                return parseStatementStartWithExprRhs(expression);
        }
    }

    private boolean isPossibleExpressionStatement(STNode expression) {
        switch (expression.kind) {
            case METHOD_CALL:
            case FUNCTION_CALL:
            case CHECK_EXPRESSION:
            case REMOTE_METHOD_CALL_ACTION:
            case CHECK_ACTION:
            case BRACED_ACTION:
            case START_ACTION:
            case TRAP_ACTION:
            case FLUSH_ACTION:
            case ASYNC_SEND_ACTION:
            case SYNC_SEND_ACTION:
            case RECEIVE_ACTION:
            case WAIT_ACTION:
            case QUERY_ACTION:
            case COMMIT_ACTION:
                return true;
            default:
                return false;
        }
    }

    private STNode getExpressionAsStatement(STNode expression) {
        switch (expression.kind) {
            case METHOD_CALL:
            case FUNCTION_CALL:
                return parseCallStatement(expression);
            case CHECK_EXPRESSION:
                return parseCheckStatement(expression);
            case REMOTE_METHOD_CALL_ACTION:
            case CHECK_ACTION:
            case BRACED_ACTION:
            case START_ACTION:
            case TRAP_ACTION:
            case FLUSH_ACTION:
            case ASYNC_SEND_ACTION:
            case SYNC_SEND_ACTION:
            case RECEIVE_ACTION:
            case WAIT_ACTION:
            case QUERY_ACTION:
            case COMMIT_ACTION:
            case CLIENT_RESOURCE_ACCESS_ACTION:
                return parseActionStatement(expression);
            default:
                
                STNode semicolon = parseSemicolon();
                endContext();
                expression = getExpression(expression);
                STNode exprStmt = STNodeFactory.createExpressionStatementNode(SyntaxKind.INVALID_EXPRESSION_STATEMENT,
                        expression, semicolon);
                exprStmt = SyntaxErrors.addDiagnostic(exprStmt, DiagnosticErrorCode.ERROR_INVALID_EXPRESSION_STATEMENT);
                return exprStmt;
        }
    }

    private STNode parseArrayTypeDescriptorNode(STIndexedExpressionNode indexedExpr) {
        STNode memberTypeDesc = getTypeDescFromExpr(indexedExpr.containerExpression);
        STNodeList lengthExprs = (STNodeList) indexedExpr.keyExpression;
        if (lengthExprs.isEmpty()) {
            return createArrayTypeDesc(memberTypeDesc, indexedExpr.openBracket, STNodeFactory.createEmptyNode(),
                    indexedExpr.closeBracket);
        }

        
        STNode lengthExpr = lengthExprs.get(0);
        switch (lengthExpr.kind) {
            case SIMPLE_NAME_REFERENCE:
                STSimpleNameReferenceNode nameRef = (STSimpleNameReferenceNode) lengthExpr;
                if (nameRef.name.isMissing()) {
                    return createArrayTypeDesc(memberTypeDesc, indexedExpr.openBracket, STNodeFactory.createEmptyNode(),
                            indexedExpr.closeBracket);
                }
                break;
            case ASTERISK_LITERAL:
            case QUALIFIED_NAME_REFERENCE:
                break;
            case NUMERIC_LITERAL:
                SyntaxKind innerChildKind = lengthExpr.childInBucket(0).kind;
                if (innerChildKind == SyntaxKind.DECIMAL_INTEGER_LITERAL_TOKEN ||
                        innerChildKind == SyntaxKind.HEX_INTEGER_LITERAL_TOKEN) {
                    break;
                }
                
            default:
                STNode newOpenBracketWithDiagnostics = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(
                        indexedExpr.openBracket, lengthExpr, DiagnosticErrorCode.ERROR_INVALID_ARRAY_LENGTH);
                indexedExpr = indexedExpr.replace(indexedExpr.openBracket, newOpenBracketWithDiagnostics);
                lengthExpr = STNodeFactory.createEmptyNode();
        }

        return createArrayTypeDesc(memberTypeDesc, indexedExpr.openBracket, lengthExpr, indexedExpr.closeBracket);
    }

    /**
     * <p>
     * Parse call statement, given the call expression.
     * </p>
     * <code>
     * call-stmt := call-expr ;
     * <br/>
     * call-expr := function-call-expr | method-call-expr | checking-keyword call-expr
     * </code>
     *
     * @param expression Call expression associated with the call statement
     * @return Call statement node
     */
    private STNode parseCallStatement(STNode expression) {
        return parseCallStatementOrCheckStatement(expression);
    }

    /**
     * <p>
     * Parse checking statement.
     * </p>
     * <code>
     * checking-stmt := checking-expr ;
     * <br/>
     * checking-expr := checking-keyword expr ;
     * </code>
     *
     * @param expression Checking expression associated with the checking statement
     * @return Checking statement node
     */
    private STNode parseCheckStatement(STNode expression) {
        return parseCallStatementOrCheckStatement(expression);
    }

    private STNode parseCallStatementOrCheckStatement(STNode expression) {
        STNode semicolon = parseSemicolon();
        endContext();
        return STNodeFactory.createExpressionStatementNode(SyntaxKind.CALL_STATEMENT, expression, semicolon);
    }

    private STNode parseActionStatement(STNode action) {
        STNode semicolon = parseSemicolon();
        endContext();
        return STNodeFactory.createExpressionStatementNode(SyntaxKind.ACTION_STATEMENT, action, semicolon);
    }

    /**
     * Parse client resource access action, given the starting expression.
     * <br/><br/>
     * <code>
     * client-resource-access-action := expression "->" "/" [resource-access-path] ["." method-name] ["(" arg-list ")"]
     * </code>
     * 
     * @param expression Expression
     * @param rightArrow Right arrow token
     * @param slashToken Slash token
     * @return Parsed node
     */
    private STNode parseClientResourceAccessAction(STNode expression, STNode rightArrow, STNode slashToken,
                                                   boolean isRhsExpr, boolean isInMatchGuard) {
        startContext(ParserRuleContext.CLIENT_RESOURCE_ACCESS_ACTION);
        
        STNode resourceAccessPath = parseOptionalResourceAccessPath(isRhsExpr, isInMatchGuard);
        STNode resourceAccessMethodDot = parseOptionalResourceAccessMethodDot(isRhsExpr, isInMatchGuard);
        STNode resourceAccessMethodName = STNodeFactory.createEmptyNode();
        if (resourceAccessMethodDot != null) {
            resourceAccessMethodName = STNodeFactory.createSimpleNameReferenceNode(parseFunctionName());
        }
        
        STNode resourceMethodCallArgList = parseOptionalResourceAccessActionArgList(isRhsExpr, isInMatchGuard);
        endContext();
        
        return STNodeFactory.createClientResourceAccessActionNode(expression, rightArrow, slashToken, 
                resourceAccessPath, resourceAccessMethodDot, resourceAccessMethodName, resourceMethodCallArgList);
    }
    
    private STNode parseOptionalResourceAccessPath(boolean isRhsExpr, boolean isInMatchGuard) {
        STNode resourceAccessPath = STNodeFactory.createEmptyNodeList();
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
            case OPEN_BRACKET_TOKEN:
                resourceAccessPath = parseResourceAccessPath(isRhsExpr, isInMatchGuard);
                break;
            case DOT_TOKEN:
            case OPEN_PAREN_TOKEN:
                break;
            default:
                if (isEndOfActionOrExpression(nextToken, isRhsExpr, isInMatchGuard)) {
                    break;
                }

                recover(nextToken, ParserRuleContext.OPTIONAL_RESOURCE_ACCESS_PATH);
                return parseOptionalResourceAccessPath(isRhsExpr, isInMatchGuard);
        }
        return  resourceAccessPath;
    }
    
    private STNode parseOptionalResourceAccessMethodDot(boolean isRhsExpr, boolean isInMatchGuard) {
        STNode dotToken = STNodeFactory.createEmptyNode();
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case DOT_TOKEN:
                dotToken = consume();
                break;
            case OPEN_PAREN_TOKEN:
                break;
            default:
                if (isEndOfActionOrExpression(nextToken, isRhsExpr, isInMatchGuard)) {
                    break;
                }

                recover(nextToken, ParserRuleContext.OPTIONAL_RESOURCE_ACCESS_METHOD);
                return parseOptionalResourceAccessMethodDot(isRhsExpr, isInMatchGuard);
        }
        
        return dotToken;
    }
    
    private STNode parseOptionalResourceAccessActionArgList(boolean isRhsExpr, boolean isInMatchGuard) {
        STNode argList = STNodeFactory.createEmptyNode();
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                argList = parseParenthesizedArgList();
                break;
            default:
                if (isEndOfActionOrExpression(nextToken, isRhsExpr, isInMatchGuard)) {
                    break;
                }

                recover(nextToken, ParserRuleContext.OPTIONAL_RESOURCE_ACCESS_ACTION_ARG_LIST);
                return parseOptionalResourceAccessActionArgList(isRhsExpr, isInMatchGuard);
        }
        
        return argList;
    }

    /**
     * Parse resource access path.
     * <br/><br/>
     * <code>
     * resource-access-path :=
     *    resource-access-segments ["/" resource-access-rest-segment]
     *    | resource-access-rest-segment
     * <br/><br/>
     * resource-access-segments := resource-access-segment ("/" resource-access-segment ")*
     * <br/><br/>
     * resource-access-segment := resource-path-segment-name | computed-resource-access-segment
     * <br/><br/>
     * resource-path-segment-name := identifier
     * </code>
     * @return
     */
    private STNode parseResourceAccessPath(boolean isRhsExpr, boolean isInMatchGuard) {
        List<STNode> pathSegmentList = new ArrayList<>();
        
        STNode pathSegment = parseResourceAccessSegment();
        pathSegmentList.add(pathSegment);

        STNode leadingSlash;
        STNode previousPathSegmentNode = pathSegment;
        while (!isEndOfResourceAccessPathSegments(peek(), isRhsExpr, isInMatchGuard)) {
            leadingSlash = parseResourceAccessSegmentRhs(isRhsExpr, isInMatchGuard);
            if (leadingSlash == null) {
                break;
            }
            
            pathSegment = parseResourceAccessSegment();
            
            if (previousPathSegmentNode.kind == SyntaxKind.RESOURCE_ACCESS_REST_SEGMENT) {
                updateLastNodeInListWithInvalidNode(pathSegmentList, leadingSlash, null);
                updateLastNodeInListWithInvalidNode(pathSegmentList, pathSegment, 
                        DiagnosticErrorCode.RESOURCE_ACCESS_SEGMENT_IS_NOT_ALLOWED_AFTER_REST_SEGMENT);
            } else {
                pathSegmentList.add(leadingSlash);
                pathSegmentList.add(pathSegment);
                previousPathSegmentNode = pathSegment;
            }
        }

        return STNodeFactory.createNodeList(pathSegmentList);
    }
    
    private STNode parseResourceAccessSegment() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
                return consume();
            case OPEN_BRACKET_TOKEN:
                return parseComputedOrResourceAccessRestSegment(consume());
            default:
                recover(nextToken, ParserRuleContext.RESOURCE_ACCESS_PATH_SEGMENT);
                return parseResourceAccessSegment();
        }
    }

    /**
     * Parse computed resource segment or resource access rest segment.
     * <code>
     * <br/>
     * computed-resource-access-segment := "[" expression "]"
     * <br/>
     * resource-access-rest-segment := "[" "..." expression "]"
     * </code>
     * @param openBracket Open bracket token
     * @return Parsed node
     */
    private STNode parseComputedOrResourceAccessRestSegment(STNode openBracket) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case ELLIPSIS_TOKEN:
                STNode ellipsisToken = consume();
                STNode expression = parseExpression();
                STNode closeBracketToken = parseCloseBracket();
                return STNodeFactory.createResourceAccessRestSegmentNode(openBracket, ellipsisToken,
                        expression, closeBracketToken);
            default:
                if (isValidExprStart(nextToken.kind)) {
                    expression = parseExpression();
                    closeBracketToken = parseCloseBracket();
                    return STNodeFactory.createComputedResourceAccessSegmentNode(openBracket, expression, 
                            closeBracketToken);
                }
                
                recover(nextToken, ParserRuleContext.COMPUTED_SEGMENT_OR_REST_SEGMENT);
                return parseComputedOrResourceAccessRestSegment(openBracket);
        }
    }

    /**
     * Parse resource access segment end.
     *
     * @return Parsed node
     */
    private STNode parseResourceAccessSegmentRhs(boolean isRhsExpr, boolean isInMatchGuard) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case SLASH_TOKEN:
                return consume();
            default:
                if (isEndOfResourceAccessPathSegments(nextToken, isRhsExpr, isInMatchGuard)) {
                    return null;
                }
                
                recover(nextToken, ParserRuleContext.RESOURCE_ACCESS_SEGMENT_RHS);
                return parseResourceAccessSegmentRhs(isRhsExpr, isInMatchGuard);
        }
    }
    
    private boolean isEndOfResourceAccessPathSegments(STToken nextToken,
                                                      boolean isRhsExpr, boolean isInMatchGuard) {
        SyntaxKind nextTokenKind = nextToken.kind;
        switch (nextTokenKind) {
            case DOT_TOKEN:
            case OPEN_PAREN_TOKEN:
                return true;
            default:
                return isEndOfActionOrExpression(nextToken, isRhsExpr, isInMatchGuard);
        }
    }
    
    private STNode parseRemoteMethodCallOrClientResourceAccessOrAsyncSendAction(STNode expression, boolean isRhsExpr,
                                                                                boolean isInMatchGuard) {
        STNode rightArrow = parseRightArrow();
        return parseClientResourceAccessOrAsyncSendActionRhs(expression, rightArrow, isRhsExpr, isInMatchGuard);
    }

    private STNode parseClientResourceAccessOrAsyncSendActionRhs(STNode expression, STNode rightArrow,
                                                                 boolean isRhsExpr, boolean isInMatchGuard) {
        STNode name;
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case FUNCTION_KEYWORD:
                STNode functionKeyword = consume();
                name = STNodeFactory.createSimpleNameReferenceNode(functionKeyword);
                return parseAsyncSendAction(expression, rightArrow, name);
            case CONTINUE_KEYWORD:
            case COMMIT_KEYWORD:
                name = getKeywordAsSimpleNameRef();
                break;
            case SLASH_TOKEN:
                STNode slashToken = consume();
                return parseClientResourceAccessAction(expression, rightArrow, slashToken, isRhsExpr, isInMatchGuard);
            default:
                if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN) {
                    
                    
                    
                    SyntaxKind nextNextNextTokenKind = getNextNextToken().kind;
                    if (nextNextNextTokenKind == SyntaxKind.OPEN_PAREN_TOKEN ||
                            isEndOfActionOrExpression(getNextNextToken(), isRhsExpr, isInMatchGuard) ||
                            nextToken.isMissing()) {
                        name = STNodeFactory.createSimpleNameReferenceNode(parseFunctionName());
                        break;
                    }
                }
                
                STToken token = peek();
                Solution solution = recover(token, ParserRuleContext.REMOTE_OR_RESOURCE_CALL_OR_ASYNC_SEND_RHS);
                if (solution.action == Action.KEEP) {
                    
                    name = STNodeFactory.createSimpleNameReferenceNode(parseFunctionName());
                    break;
                }
                return parseClientResourceAccessOrAsyncSendActionRhs(expression, rightArrow, isRhsExpr, isInMatchGuard);
        }

        return parseRemoteCallOrAsyncSendEnd(expression, rightArrow, name);
    }

    private STNode parseRemoteCallOrAsyncSendEnd(STNode expression, STNode rightArrow, STNode name) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                return parseRemoteMethodCallAction(expression, rightArrow, name);
            case SEMICOLON_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case OPEN_BRACE_TOKEN:
            case COMMA_TOKEN:
            case FROM_KEYWORD:
            case JOIN_KEYWORD:
            case ON_KEYWORD:
            case LET_KEYWORD:
            case WHERE_KEYWORD:
            case ORDER_KEYWORD:
            case LIMIT_KEYWORD:
            case SELECT_KEYWORD:
                return parseAsyncSendAction(expression, rightArrow, name);
            default:
                if (isGroupOrCollectKeyword(nextToken)) {
                    return parseAsyncSendAction(expression, rightArrow, name);
                }
                recover(peek(), ParserRuleContext.REMOTE_CALL_OR_ASYNC_SEND_END);
                return parseRemoteCallOrAsyncSendEnd(expression, rightArrow, name);
        }
    }

    private STNode parseAsyncSendAction(STNode expression, STNode rightArrow, STNode peerWorker) {
        return STNodeFactory.createAsyncSendActionNode(expression, rightArrow, peerWorker);
    }


    /**
     * Parse remote method call action.
     * <p>
     * <code>
     * remote-method-call-action := expression -> method-name ( arg-list )
     * <br/>
     * async-send-action := expression -> peer-worker ;
     * </code>
     *
     * @param expression LHS expression
     * @param rightArrow  right arrow token
     * @param name remote method name
     * @return
     */
    private STNode parseRemoteMethodCallAction(STNode expression, STNode rightArrow, STNode name) {
        STNode openParenToken = parseArgListOpenParenthesis();
        STNode arguments = parseArgsList();
        STNode closeParenToken = parseArgListCloseParenthesis();
        return STNodeFactory.createRemoteMethodCallActionNode(expression, rightArrow, name, openParenToken, arguments,
                closeParenToken);
    }

    /**
     * Parse right arrow (<code>-></code>) token.
     *
     * @return Parsed node
     */
    private STNode parseRightArrow() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.RIGHT_ARROW_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.RIGHT_ARROW);
            return parseRightArrow();
        }
    }

    /**
     * Parse map type descriptor.
     * map-type-descriptor := `map` type-parameter
     *
     * @return Parsed node
     */
    private STNode parseMapTypeDescriptor(STNode mapKeyword) {
        STNode typeParameter = parseTypeParameter();
        return STNodeFactory.createMapTypeDescriptorNode(mapKeyword, typeParameter);
    }

    /**
     * Parse parameterized type descriptor.
     * parameterized-type-descriptor := `typedesc` [type-parameter]
     * <br/>&nbsp;| `future` [type-parameter]
     * <br/>&nbsp;| `xml` [type-parameter]
     * <br/>&nbsp;| `error` [type-parameter]
     *
     * @return Parsed node
     */
    private STNode parseParameterizedTypeDescriptor(STNode keywordToken) {
        STNode typeParamNode;
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.LT_TOKEN) {
            typeParamNode = parseTypeParameter();
        } else {
            typeParamNode = STNodeFactory.createEmptyNode();
        }

        SyntaxKind parameterizedTypeDescKind = getParameterizedTypeDescKind(keywordToken);
        return STNodeFactory.createParameterizedTypeDescriptorNode(parameterizedTypeDescKind, keywordToken,
                typeParamNode);
    }

    private SyntaxKind getParameterizedTypeDescKind(STNode keywordToken) {
        switch (keywordToken.kind) {
            case TYPEDESC_KEYWORD:
                return SyntaxKind.TYPEDESC_TYPE_DESC;
            case FUTURE_KEYWORD:
                return SyntaxKind.FUTURE_TYPE_DESC;
            case XML_KEYWORD:
                return SyntaxKind.XML_TYPE_DESC;
            case ERROR_KEYWORD:
            default:
                return SyntaxKind.ERROR_TYPE_DESC;
        }
    }
    
    /**
     * Parse <code> < </code> token.
     *
     * @return Parsed node
     */
    private STNode parseGTToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.GT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.GT);
            return parseGTToken();
        }
    }

    /**
     * Parse <code> > </code> token.
     *
     * @return Parsed node
     */
    private STNode parseLTToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.LT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.LT);
            return parseLTToken();
        }
    }

    /**
     * Parse nil literal. Here nil literal is only referred to ( ).
     *
     * @return Parsed node
     */
    private STNode parseNilLiteral() {
        startContext(ParserRuleContext.NIL_LITERAL);
        STNode openParenthesisToken = parseOpenParenthesis();
        STNode closeParenthesisToken = parseCloseParenthesis();
        endContext();
        return STNodeFactory.createNilLiteralNode(openParenthesisToken, closeParenthesisToken);
    }

    /**
     * Parse annotation declaration, given the qualifier.
     *
     * @param metadata     Metadata
     * @param qualifier    Qualifier that precedes the listener declaration
     * @param constKeyword Const keyword
     * @return Parsed node
     */
    private STNode parseAnnotationDeclaration(STNode metadata, STNode qualifier, STNode constKeyword) {
        startContext(ParserRuleContext.ANNOTATION_DECL);
        STNode annotationKeyword = parseAnnotationKeyword();
        STNode annotDecl = parseAnnotationDeclFromType(metadata, qualifier, constKeyword, annotationKeyword);
        endContext();
        return annotDecl;
    }

    /**
     * Parse annotation keyword.
     *
     * @return Parsed node
     */
    private STNode parseAnnotationKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ANNOTATION_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ANNOTATION_KEYWORD);
            return parseAnnotationKeyword();
        }
    }

    /**
     * Parse the components that follows after the annotation keyword of a annotation declaration.
     *
     * @param metadata          Metadata
     * @param qualifier         Qualifier that precedes the constant decl
     * @param constKeyword      Const keyword
     * @param annotationKeyword
     * @return Parsed node
     */
    private STNode parseAnnotationDeclFromType(STNode metadata, STNode qualifier, STNode constKeyword,
                                               STNode annotationKeyword) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
                return parseAnnotationDeclWithOptionalType(metadata, qualifier, constKeyword, annotationKeyword);
            default:
                if (isTypeStartingToken(nextToken.kind)) {
                    break;
                }

                recover(peek(), ParserRuleContext.ANNOT_DECL_OPTIONAL_TYPE);
                return parseAnnotationDeclFromType(metadata, qualifier, constKeyword, annotationKeyword);
        }

        STNode typeDesc = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_ANNOTATION_DECL);
        STNode annotTag = parseAnnotationTag();
        return parseAnnotationDeclAttachPoints(metadata, qualifier, constKeyword, annotationKeyword, typeDesc,
                annotTag);
    }

    /**
     * Parse annotation tag.
     * <p>
     * <code>annot-tag := identifier</code>
     *
     * @return
     */
    private STNode parseAnnotationTag() {
        STToken token = peek();
        if (token.kind == SyntaxKind.IDENTIFIER_TOKEN) {
            return consume();
        } else {
            recover(peek(), ParserRuleContext.ANNOTATION_TAG);
            return parseAnnotationTag();
        }
    }

    private STNode parseAnnotationDeclWithOptionalType(STNode metadata, STNode qualifier, STNode constKeyword,
                                                       STNode annotationKeyword) {
        
        
        STNode typeDescOrAnnotTag = parseQualifiedIdentifier(ParserRuleContext.ANNOT_DECL_OPTIONAL_TYPE);
        if (typeDescOrAnnotTag.kind == SyntaxKind.QUALIFIED_NAME_REFERENCE) {
            STNode annotTag = parseAnnotationTag();
            return parseAnnotationDeclAttachPoints(metadata, qualifier, constKeyword, annotationKeyword,
                    typeDescOrAnnotTag, annotTag);
        }

        
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN || isValidTypeContinuationToken(nextToken)) {
            STNode typeDesc = parseComplexTypeDescriptor(typeDescOrAnnotTag,
                    ParserRuleContext.TYPE_DESC_IN_ANNOTATION_DECL, false);
            STNode annotTag = parseAnnotationTag();
            return parseAnnotationDeclAttachPoints(metadata, qualifier, constKeyword, annotationKeyword, typeDesc,
                    annotTag);
        }

        STNode annotTag = ((STSimpleNameReferenceNode) typeDescOrAnnotTag).name;
        return parseAnnotationDeclRhs(metadata, qualifier, constKeyword, annotationKeyword, annotTag);
    }

    /**
     * Parse the component that follows the first identifier in an annotation decl. The identifier
     * can be either the type-name (a user defined type) or the annot-tag, where the type-name
     * is not present.
     *
     * @param metadata           Metadata
     * @param qualifier          Qualifier that precedes the annotation decl
     * @param constKeyword       Const keyword
     * @param annotationKeyword  Annotation keyword
     * @param typeDescOrAnnotTag Identifier that follows the annotation-keyword
     * @return Parsed node
     */
    private STNode parseAnnotationDeclRhs(STNode metadata, STNode qualifier, STNode constKeyword,
                                          STNode annotationKeyword, STNode typeDescOrAnnotTag) {
        STToken nextToken = peek();
        STNode typeDesc;
        STNode annotTag;
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
                typeDesc = typeDescOrAnnotTag;
                annotTag = parseAnnotationTag();
                break;
            case SEMICOLON_TOKEN:
            case ON_KEYWORD:
                typeDesc = STNodeFactory.createEmptyNode();
                annotTag = typeDescOrAnnotTag;
                break;
            default:
                recover(peek(), ParserRuleContext.ANNOT_DECL_RHS);
                return parseAnnotationDeclRhs(metadata, qualifier, constKeyword, annotationKeyword, typeDescOrAnnotTag);
        }

        return parseAnnotationDeclAttachPoints(metadata, qualifier, constKeyword, annotationKeyword, typeDesc,
                annotTag);
    }

    private STNode parseAnnotationDeclAttachPoints(STNode metadata, STNode qualifier, STNode constKeyword,
                                                   STNode annotationKeyword, STNode typeDesc, STNode annotTag) {
        STNode onKeyword;
        STNode attachPoints;
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case SEMICOLON_TOKEN:
                onKeyword = STNodeFactory.createEmptyNode();
                attachPoints = STNodeFactory.createEmptyNodeList();
                break;
            case ON_KEYWORD:
                onKeyword = parseOnKeyword();
                attachPoints = parseAnnotationAttachPoints();
                onKeyword = cloneWithDiagnosticIfListEmpty(attachPoints, onKeyword,
                        DiagnosticErrorCode.ERROR_MISSING_ANNOTATION_ATTACH_POINT);
                break;
            default:
                recover(peek(), ParserRuleContext.ANNOT_OPTIONAL_ATTACH_POINTS);
                return parseAnnotationDeclAttachPoints(metadata, qualifier, constKeyword, annotationKeyword, typeDesc,
                        annotTag);
        }

        STNode semicolonToken = parseSemicolon();
        return STNodeFactory.createAnnotationDeclarationNode(metadata, qualifier, constKeyword, annotationKeyword,
                typeDesc, annotTag, onKeyword, attachPoints, semicolonToken);
    }

    /**
     * Parse annotation attach points.
     * <p>
     * <code>
     * annot-attach-points := annot-attach-point (, annot-attach-point)*
     * <br/><br/>
     * annot-attach-point := dual-attach-point | source-only-attach-point
     * <br/><br/>
     * dual-attach-point := [source] dual-attach-point-ident
     * <br/><br/>
     * dual-attach-point-ident :=
     * type
     * | class
     * | [object|service remote] function
     * | parameter
     * | return
     * | service
     * | [object|record] field
     * <br/><br/>
     * source-only-attach-point := source source-only-attach-point-ident
     * <br/><br/>
     * source-only-attach-point-ident :=
     * annotation
     * | external
     * | var
     * | const
     * | listener
     * | worker
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseAnnotationAttachPoints() {
        startContext(ParserRuleContext.ANNOT_ATTACH_POINTS_LIST);
        List<STNode> attachPoints = new ArrayList<>();

        STToken nextToken = peek();
        if (isEndAnnotAttachPointList(nextToken.kind)) {
            endContext();
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode attachPoint = parseAnnotationAttachPoint();
        attachPoints.add(attachPoint);

        
        nextToken = peek();
        STNode leadingComma;
        while (!isEndAnnotAttachPointList(nextToken.kind)) {
            leadingComma = parseAttachPointEnd();
            if (leadingComma == null) {
                break;
            }
            attachPoints.add(leadingComma);

            
            attachPoint = parseAnnotationAttachPoint();
            if (attachPoint == null) {
                STToken missingAttachPointIdent = SyntaxErrors.createMissingToken(SyntaxKind.TYPE_KEYWORD);
                STNode identList = STNodeFactory.createNodeList(missingAttachPointIdent);
                attachPoint = STNodeFactory.createAnnotationAttachPointNode(STNodeFactory.createEmptyNode(), identList);
                attachPoint = SyntaxErrors.addDiagnostic(attachPoint,
                        DiagnosticErrorCode.ERROR_MISSING_ANNOTATION_ATTACH_POINT);
                attachPoints.add(attachPoint);
                break;
            }

            attachPoints.add(attachPoint);
            nextToken = peek();
        }
        
        if (attachPoint.lastToken().isMissing() && this.tokenReader.peek().kind == SyntaxKind.IDENTIFIER_TOKEN &&
                !this.tokenReader.head().hasTrailingNewline()) {
            
            
            STToken nextNonVirtualToken = this.tokenReader.read();
            updateLastNodeInListWithInvalidNode(attachPoints, nextNonVirtualToken,
                    DiagnosticErrorCode.ERROR_INVALID_TOKEN, nextNonVirtualToken.text());
        }

        endContext();
        return STNodeFactory.createNodeList(attachPoints);
    }

    /**
     * Parse annotation attach point end.
     *
     * @return Parsed node
     */
    private STNode parseAttachPointEnd() {
        switch (peek().kind) {
            case SEMICOLON_TOKEN:
                
                return null;
            case COMMA_TOKEN:
                return consume();
            default:
                recover(peek(), ParserRuleContext.ATTACH_POINT_END);
                return parseAttachPointEnd();
        }
    }

    private boolean isEndAnnotAttachPointList(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case EOF_TOKEN:
            case SEMICOLON_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse annotation attach point.
     *
     * @return Parsed node
     */
    private STNode parseAnnotationAttachPoint() {
        switch (peek().kind) {
            case EOF_TOKEN:
                return null;

            
            case ANNOTATION_KEYWORD:
            case EXTERNAL_KEYWORD:
            case VAR_KEYWORD:
            case CONST_KEYWORD:
            case LISTENER_KEYWORD:
            case WORKER_KEYWORD:
                

            case SOURCE_KEYWORD:
                STNode sourceKeyword = parseSourceKeyword();
                return parseAttachPointIdent(sourceKeyword);

            
            case OBJECT_KEYWORD:
            case TYPE_KEYWORD:
            case FUNCTION_KEYWORD:
            case PARAMETER_KEYWORD:
            case RETURN_KEYWORD:
            case SERVICE_KEYWORD:
            case FIELD_KEYWORD:
            case RECORD_KEYWORD:
            case CLASS_KEYWORD:
                sourceKeyword = STNodeFactory.createEmptyNode();
                STNode firstIdent = consume();
                return parseDualAttachPointIdent(sourceKeyword, firstIdent);
            default:
                recover(peek(), ParserRuleContext.ATTACH_POINT);
                return parseAnnotationAttachPoint();
        }
    }

    /**
     * Parse source keyword.
     *
     * @return Parsed node
     */
    private STNode parseSourceKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.SOURCE_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.SOURCE_KEYWORD);
            return parseSourceKeyword();
        }
    }

    /**
     * Parse attach point ident gievn.
     * <p>
     * <code>
     * source-only-attach-point-ident := annotation | external | var | const | listener | worker
     * <br/><br/>
     * dual-attach-point-ident := type | class | [object|service remote] function | parameter
     * | return | service | [object|record] field
     * </code>
     *
     * @param sourceKeyword Source keyword
     * @return Parsed node
     */
    private STNode parseAttachPointIdent(STNode sourceKeyword) {
        switch (peek().kind) {
            case ANNOTATION_KEYWORD:
            case EXTERNAL_KEYWORD:
            case VAR_KEYWORD:
            case CONST_KEYWORD:
            case LISTENER_KEYWORD:
            case WORKER_KEYWORD:
                STNode firstIdent = consume();
                STNode identList = STNodeFactory.createNodeList(firstIdent);
                return STNodeFactory.createAnnotationAttachPointNode(sourceKeyword, identList);
            case OBJECT_KEYWORD:
            case RESOURCE_KEYWORD:
            case RECORD_KEYWORD:
            case TYPE_KEYWORD:
            case FUNCTION_KEYWORD:
            case PARAMETER_KEYWORD:
            case RETURN_KEYWORD:
            case SERVICE_KEYWORD:
            case FIELD_KEYWORD:
            case CLASS_KEYWORD:
                firstIdent = consume();
                return parseDualAttachPointIdent(sourceKeyword, firstIdent);
            default:
                recover(peek(), ParserRuleContext.ATTACH_POINT_IDENT);
                return parseAttachPointIdent(sourceKeyword);
        }
    }

    /**
     * Parse dual-attach-point ident.
     *
     * @param sourceKeyword Source keyword
     * @param firstIdent    first part of the dual attach-point
     * @return Parsed node
     */
    private STNode parseDualAttachPointIdent(STNode sourceKeyword, STNode firstIdent) {
        STNode secondIdent;
        switch (firstIdent.kind) {
            case OBJECT_KEYWORD:
                secondIdent = parseIdentAfterObjectIdent();
                break;
            case RESOURCE_KEYWORD:
                secondIdent = parseFunctionIdent();
                break;
            case RECORD_KEYWORD:
                secondIdent = parseFieldIdent();
                break;
            case SERVICE_KEYWORD:
                return parseServiceAttachPoint(sourceKeyword, firstIdent);
            case TYPE_KEYWORD:
            case FUNCTION_KEYWORD:
            case PARAMETER_KEYWORD:
            case RETURN_KEYWORD:
            case FIELD_KEYWORD:
            case CLASS_KEYWORD:
            default: 
                STNode identList = STNodeFactory.createNodeList(firstIdent);
                return STNodeFactory.createAnnotationAttachPointNode(sourceKeyword, identList);
        }

        STNode identList = STNodeFactory.createNodeList(firstIdent, secondIdent);
        return STNodeFactory.createAnnotationAttachPointNode(sourceKeyword, identList);
    }

    /**
     * Parse remote ident.
     *
     * @return Parsed node
     */
    private STNode parseRemoteIdent() {
        STToken token = peek();
        if (token.kind == SyntaxKind.REMOTE_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.REMOTE_IDENT);
            return parseRemoteIdent();
        }
    }

    /**
     * Parse service attach point.
     * <code>service-attach-point := service | service remote function</code>
     *
     * @return Parsed node
     */
    private STNode parseServiceAttachPoint(STNode sourceKeyword, STNode firstIdent) {
        STNode identList;
        STToken token = peek();
        switch (token.kind) {
            case REMOTE_KEYWORD:
                STNode secondIdent = parseRemoteIdent();
                STNode thirdIdent = parseFunctionIdent();
                identList = STNodeFactory.createNodeList(firstIdent, secondIdent, thirdIdent);
                return STNodeFactory.createAnnotationAttachPointNode(sourceKeyword, identList);
            case COMMA_TOKEN:
            case SEMICOLON_TOKEN:
                identList = STNodeFactory.createNodeList(firstIdent);
                return STNodeFactory.createAnnotationAttachPointNode(sourceKeyword, identList);
            default:
                recover(token, ParserRuleContext.SERVICE_IDENT_RHS);
                return parseServiceAttachPoint(sourceKeyword, firstIdent);
        }
    }

    /**
     * Parse the idents that are supported after object-ident.
     *
     * @return Parsed node
     */
    private STNode parseIdentAfterObjectIdent() {
        STToken token = peek();
        switch (token.kind) {
            case FUNCTION_KEYWORD:
            case FIELD_KEYWORD:
                return consume();
            default:
                recover(token, ParserRuleContext.IDENT_AFTER_OBJECT_IDENT);
                return parseIdentAfterObjectIdent();
        }
    }

    /**
     * Parse function ident.
     *
     * @return Parsed node
     */
    private STNode parseFunctionIdent() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FUNCTION_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FUNCTION_IDENT);
            return parseFunctionIdent();
        }
    }

    /**
     * Parse field ident.
     *
     * @return Parsed node
     */
    private STNode parseFieldIdent() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FIELD_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FIELD_IDENT);
            return parseFieldIdent();
        }
    }

    /**
     * Parse XML namespace declaration.
     * <p>
     * <code>xmlns-decl := xmlns xml-namespace-uri [ as xml-namespace-prefix ] ;
     * <br/>
     * xml-namespace-uri := simple-const-expr
     * <br/>
     * xml-namespace-prefix := identifier
     * </code>
     *
     * @return
     */
    private STNode parseXMLNamespaceDeclaration(boolean isModuleVar) {
        startContext(ParserRuleContext.XML_NAMESPACE_DECLARATION);
        STNode xmlnsKeyword = parseXMLNSKeyword();

        STNode namespaceUri = parseSimpleConstExpr();
        while (!isValidXMLNameSpaceURI(namespaceUri)) {
            xmlnsKeyword = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(xmlnsKeyword, namespaceUri,
                    DiagnosticErrorCode.ERROR_INVALID_XML_NAMESPACE_URI);
            namespaceUri = parseSimpleConstExpr();
        }

        STNode xmlnsDecl = parseXMLDeclRhs(xmlnsKeyword, namespaceUri, isModuleVar);
        endContext();
        return xmlnsDecl;
    }

    /**
     * Parse xmlns keyword.
     *
     * @return Parsed node
     */
    private STNode parseXMLNSKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.XMLNS_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.XMLNS_KEYWORD);
            return parseXMLNSKeyword();
        }
    }

    private boolean isValidXMLNameSpaceURI(STNode expr) {
        switch (expr.kind) {
            case STRING_LITERAL:
            case QUALIFIED_NAME_REFERENCE:
            case SIMPLE_NAME_REFERENCE:
                return true;
            case IDENTIFIER_TOKEN:
            default:
                return false;
        }
    }

    private STNode parseSimpleConstExpr() {
        startContext(ParserRuleContext.CONSTANT_EXPRESSION);
        STNode expr = parseSimpleConstExprInternal();
        endContext();
        return expr;
    }

    /**
     * Parse simple constants expr.
     *
     * @return Parsed node
     */
    private STNode parseSimpleConstExprInternal() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case STRING_LITERAL_TOKEN:
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case NULL_KEYWORD:
                return parseBasicLiteral();
            case PLUS_TOKEN:
            case MINUS_TOKEN:
                return parseSignedIntOrFloat();
            case OPEN_PAREN_TOKEN:
                return parseNilLiteral();
            default:
                if (isPredeclaredIdentifier(nextToken.kind)) {
                    return parseQualifiedIdentifier(ParserRuleContext.VARIABLE_REF);
                }
                
                recover(nextToken, ParserRuleContext.CONSTANT_EXPRESSION_START);
                return parseSimpleConstExprInternal();
        }
    }

    /**
     * Parse the portion after the namsepsace-uri of an XML declaration.
     *
     * @param xmlnsKeyword XMLNS keyword
     * @param namespaceUri Namespace URI
     * @return Parsed node
     */
    private STNode parseXMLDeclRhs(STNode xmlnsKeyword, STNode namespaceUri, boolean isModuleVar) {
        STNode asKeyword = STNodeFactory.createEmptyNode();
        STNode namespacePrefix = STNodeFactory.createEmptyNode();

        switch (peek().kind) {
            case AS_KEYWORD:
                asKeyword = parseAsKeyword();
                namespacePrefix = parseNamespacePrefix();
                break;
            case SEMICOLON_TOKEN:
                break;
            default:
                recover(peek(), ParserRuleContext.XML_NAMESPACE_PREFIX_DECL);
                return parseXMLDeclRhs(xmlnsKeyword, namespaceUri, isModuleVar);
        }

        STNode semicolon = parseSemicolon();
        if (isModuleVar) {
            return STNodeFactory.createModuleXMLNamespaceDeclarationNode(xmlnsKeyword, namespaceUri, asKeyword,
                    namespacePrefix, semicolon);
        }
        return STNodeFactory.createXMLNamespaceDeclarationNode(xmlnsKeyword, namespaceUri, asKeyword, namespacePrefix,
                semicolon);
    }

    /**
     * Parse import prefix.
     *
     * @return Parsed node
     */
    private STNode parseNamespacePrefix() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN) {
            return consume();
        } else {
            recover(peek(), ParserRuleContext.NAMESPACE_PREFIX);
            return parseNamespacePrefix();
        }
    }

    /**
     * Parse named worker declaration.
     * <p>
     * <code>named-worker-decl := [annots] [transactional] worker worker-name return-type-descriptor { sequence-stmt }
     * </code>
     *
     * @param annots Annotations attached to the worker decl
     * @param qualifiers Preceding transactional keyword in a list
     * @return Parsed node
     */
    private STNode parseNamedWorkerDeclaration(STNode annots, List<STNode> qualifiers) {
        startContext(ParserRuleContext.NAMED_WORKER_DECL);
        STNode transactionalKeyword = getTransactionalKeyword(qualifiers);
        STNode workerKeyword = parseWorkerKeyword();
        STNode workerName = parseWorkerName();
        STNode returnTypeDesc = parseReturnTypeDescriptor();
        STNode workerBody = parseBlockNode();
        endContext();
        return STNodeFactory.createNamedWorkerDeclarationNode(annots, transactionalKeyword, workerKeyword, workerName,
                returnTypeDesc, workerBody);
    }

    private STNode getTransactionalKeyword(List<STNode> qualifierList) {
        
        List<STNode> validatedList = new ArrayList<>();

        for (int i = 0; i < qualifierList.size(); i++) {
            STNode qualifier = qualifierList.get(i);
            int nextIndex = i + 1;

            if (isSyntaxKindInList(validatedList, qualifier.kind)) {
                updateLastNodeInListWithInvalidNode(validatedList, qualifier,
                        DiagnosticErrorCode.ERROR_DUPLICATE_QUALIFIER, ((STToken) qualifier).text());
            } else if (qualifier.kind == SyntaxKind.TRANSACTIONAL_KEYWORD) {
                validatedList.add(qualifier);
            } else if (qualifierList.size() == nextIndex) {
                addInvalidNodeToNextToken(qualifier, DiagnosticErrorCode.ERROR_QUALIFIER_NOT_ALLOWED,
                        ((STToken) qualifier).text());
            } else {
                updateANodeInListWithLeadingInvalidNode(qualifierList, nextIndex, qualifier,
                        DiagnosticErrorCode.ERROR_QUALIFIER_NOT_ALLOWED, ((STToken) qualifier).text());
            }
        }

        STNode transactionalKeyword;
        if (validatedList.isEmpty()) {
            transactionalKeyword = STNodeFactory.createEmptyNode();
        } else {
            transactionalKeyword = validatedList.get(0);
        }
        return transactionalKeyword;
    }

    private STNode parseReturnTypeDescriptor() {
        
        STToken token = peek();
        if (token.kind != SyntaxKind.RETURNS_KEYWORD) {
            return STNodeFactory.createEmptyNode();
        }

        STNode returnsKeyword = consume();
        STNode annot = parseOptionalAnnotations();
        STNode type = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_RETURN_TYPE_DESC);
        return STNodeFactory.createReturnTypeDescriptorNode(returnsKeyword, annot, type);
    }

    /**
     * Parse worker keyword.
     *
     * @return Parsed node
     */
    private STNode parseWorkerKeyword() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.WORKER_KEYWORD) {
            return consume();
        } else {
            recover(peek(), ParserRuleContext.WORKER_KEYWORD);
            return parseWorkerKeyword();
        }
    }

    /**
     * Parse worker name.
     * <p>
     * <code>worker-name := identifier</code>
     *
     * @return Parsed node
     */
    private STNode parseWorkerName() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN) {
            return consume();
        } else {
            recover(peek(), ParserRuleContext.WORKER_NAME);
            return parseWorkerName();
        }
    }

    /**
     * Parse lock statement.
     * <code>lock-stmt := lock block-stmt [on-fail-clause]</code>
     *
     * @return Lock statement
     */
    private STNode parseLockStatement() {
        startContext(ParserRuleContext.LOCK_STMT);
        STNode lockKeyword = parseLockKeyword();
        STNode blockStatement = parseBlockNode();
        endContext();
        STNode onFailClause = parseOptionalOnFailClause();
        return STNodeFactory.createLockStatementNode(lockKeyword, blockStatement, onFailClause);
    }

    /**
     * Parse lock-keyword.
     *
     * @return lock-keyword node
     */
    private STNode parseLockKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.LOCK_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.LOCK_KEYWORD);
            return parseLockKeyword();
        }
    }

    /**
     * Parse union type descriptor.
     * union-type-descriptor := type-descriptor | type-descriptor
     *
     * @param leftTypeDesc Type desc in the LHS os the union type desc.
     * @param context      Current context.
     * @return parsed union type desc node
     */
    private STNode parseUnionTypeDescriptor(STNode leftTypeDesc, ParserRuleContext context,
                                            boolean isTypedBindingPattern) {
        
        STNode pipeToken = consume();
        STNode rightTypeDesc = parseTypeDescriptorInternal(new ArrayList<>(), context, isTypedBindingPattern, false,
                TypePrecedence.UNION);
        return mergeTypesWithUnion(leftTypeDesc, pipeToken, rightTypeDesc);
    }

    /**
     * Creates a union type descriptor after validating lhs and rhs types.
     * <p>
     * <i>Note: Since type precedence and associativity are not taken into account here,
     * this method should not be called directly when types are unknown.
     * <br/>
     * Call {@link 
     *
     * @param leftTypeDesc  lhs type
     * @param pipeToken     pipe token
     * @param rightTypeDesc rhs type
     * @return a UnionTypeDescriptorNode
     */
    private STNode createUnionTypeDesc(STNode leftTypeDesc, STNode pipeToken, STNode rightTypeDesc) {
        leftTypeDesc = validateForUsageOfVar(leftTypeDesc);
        rightTypeDesc = validateForUsageOfVar(rightTypeDesc);
        return STNodeFactory.createUnionTypeDescriptorNode(leftTypeDesc, pipeToken, rightTypeDesc);
    }

    /**
     * Parse pipe token.
     *
     * @return parsed pipe token node
     */
    private STNode parsePipeToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.PIPE_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.PIPE);
            return parsePipeToken();
        }
    }

    private boolean isTypeStartingToken(SyntaxKind nodeKind) {
        return isTypeStartingToken(nodeKind, getNextNextToken());
    }
    
    private static boolean isTypeStartingToken(SyntaxKind nextTokenKind, STToken nextNextToken) {
        switch (nextTokenKind) {
            case IDENTIFIER_TOKEN:
            case SERVICE_KEYWORD:
            case RECORD_KEYWORD:
            case OBJECT_KEYWORD:
            case ABSTRACT_KEYWORD:
            case CLIENT_KEYWORD:
            case OPEN_PAREN_TOKEN: 
            case MAP_KEYWORD: 
            case STREAM_KEYWORD: 
            case TABLE_KEYWORD: 
            case FUNCTION_KEYWORD:
            case OPEN_BRACKET_TOKEN:
            case DISTINCT_KEYWORD:
            case ISOLATED_KEYWORD:
            case TRANSACTIONAL_KEYWORD:
            case TRANSACTION_KEYWORD:
                return true;
            default:
                if (isParameterizedTypeToken(nextTokenKind)) {
                    return true;
                }
                
                if (isSingletonTypeDescStart(nextTokenKind, nextNextToken)) {
                    return true;
                }
                return isSimpleType(nextTokenKind);
        }
    }

    /**
     * Check if the token kind is a type descriptor in terminal expression.
     * <p>
     * simple-type-in-expr :=
     * boolean | int | byte | float | decimal | string | handle | json | anydata | any | never
     *
     * @param nodeKind token kind to check
     * @return <code>true</code> for simple type token in expression. <code>false</code> otherwise.
     */
    private boolean isSimpleTypeInExpression(SyntaxKind nodeKind) {
        switch (nodeKind) {
            case VAR_KEYWORD:
            case READONLY_KEYWORD:
                return false;
            default:
                return isSimpleType(nodeKind);
        }
    }

    static boolean isSimpleType(SyntaxKind nodeKind) {
        switch (nodeKind) {
            case INT_KEYWORD:
            case FLOAT_KEYWORD:
            case DECIMAL_KEYWORD:
            case BOOLEAN_KEYWORD:
            case STRING_KEYWORD:
            case BYTE_KEYWORD:
            case JSON_KEYWORD:
            case HANDLE_KEYWORD:
            case ANY_KEYWORD:
            case ANYDATA_KEYWORD:
            case NEVER_KEYWORD:
            case VAR_KEYWORD:
            case READONLY_KEYWORD:
                return true;
            default:
                return false;
        }
    }

    static boolean isPredeclaredPrefix(SyntaxKind nodeKind) {
        switch (nodeKind) {
            case BOOLEAN_KEYWORD:
            case DECIMAL_KEYWORD:
            case ERROR_KEYWORD:
            case FLOAT_KEYWORD:
            case FUNCTION_KEYWORD:
            case FUTURE_KEYWORD:
            case INT_KEYWORD:
            case MAP_KEYWORD:
            case OBJECT_KEYWORD:
            case STREAM_KEYWORD:
            case STRING_KEYWORD:
            case TABLE_KEYWORD:
            case TRANSACTION_KEYWORD:
            case TYPEDESC_KEYWORD:
            case XML_KEYWORD:
                return true;
            default:
                return false;
        }
    }

    private boolean isQualifiedIdentifierPredeclaredPrefix(SyntaxKind nodeKind) {
       return isPredeclaredPrefix(nodeKind) && getNextNextToken().kind == SyntaxKind.COLON_TOKEN;
    }

    private static SyntaxKind getBuiltinTypeSyntaxKind(SyntaxKind typeKeyword) {
        switch (typeKeyword) {
            case INT_KEYWORD:
                return SyntaxKind.INT_TYPE_DESC;
            case FLOAT_KEYWORD:
                return SyntaxKind.FLOAT_TYPE_DESC;
            case DECIMAL_KEYWORD:
                return SyntaxKind.DECIMAL_TYPE_DESC;
            case BOOLEAN_KEYWORD:
                return SyntaxKind.BOOLEAN_TYPE_DESC;
            case STRING_KEYWORD:
                return SyntaxKind.STRING_TYPE_DESC;
            case BYTE_KEYWORD:
                return SyntaxKind.BYTE_TYPE_DESC;
            case JSON_KEYWORD:
                return SyntaxKind.JSON_TYPE_DESC;
            case HANDLE_KEYWORD:
                return SyntaxKind.HANDLE_TYPE_DESC;
            case ANY_KEYWORD:
                return SyntaxKind.ANY_TYPE_DESC;
            case ANYDATA_KEYWORD:
                return SyntaxKind.ANYDATA_TYPE_DESC;
            case NEVER_KEYWORD:
                return SyntaxKind.NEVER_TYPE_DESC;
            case VAR_KEYWORD:
                return SyntaxKind.VAR_TYPE_DESC;
            case READONLY_KEYWORD:
                return SyntaxKind.READONLY_TYPE_DESC;
            default:
                assert false : typeKeyword + " is not a built-in type";
                return SyntaxKind.TYPE_REFERENCE;
        }
    }

    /**
     * Parse fork-keyword.
     *
     * @return Fork-keyword node
     */
    private STNode parseForkKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FORK_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FORK_KEYWORD);
            return parseForkKeyword();
        }
    }

    /**
     * Parse fork statement.
     * <code>fork-stmt := fork { named-worker-decl+ }</code>
     *
     * @return Fork statement
     */
    private STNode parseForkStatement() {
        startContext(ParserRuleContext.FORK_STMT);
        STNode forkKeyword = parseForkKeyword();
        STNode openBrace = parseOpenBrace();

        
        ArrayList<STNode> workers = new ArrayList<>();
        while (!isEndOfStatements()) {
            STNode stmt = parseStatement();
            if (stmt == null) {
                break;
            }

            if (validateStatement(stmt)) {
                continue;
            }

            switch (stmt.kind) {
                case NAMED_WORKER_DECLARATION:
                    workers.add(stmt);
                    break;
                default:
                    
                    
                    
                    if (workers.isEmpty()) {
                        openBrace = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(openBrace, stmt,
                                DiagnosticErrorCode.ERROR_ONLY_NAMED_WORKERS_ALLOWED_HERE);
                    } else {
                        updateLastNodeInListWithInvalidNode(workers, stmt,
                                DiagnosticErrorCode.ERROR_ONLY_NAMED_WORKERS_ALLOWED_HERE);
                    }
            }
        }

        STNode namedWorkerDeclarations = STNodeFactory.createNodeList(workers);
        STNode closeBrace = parseCloseBrace();
        endContext();

        STNode forkStmt =
                STNodeFactory.createForkStatementNode(forkKeyword, openBrace, namedWorkerDeclarations, closeBrace);
        if (isNodeListEmpty(namedWorkerDeclarations)) {
            return SyntaxErrors.addDiagnostic(forkStmt,
                    DiagnosticErrorCode.ERROR_MISSING_NAMED_WORKER_DECLARATION_IN_FORK_STMT);
        }

        return forkStmt;
    }

    /**
     * Parse trap expression.
     * <p>
     * <code>
     * trap-expr := trap expression
     * </code>
     *
     * @param allowActions Allow actions
     * @param isRhsExpr    Whether this is a RHS expression or not
     * @return Trap expression node
     */
    private STNode parseTrapExpression(boolean isRhsExpr, boolean allowActions, boolean isInConditionalExpr) {
        STNode trapKeyword = parseTrapKeyword();
        STNode expr = parseExpression(OperatorPrecedence.TRAP, isRhsExpr, allowActions, isInConditionalExpr);
        if (isAction(expr)) {
            return STNodeFactory.createTrapExpressionNode(SyntaxKind.TRAP_ACTION, trapKeyword, expr);
        }

        return STNodeFactory.createTrapExpressionNode(SyntaxKind.TRAP_EXPRESSION, trapKeyword, expr);
    }

    /**
     * Parse trap-keyword.
     *
     * @return Trap-keyword node
     */
    private STNode parseTrapKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.TRAP_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.TRAP_KEYWORD);
            return parseTrapKeyword();
        }
    }

    /**
     * Parse list constructor expression.
     * <p>
     * <code>
     * list-constructor-expr := [ [ list-members  ] ]
     * <br/>
     * list-members  := list-member  (, list-member)*
     * <br/>
     * list-member := expression | spread-member
     * <br/>
     * spread-member := ... expression
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseListConstructorExpr() {
        startContext(ParserRuleContext.LIST_CONSTRUCTOR);
        STNode openBracket = parseOpenBracket();
        STNode listMembers = parseListMembers();
        STNode closeBracket = parseCloseBracket();
        endContext();
        return STNodeFactory.createListConstructorExpressionNode(openBracket, listMembers, closeBracket);
    }

    /**
     * Parse optional list member list.
     *
     * @return Parsed node
     */
    private STNode parseListMembers() {
        List<STNode> listMembers = new ArrayList<>();
        if (isEndOfListConstructor(peek().kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        STNode listMember = parseListMember();
        listMembers.add(listMember);
        return parseListMembers(listMembers);
    }

    private STNode parseListMembers(List<STNode> listMembers) {
        
        STNode listConstructorMemberEnd;
        while (!isEndOfListConstructor(peek().kind)) {
            listConstructorMemberEnd = parseListConstructorMemberEnd();
            if (listConstructorMemberEnd == null) {
                break;
            }
            listMembers.add(listConstructorMemberEnd);

            STNode listMember = parseListMember();
            listMembers.add(listMember);
        }

        return STNodeFactory.createNodeList(listMembers);
    }

    /**
     * Parse list member.
     * <p>
     * <code>
     * list-member := expression | spread-member
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseListMember() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.ELLIPSIS_TOKEN) {
            return parseSpreadMember();
        } else {
            return parseExpression();
        }
    }

    /**
     * Parse spread member.
     * <p>
     * <code>
     * spread-member := ... expression
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseSpreadMember() {
        STNode ellipsis = parseEllipsis();
        STNode expr = parseExpression();
        return STNodeFactory.createSpreadMemberNode(ellipsis, expr);
    }

    private boolean isEndOfListConstructor(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACKET_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseListConstructorMemberEnd() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return consume();
            case CLOSE_BRACKET_TOKEN:
                return null;
            default:
                recover(nextToken, ParserRuleContext.LIST_CONSTRUCTOR_MEMBER_END);
                return parseListConstructorMemberEnd();
        }
    }

    /**
     * Parse foreach statement.
     * <code>foreach-stmt := foreach typed-binding-pattern in action-or-expr block-stmt [on-fail-clause]</code>
     *
     * @return foreach statement
     */
    private STNode parseForEachStatement() {
        startContext(ParserRuleContext.FOREACH_STMT);
        STNode forEachKeyword = parseForEachKeyword();
        STNode typedBindingPattern = parseTypedBindingPattern(ParserRuleContext.FOREACH_STMT);
        STNode inKeyword = parseInKeyword();
        STNode actionOrExpr = parseActionOrExpression();
        STNode blockStatement = parseBlockNode();
        endContext();
        STNode onFailClause = parseOptionalOnFailClause();
        return STNodeFactory.createForEachStatementNode(forEachKeyword, typedBindingPattern, inKeyword, actionOrExpr,
                blockStatement, onFailClause);
    }

    /**
     * Parse foreach-keyword.
     *
     * @return ForEach-keyword node
     */
    private STNode parseForEachKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FOREACH_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FOREACH_KEYWORD);
            return parseForEachKeyword();
        }
    }

    /**
     * Parse in-keyword.
     *
     * @return In-keyword node
     */
    private STNode parseInKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.IN_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.IN_KEYWORD);
            return parseInKeyword();
        }
    }

    /**
     * Parse type cast expression.
     * <p>
     * <code>
     * type-cast-expr := < type-cast-param > expression
     * <br/>
     * type-cast-param := [annots] type-descriptor | annots
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseTypeCastExpr(boolean isRhsExpr, boolean allowActions, boolean isInConditionalExpr) {
        startContext(ParserRuleContext.TYPE_CAST);
        STNode ltToken = parseLTToken();
        return parseTypeCastExpr(ltToken, isRhsExpr, allowActions, isInConditionalExpr);
    }

    private STNode parseTypeCastExpr(STNode ltToken, boolean isRhsExpr, boolean allowActions,
                                     boolean isInConditionalExpr) {
        STNode typeCastParam = parseTypeCastParam();
        STNode gtToken = parseGTToken();
        endContext();

        
        
        STNode expression =
                parseExpression(OperatorPrecedence.EXPRESSION_ACTION, isRhsExpr, allowActions, isInConditionalExpr);
        return STNodeFactory.createTypeCastExpressionNode(ltToken, typeCastParam, gtToken, expression);
    }

    private STNode parseTypeCastParam() {
        STNode annot;
        STNode type;
        STToken token = peek();

        switch (token.kind) {
            case AT_TOKEN:
                annot = parseOptionalAnnotations();
                token = peek();
                if (isTypeStartingToken(token.kind)) {
                    type = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_ANGLE_BRACKETS);
                } else {
                    type = STNodeFactory.createEmptyNode();
                }
                break;
            default:
                annot = STNodeFactory.createEmptyNode();
                type = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_ANGLE_BRACKETS);
                break;
        }

        return STNodeFactory.createTypeCastParamNode(getAnnotations(annot), type);
    }

    /**
     * Parse table constructor expression.
     * <p>
     * <code>
     * table-constructor-expr-rhs := [ [row-list] ]
     * </code>
     *
     * @param tableKeyword tableKeyword that precedes this rhs
     * @param keySpecifier keySpecifier that precedes this rhs
     * @return Parsed node
     */
    private STNode parseTableConstructorExprRhs(STNode tableKeyword, STNode keySpecifier) {
        switchContext(ParserRuleContext.TABLE_CONSTRUCTOR);
        STNode openBracket = parseOpenBracket();
        STNode rowList = parseRowList();
        STNode closeBracket = parseCloseBracket();
        return STNodeFactory.createTableConstructorExpressionNode(tableKeyword, keySpecifier, openBracket, rowList,
                closeBracket);
    }

    /**
     * Parse table-keyword.
     *
     * @return Table-keyword node
     */
    private STNode parseTableKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.TABLE_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.TABLE_KEYWORD);
            return parseTableKeyword();
        }
    }

    /**
     * Parse table rows.
     * <p>
     * <code>row-list := [ mapping-constructor-expr (, mapping-constructor-expr)* ]</code>
     *
     * @return Parsed node
     */
    private STNode parseRowList() {
        STToken nextToken = peek();
        
        if (isEndOfTableRowList(nextToken.kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        List<STNode> mappings = new ArrayList<>();
        STNode mapExpr = parseMappingConstructorExpr();
        mappings.add(mapExpr);

        
        nextToken = peek();
        STNode rowEnd;
        while (!isEndOfTableRowList(nextToken.kind)) {
            rowEnd = parseTableRowEnd();
            if (rowEnd == null) {
                break;
            }

            mappings.add(rowEnd);
            mapExpr = parseMappingConstructorExpr();
            mappings.add(mapExpr);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(mappings);
    }

    private boolean isEndOfTableRowList(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACKET_TOKEN:
                return true;
            case COMMA_TOKEN:
            case OPEN_BRACE_TOKEN:
                return false;
            default:
                return isEndOfMappingConstructor(tokenKind);
        }
    }

    private STNode parseTableRowEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACKET_TOKEN:
            case EOF_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.TABLE_ROW_END);
                return parseTableRowEnd();
        }
    }

    /**
     * Parse key specifier.
     * <p>
     * <code>key-specifier := key ( [ field-name (, field-name)* ] )</code>
     *
     * @return Parsed node
     */
    private STNode parseKeySpecifier() {
        startContext(ParserRuleContext.KEY_SPECIFIER);
        STNode keyKeyword = parseKeyKeyword();
        STNode openParen = parseOpenParenthesis();
        STNode fieldNames = parseFieldNames();
        STNode closeParen = parseCloseParenthesis();
        endContext();
        return STNodeFactory.createKeySpecifierNode(keyKeyword, openParen, fieldNames, closeParen);
    }

    /**
     * Parse key-keyword.
     *
     * @return Key-keyword node
     */
    private STNode parseKeyKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.KEY_KEYWORD) {
            return consume();
        }

        if (isKeyKeyword(token)) {
            
            return getKeyKeyword(consume());
        }

        recover(token, ParserRuleContext.KEY_KEYWORD);
        return parseKeyKeyword();
    }

    static boolean isKeyKeyword(STToken token) {
        return token.kind == SyntaxKind.IDENTIFIER_TOKEN && LexerTerminals.KEY.equals(token.text());
    }

    private STNode getKeyKeyword(STToken token) {
        return STNodeFactory.createToken(SyntaxKind.KEY_KEYWORD, token.leadingMinutiae(), token.trailingMinutiae(),
                token.diagnostics());
    }

    private STToken getUnderscoreKeyword(STToken token) {
        return STNodeFactory.createToken(SyntaxKind.UNDERSCORE_KEYWORD, token.leadingMinutiae(),
                token.trailingMinutiae(), token.diagnostics());
    }

    /**
     * Parse field names.
     * <p>
     * <code>field-name-list := [ field-name (, field-name)* ]</code>
     *
     * @return Parsed node
     */
    private STNode parseFieldNames() {
        STToken nextToken = peek();
        
        if (isEndOfFieldNamesList(nextToken.kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        List<STNode> fieldNames = new ArrayList<>();
        STNode fieldName = parseVariableName();
        fieldNames.add(fieldName);

        
        nextToken = peek();
        STNode leadingComma;
        while (!isEndOfFieldNamesList(nextToken.kind)) {
            leadingComma = parseComma();
            fieldNames.add(leadingComma);
            fieldName = parseVariableName();
            fieldNames.add(fieldName);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(fieldNames);
    }

    private boolean isEndOfFieldNamesList(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case COMMA_TOKEN:
            case IDENTIFIER_TOKEN:
                return false;
            default:
                return true;
        }
    }

    /**
     * Parse error-keyword.
     *
     * @return Parsed error-keyword node
     */
    private STNode parseErrorKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ERROR_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ERROR_KEYWORD);
            return parseErrorKeyword();
        }
    }

    /**
     * Parse stream type descriptor.
     * <p>
     * stream-type-descriptor := stream [stream-type-parameters]
     * <br/>
     * stream-type-parameters := < type-descriptor [, type-descriptor]>
     * </p>
     *
     * @return Parsed stream type descriptor node
     */
    private STNode parseStreamTypeDescriptor(STNode streamKeywordToken) {
        STNode streamTypeParamsNode;
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.LT_TOKEN) {
            streamTypeParamsNode = parseStreamTypeParamsNode();
        } else {
            streamTypeParamsNode = STNodeFactory.createEmptyNode();
        }
        return STNodeFactory.createStreamTypeDescriptorNode(streamKeywordToken, streamTypeParamsNode);
    }

    /**
     * Parse stream type params node.
     * <p>
     * stream-type-parameters := < type-descriptor [, type-descriptor]>
     * </p>
     *
     * @return Parsed stream type params node
     */
    private STNode parseStreamTypeParamsNode() {
        STNode ltToken = parseLTToken();
        startContext(ParserRuleContext.TYPE_DESC_IN_STREAM_TYPE_DESC);
        STNode leftTypeDescNode = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_STREAM_TYPE_DESC);
        STNode streamTypedesc = parseStreamTypeParamsNode(ltToken, leftTypeDescNode);
        endContext();
        return streamTypedesc;
    }

    private STNode parseStreamTypeParamsNode(STNode ltToken, STNode leftTypeDescNode) {
        STNode commaToken, rightTypeDescNode, gtToken;
        switch (peek().kind) {
            case COMMA_TOKEN:
                commaToken = parseComma();
                rightTypeDescNode = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_STREAM_TYPE_DESC);
                break;
            case GT_TOKEN:
                commaToken = STNodeFactory.createEmptyNode();
                rightTypeDescNode = STNodeFactory.createEmptyNode();
                break;
            default:
                recover(peek(), ParserRuleContext.STREAM_TYPE_FIRST_PARAM_RHS);
                return parseStreamTypeParamsNode(ltToken, leftTypeDescNode);
        }

        gtToken = parseGTToken();
        return STNodeFactory.createStreamTypeParamsNode(ltToken, leftTypeDescNode, commaToken, rightTypeDescNode,
                gtToken);
    }

    /**
     * Parse let expression.
     * <p>
     * <code>
     * let-expr := let let-var-decl [, let-var-decl]* in expression
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseLetExpression(boolean isRhsExpr, boolean isInConditionalExpr) {
        STNode letKeyword = parseLetKeyword();
        STNode letVarDeclarations = parseLetVarDeclarations(ParserRuleContext.LET_EXPR_LET_VAR_DECL, isRhsExpr, false);
        STNode inKeyword = parseInKeyword();

        
        letKeyword = cloneWithDiagnosticIfListEmpty(letVarDeclarations, letKeyword,
                DiagnosticErrorCode.ERROR_MISSING_LET_VARIABLE_DECLARATION);

        
        
        STNode expression = parseExpression(OperatorPrecedence.REMOTE_CALL_ACTION, isRhsExpr, false,
                isInConditionalExpr);
        return STNodeFactory.createLetExpressionNode(letKeyword, letVarDeclarations, inKeyword, expression);
    }

    /**
     * Parse let-keyword.
     *
     * @return Let-keyword node
     */
    private STNode parseLetKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.LET_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.LET_KEYWORD);
            return parseLetKeyword();
        }
    }

    /**
     * Parse let variable declarations.
     * <p>
     * <code>let-var-decl-list := let-var-decl [, let-var-decl]*</code>
     *
     * @return Parsed node
     */
    private STNode parseLetVarDeclarations(ParserRuleContext context, boolean isRhsExpr, boolean allowActions) {
        startContext(context);
        List<STNode> varDecls = new ArrayList<>();
        STToken nextToken = peek();

        if (isEndOfLetVarDeclarations(nextToken, getNextNextToken())) {
            endContext();
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode varDec = parseLetVarDecl(context, isRhsExpr, allowActions);
        varDecls.add(varDec);

        
        nextToken = peek();
        STNode leadingComma;
        while (!isEndOfLetVarDeclarations(nextToken, getNextNextToken())) {
            leadingComma = parseComma();
            varDecls.add(leadingComma);
            varDec = parseLetVarDecl(context, isRhsExpr, allowActions);
            varDecls.add(varDec);
            nextToken = peek();
        }

        endContext();
        return STNodeFactory.createNodeList(varDecls);
    }

    static boolean isEndOfLetVarDeclarations(STToken nextToken, STToken nextNextToken) {
        SyntaxKind tokenKind = nextToken.kind;
        switch (tokenKind) {
            case COMMA_TOKEN:
            case AT_TOKEN:
                return false;
            case IN_KEYWORD:
                return true;
            default:
                if (isGroupOrCollectKeyword(nextToken)) {
                    return true;
                }
                return !isTypeStartingToken(tokenKind, nextNextToken);
        }
    }

    /**
     * Parse let variable declaration.
     * <p>
     * <code>let-var-decl := [annots] typed-binding-pattern = expression</code>
     *
     * @return Parsed node
     */
    private STNode parseLetVarDecl(ParserRuleContext context, boolean isRhsExpr, boolean allowActions) {
        STNode annot = parseOptionalAnnotations();
        STNode typedBindingPattern = parseTypedBindingPattern(ParserRuleContext.LET_EXPR_LET_VAR_DECL);
        STNode assign = parseAssignOp();
        STNode expression = context ==  ParserRuleContext.LET_CLAUSE_LET_VAR_DECL ?
                parseExpression(OperatorPrecedence.QUERY, isRhsExpr, allowActions) :
                parseExpression(OperatorPrecedence.ANON_FUNC_OR_LET, isRhsExpr, false);

        return STNodeFactory.createLetVariableDeclarationNode(annot, typedBindingPattern, assign, expression);
    }

    /**
     * Parse raw backtick string template expression.
     * <p>
     * <code>BacktickString := `expression`</code>
     *
     * @return Template expression node
     */
    private STNode parseTemplateExpression() {
        STNode type = STNodeFactory.createEmptyNode();
        STNode startingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);
        STNode content = parseTemplateContent();
        STNode endingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);
        return STNodeFactory.createTemplateExpressionNode(SyntaxKind.RAW_TEMPLATE_EXPRESSION, type, startingBackTick,
                content, endingBackTick);
    }

    private STNode parseTemplateContent() {
        List<STNode> items = new ArrayList<>();
        STToken nextToken = peek();
        while (!isEndOfBacktickContent(nextToken.kind)) {
            STNode contentItem = parseTemplateItem();
            items.add(contentItem);
            nextToken = peek();
        }
        return STNodeFactory.createNodeList(items);
    }

    private boolean isEndOfBacktickContent(SyntaxKind kind) {
        switch (kind) {
            case EOF_TOKEN:
            case BACKTICK_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseTemplateItem() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.INTERPOLATION_START_TOKEN) {
            return parseInterpolation();
        }

        
        return consume();
    }

    /**
     * Parse string template expression.
     * <p>
     * <code>string-template-expr := string ` expression `</code>
     *
     * @return String template expression node
     */
    private STNode parseStringTemplateExpression() {
        STNode type = parseStringKeyword();
        STNode startingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);
        STNode content = parseTemplateContent();
        STNode endingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_END);
        return STNodeFactory.createTemplateExpressionNode(SyntaxKind.STRING_TEMPLATE_EXPRESSION, type, startingBackTick,
                content, endingBackTick);
    }

    /**
     * Parse <code>string</code> keyword.
     *
     * @return string keyword node
     */
    private STNode parseStringKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.STRING_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.STRING_KEYWORD);
            return parseStringKeyword();
        }
    }

    /**
     * Parse XML template expression.
     * <p>
     * <code>xml-template-expr := xml BacktickString</code>
     *
     * @return XML template expression
     */
    private STNode parseXMLTemplateExpression() {
        STNode xmlKeyword = parseXMLKeyword();
        STNode startingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);

        if (startingBackTick.isMissing()) {
            return createMissingTemplateExpressionNode(xmlKeyword, SyntaxKind.XML_TEMPLATE_EXPRESSION);
        }

        STNode content = parseTemplateContentAsXML();
        STNode endingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_END);
        return STNodeFactory.createTemplateExpressionNode(SyntaxKind.XML_TEMPLATE_EXPRESSION, xmlKeyword,
                startingBackTick, content, endingBackTick);
    }

    /**
     * Parse <code>xml</code> keyword.
     *
     * @return xml keyword node
     */
    private STNode parseXMLKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.XML_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.XML_KEYWORD);
            return parseXMLKeyword();
        }
    }

    /**
     * Parse the content of the template string as XML. This method first read the
     * input in the same way as the raw-backtick-template (BacktickString). Then
     * it parses the content as XML.
     *
     * @return XML node
     */
    private STNode parseTemplateContentAsXML() {
        
        
        
        ArrayDeque<STNode> expressions = new ArrayDeque<>();
        StringBuilder xmlStringBuilder = new StringBuilder();
        STToken nextToken = peek();
        while (!isEndOfBacktickContent(nextToken.kind)) {
            STNode contentItem = parseTemplateItem();
            if (contentItem.kind == SyntaxKind.TEMPLATE_STRING) {
                xmlStringBuilder.append(((STToken) contentItem).text());
            } else {
                xmlStringBuilder.append("${}");
                expressions.add(contentItem);
            }
            nextToken = peek();
        }

        CharReader charReader = CharReader.from(xmlStringBuilder.toString());
        AbstractTokenReader tokenReader = new TokenReader(new XMLLexer(charReader));
        XMLParser xmlParser = new XMLParser(tokenReader, expressions);
        return xmlParser.parse();
    }

    /**
     * Parse regular expression constructor.
     * <p>
     * <code>regexp-constructor-expr := re BacktickString</code>
     *
     * @return Regular expression template expression
     */
    private STNode parseRegExpTemplateExpression() {
        STNode reKeyword = consume();
        STNode startingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);

        if (startingBackTick.isMissing()) {
            return createMissingTemplateExpressionNode(reKeyword, SyntaxKind.REGEX_TEMPLATE_EXPRESSION);
        }

        STNode content = parseTemplateContentAsRegExp();
        STNode endingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_END);
        return STNodeFactory.createTemplateExpressionNode(SyntaxKind.REGEX_TEMPLATE_EXPRESSION, reKeyword,
                startingBackTick, content, endingBackTick);
    }

    private STNode createMissingTemplateExpressionNode(STNode reKeyword, SyntaxKind kind) {
        
        STNode startingBackTick = SyntaxErrors.createMissingToken(SyntaxKind.BACKTICK_TOKEN);
        STNode endingBackTick = SyntaxErrors.createMissingToken(SyntaxKind.BACKTICK_TOKEN);
        STNode content = STAbstractNodeFactory.createEmptyNodeList();
        STNode templateExpr =
                STNodeFactory.createTemplateExpressionNode(kind, reKeyword, startingBackTick, content, endingBackTick);
        templateExpr = SyntaxErrors.addDiagnostic(templateExpr, DiagnosticErrorCode.ERROR_MISSING_BACKTICK_STRING);
        return templateExpr;
    }

    /**
     * Parse the content of the template string as regular expression. This method first read the
     * input in the same way as the raw-backtick-template (BacktickString). Then
     * it parses the content as regular expression.
     *
     * @return Template expression node
     */
    private STNode parseTemplateContentAsRegExp() {
        
        
        
        this.tokenReader.startMode(ParserMode.REGEXP);
        ArrayDeque<STNode> expressions = new ArrayDeque<>();
        StringBuilder regExpStringBuilder = new StringBuilder();
        STToken nextToken = peek();
        while (!isEndOfBacktickContent(nextToken.kind)) {
            STNode contentItem = parseTemplateItem();
            if (contentItem.kind == SyntaxKind.TEMPLATE_STRING) {
                regExpStringBuilder.append(((STToken) contentItem).text());
            } else {
                regExpStringBuilder.append("${}");
                expressions.add(contentItem);
            }
            nextToken = peek();
        }
        this.tokenReader.endMode();
        CharReader charReader = CharReader.from(regExpStringBuilder.toString());
        AbstractTokenReader tokenReader = new TokenReader(new RegExpLexer(charReader));
        RegExpParser regExpParser = new RegExpParser(tokenReader, expressions);
        return regExpParser.parse();
    }

    /**
     * Parse interpolation of a back-tick string.
     * <p>
     * <code>
     * interpolation := ${ expression }
     * </code>
     *
     * @return Interpolation node
     */
    private STNode parseInterpolation() {
        startContext(ParserRuleContext.INTERPOLATION);
        STNode interpolStart = parseInterpolationStart();
        STNode expr = parseExpression();

        
        while (!isEndOfInterpolation()) {
            STToken nextToken = consume();
            expr = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(expr, nextToken,
                    DiagnosticErrorCode.ERROR_INVALID_TOKEN, nextToken.text());
        }

        STNode closeBrace = parseCloseBrace();
        endContext();
        return STNodeFactory.createInterpolationNode(interpolStart, expr, closeBrace);
    }

    private boolean isEndOfInterpolation() {
        SyntaxKind nextTokenKind = peek().kind;
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case BACKTICK_TOKEN:
                return true;
            default:
                
                ParserMode currentLexerMode = this.tokenReader.getCurrentMode();
                return nextTokenKind == SyntaxKind.CLOSE_BRACE_TOKEN && currentLexerMode != ParserMode.INTERPOLATION &&
                        currentLexerMode != ParserMode.INTERPOLATION_BRACED_CONTENT;
        }
    }

    /**
     * Parse interpolation start token.
     * <p>
     * <code>interpolation-start := ${</code>
     *
     * @return Interpolation start token
     */
    private STNode parseInterpolationStart() {
        STToken token = peek();
        if (token.kind == SyntaxKind.INTERPOLATION_START_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.INTERPOLATION_START_TOKEN);
            return parseInterpolationStart();
        }
    }

    /**
     * Parse back-tick token.
     *
     * @return Back-tick token
     */
    private STNode parseBacktickToken(ParserRuleContext ctx) {
        STToken token = peek();
        if (token.kind == SyntaxKind.BACKTICK_TOKEN) {
            return consume();
        } else {
            recover(token, ctx);
            return parseBacktickToken(ctx);
        }
    }

    /**
     * Parse table type descriptor.
     * <p>
     * table-type-descriptor := table row-type-parameter [key-constraint]
     * row-type-parameter := type-parameter
     * key-constraint := key-specifier | key-type-constraint
     * key-specifier := key ( [ field-name (, field-name)* ] )
     * key-type-constraint := key type-parameter
     * </p>
     *
     * @return Parsed table type desc node.
     */
    private STNode parseTableTypeDescriptor(STNode tableKeywordToken) {
        STNode rowTypeParameterNode = parseRowTypeParameter();
        STNode keyConstraintNode;
        STToken nextToken = peek();
        if (isKeyKeyword(nextToken)) {
            STNode keyKeywordToken = getKeyKeyword(consume());
            keyConstraintNode = parseKeyConstraint(keyKeywordToken);
        } else {
            keyConstraintNode = STNodeFactory.createEmptyNode();
        }
        return STNodeFactory.createTableTypeDescriptorNode(tableKeywordToken, rowTypeParameterNode, keyConstraintNode);
    }

    /**
     * Parse row type parameter node.
     * <p>
     * row-type-parameter := type-parameter
     * </p>
     *
     * @return Parsed node.
     */
    private STNode parseRowTypeParameter() {
        startContext(ParserRuleContext.ROW_TYPE_PARAM);
        STNode rowTypeParameterNode = parseTypeParameter();
        endContext();
        return rowTypeParameterNode;
    }

    /**
     * Parse type parameter node.
     * <p>
     * type-parameter := < type-descriptor >
     * </p>
     *
     * @return Parsed node
     */
    private STNode parseTypeParameter() {
        STNode ltToken = parseLTToken();
        STNode typeNode = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_ANGLE_BRACKETS);
        STNode gtToken = parseGTToken();
        return STNodeFactory.createTypeParameterNode(ltToken, typeNode, gtToken);
    }

    /**
     * Parse key constraint.
     * <p>
     * key-constraint := key-specifier | key-type-constraint
     * </p>
     *
     * @return Parsed node.
     */
    private STNode parseKeyConstraint(STNode keyKeywordToken) {
        switch (peek().kind) {
            case OPEN_PAREN_TOKEN:
                return parseKeySpecifier(keyKeywordToken);
            case LT_TOKEN:
                return parseKeyTypeConstraint(keyKeywordToken);
            default:
                recover(peek(), ParserRuleContext.KEY_CONSTRAINTS_RHS);
                return parseKeyConstraint(keyKeywordToken);
        }
    }

    /**
     * Parse key specifier given parsed key keyword token.
     * <p>
     * <code>key-specifier := key ( [ field-name (, field-name)* ] )</code>
     *
     * @return Parsed node
     */
    private STNode parseKeySpecifier(STNode keyKeywordToken) {
        startContext(ParserRuleContext.KEY_SPECIFIER);
        STNode openParenToken = parseOpenParenthesis();
        STNode fieldNamesNode = parseFieldNames();
        STNode closeParenToken = parseCloseParenthesis();
        endContext();
        return STNodeFactory.createKeySpecifierNode(keyKeywordToken, openParenToken, fieldNamesNode, closeParenToken);
    }

    /**
     * Parse key type constraint.
     * <p>
     * key-type-constraint := key type-parameter
     * </p>
     *
     * @return Parsed node
     */
    private STNode parseKeyTypeConstraint(STNode keyKeywordToken) {
        STNode typeParameterNode = parseTypeParameter();
        return STNodeFactory.createKeyTypeConstraintNode(keyKeywordToken, typeParameterNode);
    }

    /**
     * Parse function type descriptor.
     * <p>
     * <code>
     * function-type-descriptor := function-quals function function-signature 
     * <br/>&nbsp;| [isolated] function
     * <br/>
     * function-quals := (transactional | isolated)*
     * </code>
     *
     * @param qualifiers Preceding type descriptor qualifiers
     * @return Function type descriptor node
     */
    private STNode parseFunctionTypeDesc(List<STNode> qualifiers) {
        startContext(ParserRuleContext.FUNC_TYPE_DESC);
        STNode functionKeyword = parseFunctionKeyword();

        boolean hasFuncSignature = false;
        STNode signature = STNodeFactory.createEmptyNode();
        if (peek().kind == SyntaxKind.OPEN_PAREN_TOKEN ||
                isSyntaxKindInList(qualifiers, SyntaxKind.TRANSACTIONAL_KEYWORD)) {
            signature = parseFuncSignature(true);
            hasFuncSignature = true;
        }

        
        STNode[] nodes = createFuncTypeQualNodeList(qualifiers, functionKeyword, hasFuncSignature);
        STNode qualifierList = nodes[0];
        functionKeyword = nodes[1];

        endContext();
        return STNodeFactory.createFunctionTypeDescriptorNode(qualifierList, functionKeyword, signature);
    }
    
    private STNode getLastNodeInList(List<STNode> nodeList) {
        return nodeList.get(nodeList.size() - 1);
    }

    private STNode[] createFuncTypeQualNodeList(List<STNode> qualifierList, STNode functionKeyword,
                                                boolean hasFuncSignature) {
        
        List<STNode> validatedList = new ArrayList<>();

        for (int i = 0; i < qualifierList.size(); i++) {
            STNode qualifier = qualifierList.get(i);
            int nextIndex = i + 1;

            if (isSyntaxKindInList(validatedList, qualifier.kind)) {
                updateLastNodeInListWithInvalidNode(validatedList, qualifier,
                        DiagnosticErrorCode.ERROR_DUPLICATE_QUALIFIER, ((STToken) qualifier).text());
            } else if (hasFuncSignature && isRegularFuncQual(qualifier.kind)) {
                validatedList.add(qualifier);
            } else if (qualifier.kind == SyntaxKind.ISOLATED_KEYWORD) {
                validatedList.add(qualifier);
            } else if (qualifierList.size() == nextIndex) {
                functionKeyword = SyntaxErrors.cloneWithLeadingInvalidNodeMinutiae(functionKeyword, qualifier,
                        DiagnosticErrorCode.ERROR_QUALIFIER_NOT_ALLOWED, ((STToken) qualifier).text());
            } else {
                updateANodeInListWithLeadingInvalidNode(qualifierList, nextIndex, qualifier,
                        DiagnosticErrorCode.ERROR_QUALIFIER_NOT_ALLOWED, ((STToken) qualifier).text());
            }
        }

        STNode nodeList = STNodeFactory.createNodeList(validatedList);
        return new STNode[]{ nodeList, functionKeyword };
    }

    private boolean isRegularFuncQual(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case ISOLATED_KEYWORD:
            case TRANSACTIONAL_KEYWORD:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse explicit anonymous function expression.
     * <p>
     * <code>explicit-anonymous-function-expr :=
     * [annots] (isolated| transactional) function function-signature anon-func-body</code>
     *
     * @param annots     Annotations.
     * @param qualifiers Function qualifiers
     * @param isRhsExpr  Is expression in rhs context
     * @return Anonymous function expression node
     */
    private STNode parseExplicitFunctionExpression(STNode annots, List<STNode> qualifiers, boolean isRhsExpr) {
        startContext(ParserRuleContext.ANON_FUNC_EXPRESSION);
        STNode funcKeyword = parseFunctionKeyword();

        
        STNode[] nodes = createFuncTypeQualNodeList(qualifiers, funcKeyword, true);
        STNode qualifierList = nodes[0];
        funcKeyword = nodes[1];

        STNode funcSignature = parseFuncSignature(false);
        
        STNode funcBody = parseAnonFuncBody(isRhsExpr);
        return STNodeFactory.createExplicitAnonymousFunctionExpressionNode(annots, qualifierList, funcKeyword,
                funcSignature, funcBody);
    }

    /**
     * Parse anonymous function body.
     * <p>
     * <code>anon-func-body := block-function-body | expr-function-body</code>
     *
     * @param isRhsExpr Is expression in rhs context
     * @return Anon function body node
     */
    private STNode parseAnonFuncBody(boolean isRhsExpr) {
        switch (peek().kind) {
            case OPEN_BRACE_TOKEN:
            case EOF_TOKEN:
                STNode body = parseFunctionBodyBlock(true);
                endContext();
                return body;
            case RIGHT_DOUBLE_ARROW_TOKEN:
                
                
                endContext();
                return parseExpressionFuncBody(true, isRhsExpr);
            default:
                recover(peek(), ParserRuleContext.ANON_FUNC_BODY);
                return parseAnonFuncBody(isRhsExpr);
        }
    }

    /**
     * Parse expression function body.
     * <p>
     * <code>expr-function-body := => expression</code>
     *
     * @param isAnon    Is anonymous function.
     * @param isRhsExpr Is expression in rhs context
     * @return Expression function body node
     */
    private STNode parseExpressionFuncBody(boolean isAnon, boolean isRhsExpr) {
        STNode rightDoubleArrow = parseDoubleRightArrow();

        
        
        STNode expression = parseExpression(OperatorPrecedence.REMOTE_CALL_ACTION, isRhsExpr, false);

        STNode semiColon;
        if (isAnon) {
            semiColon = STNodeFactory.createEmptyNode();
        } else {
            semiColon = parseSemicolon();
        }
        return STNodeFactory.createExpressionFunctionBodyNode(rightDoubleArrow, expression, semiColon);
    }

    /**
     * Parse '=>' token.
     *
     * @return Double right arrow token
     */
    private STNode parseDoubleRightArrow() {
        STToken token = peek();
        if (token.kind == SyntaxKind.RIGHT_DOUBLE_ARROW_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.EXPR_FUNC_BODY_START);
            return parseDoubleRightArrow();
        }
    }

    private STNode parseImplicitAnonFunc(STNode params, boolean isRhsExpr) {
        switch (params.kind) {
            case SIMPLE_NAME_REFERENCE:
            case INFER_PARAM_LIST:
                break;
            case BRACED_EXPRESSION:
                params = getAnonFuncParam((STBracedExpressionNode) params);
                break;
            case NIL_LITERAL:
                STNilLiteralNode nilLiteralNode = (STNilLiteralNode) params;
                params = STNodeFactory.createImplicitAnonymousFunctionParameters(nilLiteralNode.openParenToken,
                        STNodeFactory.createNodeList(new ArrayList<>()), nilLiteralNode.closeParenToken);
                break;
            default:
                STToken syntheticParam = STNodeFactory.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
                syntheticParam = SyntaxErrors.cloneWithLeadingInvalidNodeMinutiae(syntheticParam, params,
                        DiagnosticErrorCode.ERROR_INVALID_PARAM_LIST_IN_INFER_ANONYMOUS_FUNCTION_EXPR);
                params = STNodeFactory.createSimpleNameReferenceNode(syntheticParam);
        }

        STNode rightDoubleArrow = parseDoubleRightArrow();
        
        
        STNode expression = parseExpression(OperatorPrecedence.REMOTE_CALL_ACTION, isRhsExpr, false);
        return STNodeFactory.createImplicitAnonymousFunctionExpressionNode(params, rightDoubleArrow, expression);
    }

    /**
     * Create a new anon-func-param node from a braced expression.
     *
     * @param bracedExpression Braced expression
     * @return Anon-func param node
     */
    private STNode getAnonFuncParam(STBracedExpressionNode bracedExpression) {
        List<STNode> paramList = new ArrayList<>();
        STNode innerExpression = bracedExpression.expression;
        STNode openParen = bracedExpression.openParen;
        if (innerExpression.kind == SyntaxKind.SIMPLE_NAME_REFERENCE) {
            paramList.add(innerExpression);
        } else {
            openParen = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(openParen, innerExpression,
                    DiagnosticErrorCode.ERROR_INVALID_PARAM_LIST_IN_INFER_ANONYMOUS_FUNCTION_EXPR);
        }
        return STNodeFactory.createImplicitAnonymousFunctionParameters(openParen,
                STNodeFactory.createNodeList(paramList), bracedExpression.closeParen);
    }

    /**
     * Parse implicit anon function expression.
     *
     * @param openParen  Open parenthesis token
     * @param firstParam First parameter
     * @param isRhsExpr  Is expression in rhs context
     * @return Implicit anon function expression node
     */
    private STNode parseImplicitAnonFunc(STNode openParen, STNode firstParam, boolean isRhsExpr) {
        List<STNode> paramList = new ArrayList<>();
        paramList.add(firstParam);

        
        STToken nextToken = peek();
        STNode paramEnd;
        STNode param;
        while (!isEndOfAnonFuncParametersList(nextToken.kind)) {
            paramEnd = parseImplicitAnonFuncParamEnd();
            if (paramEnd == null) {
                break;
            }

            paramList.add(paramEnd);
            param = parseIdentifier(ParserRuleContext.IMPLICIT_ANON_FUNC_PARAM);
            param = STNodeFactory.createSimpleNameReferenceNode(param);
            paramList.add(param);
            nextToken = peek();
        }

        STNode params = STNodeFactory.createNodeList(paramList);
        STNode closeParen = parseCloseParenthesis();
        endContext(); 

        STNode inferedParams = STNodeFactory.createImplicitAnonymousFunctionParameters(openParen, params, closeParen);
        return parseImplicitAnonFunc(inferedParams, isRhsExpr);
    }

    private STNode parseImplicitAnonFuncParamEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_PAREN_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.ANON_FUNC_PARAM_RHS);
                return parseImplicitAnonFuncParamEnd();
        }
    }

    private boolean isEndOfAnonFuncParametersList(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case CLOSE_BRACKET_TOKEN:
            case SEMICOLON_TOKEN:
            case RETURNS_KEYWORD:
            case TYPE_KEYWORD:
            case LISTENER_KEYWORD:
            case IF_KEYWORD:
            case WHILE_KEYWORD:
            case DO_KEYWORD:
            case OPEN_BRACE_TOKEN:
            case RIGHT_DOUBLE_ARROW_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse tuple type descriptor.
     * <p>
     * <code>tuple-type-descriptor := [ tuple-member-type-descriptors ]
     * <br/><br/>
     * tuple-member-type-descriptors := member-type-descriptor (, member-type-descriptor)* [, tuple-rest-descriptor]
     * | [ tuple-rest-descriptor ]
     * <br/><br/>
     * member-type-descriptor := [annots] type-descriptor
     * tuple-rest-descriptor := type-descriptor ...
     * </code>
     *
     * @return
     */
    private STNode parseTupleTypeDesc() {
        STNode openBracket = parseOpenBracket();
        startContext(ParserRuleContext.TUPLE_MEMBERS);
        STNode memberTypeDesc = parseTupleMemberTypeDescList();
        STNode closeBracket = parseCloseBracket();
        endContext();

        
        openBracket = cloneWithDiagnosticIfListEmpty(memberTypeDesc, openBracket,
                DiagnosticErrorCode.ERROR_MISSING_TYPE_DESC);

        return STNodeFactory.createTupleTypeDescriptorNode(openBracket, memberTypeDesc, closeBracket);
    }

    /**
     * Parse tuple member type descriptors.
     *
     * @return Parsed node
     */
    private STNode parseTupleMemberTypeDescList() {
        List<STNode> typeDescList = new ArrayList<>();
        STToken nextToken = peek();

        
        if (isEndOfTypeList(nextToken.kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode typeDesc = parseTupleMember();
        return parseTupleTypeMembers(typeDesc, typeDescList);
    }

    private STNode parseTupleTypeMembers(STNode firstMember, List<STNode> memberList) {
        STNode tupleMemberRhs;
        
        while (!isEndOfTypeList(peek().kind)) {
            if (firstMember.kind == SyntaxKind.REST_TYPE) {
                firstMember = invalidateTypeDescAfterRestDesc(firstMember);
                break;
            }

            tupleMemberRhs = parseTupleMemberRhs();
            if (tupleMemberRhs == null) {
                break;
            }
            memberList.add(firstMember);
            memberList.add(tupleMemberRhs);
            firstMember = parseTupleMember();
        }

        memberList.add(firstMember);
        return STNodeFactory.createNodeList(memberList);
    }

    private STNode parseTupleMember() {
        STNode annot = parseOptionalAnnotations();
        STNode typeDesc = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
        return createMemberOrRestNode(annot, typeDesc);
    }

    private STNode createMemberOrRestNode(STNode annot, STNode typeDesc) {
        STNode tupleMemberRhs = parseTypeDescInTupleRhs();
        if (tupleMemberRhs != null) {
            if (!((STNodeList) annot).isEmpty()) {
                typeDesc = SyntaxErrors.cloneWithLeadingInvalidNodeMinutiae(typeDesc, annot,
                        DiagnosticErrorCode.ERROR_ANNOTATIONS_NOT_ALLOWED_FOR_TUPLE_REST_DESCRIPTOR);
            }
            return STNodeFactory.createRestDescriptorNode(typeDesc, tupleMemberRhs);
        }

        return STNodeFactory.createMemberTypeDescriptorNode(annot, typeDesc);
    }

    private STNode invalidateTypeDescAfterRestDesc(STNode restDescriptor) {
        while (!isEndOfTypeList(peek().kind)) {
            STNode tupleMemberRhs = parseTupleMemberRhs();
            if (tupleMemberRhs == null) {
                break;
            }

            restDescriptor = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(restDescriptor, tupleMemberRhs, null);
            restDescriptor = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(restDescriptor, parseTupleMember(),
                    DiagnosticErrorCode.ERROR_TYPE_DESC_AFTER_REST_DESCRIPTOR);
        }

        return restDescriptor;
    }

    private STNode parseTupleMemberRhs() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACKET_TOKEN:
                return null;
            default:
                recover(nextToken, ParserRuleContext.TUPLE_TYPE_MEMBER_RHS);
                return parseTupleMemberRhs();
        }
    }

    private STNode parseTypeDescInTupleRhs() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
            case CLOSE_BRACKET_TOKEN:
                return null;
            case ELLIPSIS_TOKEN:
                return parseEllipsis();
            default:
                recover(nextToken, ParserRuleContext.TYPE_DESC_IN_TUPLE_RHS);
                return parseTypeDescInTupleRhs();
        }
    }

    private boolean isEndOfTypeList(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case CLOSE_BRACKET_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case EOF_TOKEN:
            case EQUAL_TOKEN:
            case SEMICOLON_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse table constructor or query expression.
     * <p>
     * <code>
     * table-constructor-or-query-expr := table-constructor-expr | query-expr
     * <br/>
     * table-constructor-expr := table [key-specifier] [ [row-list] ]
     * <br/>
     * query-expr := [query-construct-type] query-pipeline select-clause
     * [query-construct-type] query-pipeline select-clause on-conflict-clause?
     * <br/>
     * query-construct-type := table key-specifier | stream | map
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseTableConstructorOrQuery(boolean isRhsExpr, boolean allowActions) {
        startContext(ParserRuleContext.TABLE_CONSTRUCTOR_OR_QUERY_EXPRESSION);
        STNode tableOrQueryExpr = parseTableConstructorOrQueryInternal(isRhsExpr, allowActions);
        endContext();
        return tableOrQueryExpr;
    }

    private STNode parseTableConstructorOrQueryInternal(boolean isRhsExpr, boolean allowActions) {
        STNode queryConstructType;
        switch (peek().kind) {
            case FROM_KEYWORD:
                queryConstructType = STNodeFactory.createEmptyNode();
                return parseQueryExprRhs(queryConstructType, isRhsExpr, allowActions);
            case TABLE_KEYWORD:
                STNode tableKeyword = parseTableKeyword();
                return parseTableConstructorOrQuery(tableKeyword, isRhsExpr, allowActions);
            case STREAM_KEYWORD:
            case MAP_KEYWORD:
                STNode streamOrMapKeyword = consume();
                STNode keySpecifier = STNodeFactory.createEmptyNode();
                queryConstructType = parseQueryConstructType(streamOrMapKeyword, keySpecifier);
                return parseQueryExprRhs(queryConstructType, isRhsExpr, allowActions);
            default:
                recover(peek(), ParserRuleContext.TABLE_CONSTRUCTOR_OR_QUERY_START);
                return parseTableConstructorOrQueryInternal(isRhsExpr, allowActions);
        }

    }

    private STNode parseTableConstructorOrQuery(STNode tableKeyword, boolean isRhsExpr, boolean allowActions) {
        STNode keySpecifier;
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_BRACKET_TOKEN:
                keySpecifier = STNodeFactory.createEmptyNode();
                return parseTableConstructorExprRhs(tableKeyword, keySpecifier);
            case KEY_KEYWORD:
                keySpecifier = parseKeySpecifier();
                return parseTableConstructorOrQueryRhs(tableKeyword, keySpecifier, isRhsExpr, allowActions);
            case IDENTIFIER_TOKEN:
                if (isKeyKeyword(nextToken)) {
                    keySpecifier = parseKeySpecifier();
                    return parseTableConstructorOrQueryRhs(tableKeyword, keySpecifier, isRhsExpr, allowActions);
                }
                break;
            default:
                break;
        }

        recover(peek(), ParserRuleContext.TABLE_KEYWORD_RHS);
        return parseTableConstructorOrQuery(tableKeyword, isRhsExpr, allowActions);
    }

    private STNode parseTableConstructorOrQueryRhs(STNode tableKeyword, STNode keySpecifier, boolean isRhsExpr,
                                                   boolean allowActions) {
        switch (peek().kind) {
            case FROM_KEYWORD:
                return parseQueryExprRhs(parseQueryConstructType(tableKeyword, keySpecifier), isRhsExpr, allowActions);
            case OPEN_BRACKET_TOKEN:
                return parseTableConstructorExprRhs(tableKeyword, keySpecifier);
            default:
                recover(peek(), ParserRuleContext.TABLE_CONSTRUCTOR_OR_QUERY_RHS);
                return parseTableConstructorOrQueryRhs(tableKeyword, keySpecifier, isRhsExpr, allowActions);
        }
    }

    /**
     * Parse query construct type.
     * <p>
     * <code>query-construct-type := table key-specifier | stream | map</code>
     *
     * @return Parsed node
     */
    private STNode parseQueryConstructType(STNode keyword, STNode keySpecifier) {
        return STNodeFactory.createQueryConstructTypeNode(keyword, keySpecifier);
    }

    /**
     * Parse query action or expression.
     * <p>
     * <code>
     * query-expr-rhs := query-pipeline select-clause
     * query-pipeline select-clause on-conflict-clause?
     * <br/>
     * query-pipeline := from-clause intermediate-clause*
     * </code>
     *
     * @param queryConstructType queryConstructType that precedes this rhs
     * @return Parsed node
     */
    private STNode parseQueryExprRhs(STNode queryConstructType, boolean isRhsExpr, boolean allowActions) {

        switchContext(ParserRuleContext.QUERY_EXPRESSION);
        STNode fromClause = parseFromClause(isRhsExpr, allowActions);

        List<STNode> clauses = new ArrayList<>();
        STNode intermediateClause;
        STNode selectClause = null;
        STNode collectClause = null;
        while (!isEndOfIntermediateClause(peek().kind)) {
            intermediateClause = parseIntermediateClause(isRhsExpr, allowActions);
            if (intermediateClause == null) {
                break;
            }

            
            if (selectClause != null) {
                selectClause = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(selectClause, intermediateClause,
                        DiagnosticErrorCode.ERROR_MORE_CLAUSES_AFTER_SELECT_CLAUSE);
                continue;
            } else if (collectClause != null) {
                collectClause = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(collectClause, intermediateClause,
                        DiagnosticErrorCode.ERROR_MORE_CLAUSES_AFTER_COLLECT_CLAUSE);
                continue;
            }

            if (intermediateClause.kind == SyntaxKind.SELECT_CLAUSE) {
                selectClause = intermediateClause;
            } else if (intermediateClause.kind == SyntaxKind.COLLECT_CLAUSE) {
                collectClause = intermediateClause;
            } else {
                clauses.add(intermediateClause);
                continue;
            }

            if (isNestedQueryExpr() || !isValidIntermediateQueryStart(peek())) {
                
                
                
                break;
            }
        }

        if (peek().kind == SyntaxKind.DO_KEYWORD) {
            STNode intermediateClauses = STNodeFactory.createNodeList(clauses);
            STNode queryPipeline = STNodeFactory.createQueryPipelineNode(fromClause, intermediateClauses);
            return parseQueryAction(queryConstructType, queryPipeline, selectClause);
        }

        if (selectClause == null && collectClause == null) {
            STNode selectKeyword = SyntaxErrors.createMissingToken(SyntaxKind.SELECT_KEYWORD);
            STNode expr = STNodeFactory
                    .createSimpleNameReferenceNode(SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN));
            selectClause = STNodeFactory.createSelectClauseNode(selectKeyword, expr);

            
            
            if (clauses.isEmpty()) {
                fromClause = SyntaxErrors.addDiagnostic(fromClause, DiagnosticErrorCode.ERROR_MISSING_SELECT_CLAUSE);
            } else {
                int lastIndex = clauses.size() - 1;
                STNode intClauseWithDiagnostic = SyntaxErrors.addDiagnostic(clauses.get(lastIndex),
                        DiagnosticErrorCode.ERROR_MISSING_SELECT_CLAUSE);
                clauses.set(lastIndex, intClauseWithDiagnostic);
            }
        }

        STNode intermediateClauses = STNodeFactory.createNodeList(clauses);
        STNode queryPipeline = STNodeFactory.createQueryPipelineNode(fromClause, intermediateClauses);
        STNode onConflictClause = parseOnConflictClause(isRhsExpr);


        return STNodeFactory.createQueryExpressionNode(queryConstructType, queryPipeline,
                selectClause == null ? collectClause : selectClause, onConflictClause);
    }

    /**
     * Check whether currently parsing query expr is a nested query expression.
     *
     * @return <code>true</code> if currently parsing query-expr is a nested query-expr. <code>false</code> otherwise.
     */
    private boolean isNestedQueryExpr() {
        return Collections.frequency(this.errorHandler.getContextStack(), ParserRuleContext.QUERY_EXPRESSION) > 1;
    }

    private boolean isValidIntermediateQueryStart(STToken token) {
        SyntaxKind syntaxKind = token.kind;
        switch (syntaxKind) {
            case FROM_KEYWORD:
            case WHERE_KEYWORD:
            case LET_KEYWORD:
            case SELECT_KEYWORD:
            case JOIN_KEYWORD:
            case OUTER_KEYWORD:
            case ORDER_KEYWORD:
            case BY_KEYWORD:
            case ASCENDING_KEYWORD:
            case DESCENDING_KEYWORD:
            case LIMIT_KEYWORD:
                return true;
            case IDENTIFIER_TOKEN:
                return isGroupOrCollectKeyword(token);
            default:
                return false;
        }
    }

    private static boolean isGroupOrCollectKeyword(STToken nextToken) {
        if (nextToken.kind != SyntaxKind.IDENTIFIER_TOKEN || !(nextToken instanceof STIdentifierToken)) {
            return false;
        }
        String tokenText = ((STIdentifierToken) nextToken).text;
        return tokenText.equals(SyntaxKind.COLLECT_KEYWORD.stringValue())
                || tokenText.equals(SyntaxKind.GROUP_KEYWORD.stringValue());
    }

    /**
     * Parse an intermediate clause.
     * <p>
     * <code>
     * intermediate-clause := from-clause | where-clause | let-clause | join-clause | limit-clause | order-by-clause
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseIntermediateClause(boolean isRhsExpr, boolean allowActions) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case FROM_KEYWORD:
                return parseFromClause(isRhsExpr, allowActions);
            case WHERE_KEYWORD:
                return parseWhereClause(isRhsExpr);
            case LET_KEYWORD:
                return parseLetClause(isRhsExpr, allowActions);
            case SELECT_KEYWORD:
                return parseSelectClause(isRhsExpr, allowActions);
            case JOIN_KEYWORD:
            case OUTER_KEYWORD:
                return parseJoinClause(isRhsExpr);
            case ORDER_KEYWORD:
            case ASCENDING_KEYWORD:
            case DESCENDING_KEYWORD:
                return parseOrderByClause(isRhsExpr);
            case LIMIT_KEYWORD:
                return parseLimitClause(isRhsExpr);
            case DO_KEYWORD:
            case SEMICOLON_TOKEN:
            case ON_KEYWORD:
            case CONFLICT_KEYWORD:
                return null;
            default:
                if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN && nextToken instanceof STIdentifierToken) {
                    if (((STIdentifierToken) nextToken).text.equals(SyntaxKind.COLLECT_KEYWORD.stringValue())) {
                        return parseCollectClause(isRhsExpr);
                    }
                    if (((STIdentifierToken) nextToken).text.equals(SyntaxKind.GROUP_KEYWORD.stringValue())) {
                        return parseGroupByClause(isRhsExpr);
                    }
                }
                recover(peek(), ParserRuleContext.QUERY_PIPELINE_RHS);
                return parseIntermediateClause(isRhsExpr, allowActions);
        }
    }

    private STNode parseCollectClause(boolean isRhsExpr) {
        startContext(ParserRuleContext.COLLECT_CLAUSE);
        STToken nextToken = consume();
        STNode collectKeyword = STNodeFactory.createToken(SyntaxKind.COLLECT_KEYWORD,
                            nextToken.leadingMinutiae(), nextToken.trailingMinutiae());
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        endContext();
        return STNodeFactory.createCollectClauseNode(collectKeyword, expression);
    }

    /**
     * Parse join-keyword.
     *
     * @return Join-keyword node
     */
    private STNode parseJoinKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.JOIN_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.JOIN_KEYWORD);
            return parseJoinKeyword();
        }
    }

    /**
     * Parse equals keyword.
     *
     * @return Parsed node
     */
    private STNode parseEqualsKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.EQUALS_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.EQUALS_KEYWORD);
            return parseEqualsKeyword();
        }
    }

    private boolean isEndOfIntermediateClause(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case CLOSE_BRACE_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case CLOSE_BRACKET_TOKEN:
            case OPEN_BRACE_TOKEN:
            case SEMICOLON_TOKEN:
            case PUBLIC_KEYWORD:
            case FUNCTION_KEYWORD:
            case EOF_TOKEN:
            case RESOURCE_KEYWORD:
            case LISTENER_KEYWORD:
            case DOCUMENTATION_STRING:
            case PRIVATE_KEYWORD:
            case RETURNS_KEYWORD:
            case SERVICE_KEYWORD:
            case TYPE_KEYWORD:
            case CONST_KEYWORD:
            case FINAL_KEYWORD:
            case DO_KEYWORD:
            case ON_KEYWORD:
            case CONFLICT_KEYWORD:
                return true;
            default:
                return isValidExprRhsStart(tokenKind, SyntaxKind.NONE);
        }
    }

    /**
     * Parse from clause.
     * <p>
     * <code>from-clause := from typed-binding-pattern in expression</code>
     *
     * @return Parsed node
     */
    private STNode parseFromClause(boolean isRhsExpr, boolean allowActions) {
        STNode fromKeyword = parseFromKeyword();
        STNode typedBindingPattern = parseTypedBindingPattern(ParserRuleContext.FROM_CLAUSE);
        STNode inKeyword = parseInKeyword();
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, allowActions);
        return STNodeFactory.createFromClauseNode(fromKeyword, typedBindingPattern, inKeyword, expression);
    }

    /**
     * Parse from-keyword.
     *
     * @return From-keyword node
     */
    private STNode parseFromKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FROM_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FROM_KEYWORD);
            return parseFromKeyword();
        }
    }

    /**
     * Parse where clause.
     * <p>
     * <code>where-clause := where expression</code>
     *
     * @return Parsed node
     */
    private STNode parseWhereClause(boolean isRhsExpr) {
        STNode whereKeyword = parseWhereKeyword();

        
        
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        return STNodeFactory.createWhereClauseNode(whereKeyword, expression);
    }

    /**
     * Parse where-keyword.
     *
     * @return Where-keyword node
     */
    private STNode parseWhereKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.WHERE_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.WHERE_KEYWORD);
            return parseWhereKeyword();
        }
    }

    /**
     * Parse limit-keyword.
     *
     * @return limit-keyword node
     */
    private STNode parseLimitKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.LIMIT_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.LIMIT_KEYWORD);
            return parseLimitKeyword();
        }
    }

    /**
     * Parse let clause.
     * <p>
     * <code>let-clause := let let-var-decl [, let-var-decl]* </code>
     *
     * @return Parsed node
     */
    private STNode parseLetClause(boolean isRhsExpr, boolean allowActions) {
        STNode letKeyword = parseLetKeyword();
        STNode letVarDeclarations = parseLetVarDeclarations(ParserRuleContext.LET_CLAUSE_LET_VAR_DECL, isRhsExpr,
                allowActions);

        
        letKeyword = cloneWithDiagnosticIfListEmpty(letVarDeclarations, letKeyword,
                DiagnosticErrorCode.ERROR_MISSING_LET_VARIABLE_DECLARATION);

        return STNodeFactory.createLetClauseNode(letKeyword, letVarDeclarations);
    }

    /**
     * Parse group by clause.
     * <code>group-by-clause := group by grouping-key-list</code>
     *
     * @return Parsed node
     */
    private STNode parseGroupByClause(boolean isRhsExpr) {
        startContext(ParserRuleContext.GROUP_BY_CLAUSE);
        STToken nextToken = consume();
        STNode groupKeyword = STNodeFactory.createToken(SyntaxKind.GROUP_KEYWORD,
                nextToken.leadingMinutiae(), nextToken.trailingMinutiae());
        STNode byKeyword = parseByKeyword();
        STNode groupingKeys = parseGroupingKeyList(isRhsExpr);
        byKeyword = cloneWithDiagnosticIfListEmpty(groupingKeys, byKeyword,
                    DiagnosticErrorCode.ERROR_MISSING_GROUPING_KEY);
        endContext();
        return STNodeFactory.createGroupByClauseNode(groupKeyword, byKeyword, groupingKeys);
    }

    /**
     * Parse order-keyword.
     *
     * @return Order-keyword node
     */
    private STNode parseOrderKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ORDER_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ORDER_KEYWORD);
            return parseOrderKeyword();
        }
    }

    /**
     * Parse by-keyword.
     *
     * @return By-keyword node
     */
    private STNode parseByKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.BY_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.BY_KEYWORD);
            return parseByKeyword();
        }
    }

    /**
     * Parse order by clause.
     * <p>
     * <code>order-by-clause := order by order-key-list
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseOrderByClause(boolean isRhsExpr) {
        STNode orderKeyword = parseOrderKeyword();
        STNode byKeyword = parseByKeyword();
        STNode orderKeys = parseOrderKeyList(isRhsExpr);
        byKeyword = cloneWithDiagnosticIfListEmpty(orderKeys, byKeyword, DiagnosticErrorCode.ERROR_MISSING_ORDER_KEY);
        return STNodeFactory.createOrderByClauseNode(orderKeyword, byKeyword, orderKeys);
    }

    /**
     * Parse grouping key.
     * <code>grouping-key-list := grouping-key ["," grouping-key]*</code>
     *
     * @return Parsed node
     */
    private STNode parseGroupingKeyList(boolean isRhsExpr) {
        List<STNode> groupingKeys = new ArrayList<>();
        STToken nextToken = peek();

        if (isEndOfGroupByKeyListElement(nextToken)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode groupingKey = parseGroupingKey(isRhsExpr);
        groupingKeys.add(groupingKey);

        
        nextToken = peek();
        STNode groupingKeyListMemberEnd;
        while (!isEndOfGroupByKeyListElement(nextToken)) {
            groupingKeyListMemberEnd = parseGroupingKeyListMemberEnd();
            if (groupingKeyListMemberEnd == null) {
                break;
            }

            groupingKeys.add(groupingKeyListMemberEnd);
            groupingKey = parseGroupingKey(isRhsExpr);
            groupingKeys.add(groupingKey);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(groupingKeys);
    }

    /**
     * Parse order key.
     * <p>
     * <code>order-key-list := order-key [, order-key]*</code>
     *
     * @return Parsed node
     */
    private STNode parseOrderKeyList(boolean isRhsExpr) {
        startContext(ParserRuleContext.ORDER_KEY_LIST);
        List<STNode> orderKeys = new ArrayList<>();
        STToken nextToken = peek();

        if (isEndOfOrderKeys(nextToken)) {
            endContext();
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode orderKey = parseOrderKey(isRhsExpr);
        orderKeys.add(orderKey);

        
        nextToken = peek();
        STNode orderKeyListMemberEnd;
        while (!isEndOfOrderKeys(nextToken)) {
            orderKeyListMemberEnd = parseOrderKeyListMemberEnd();
            if (orderKeyListMemberEnd == null) {
                break;
            }
            orderKeys.add(orderKeyListMemberEnd);
            orderKey = parseOrderKey(isRhsExpr);
            orderKeys.add(orderKey);
            nextToken = peek();
        }

        endContext();
        return STNodeFactory.createNodeList(orderKeys);
    }

    private boolean isEndOfGroupByKeyListElement(STToken nextToken) {
        SyntaxKind tokenKind = nextToken.kind;
        switch (tokenKind) {
            case COMMA_TOKEN:
                return false;
            case EOF_TOKEN:
                return true;
            default:
                return isQueryClauseStartToken(nextToken);
        }
    }

    private boolean isEndOfOrderKeys(STToken nextToken) {
        SyntaxKind tokenKind = nextToken.kind;
        switch (tokenKind) {
            case COMMA_TOKEN:
            case ASCENDING_KEYWORD:
            case DESCENDING_KEYWORD:
                return false;
            case SEMICOLON_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return isQueryClauseStartToken(nextToken);
        }
    }

    private boolean isQueryClauseStartToken(STToken nextToken) {
        SyntaxKind tokenKind = nextToken.kind;
        switch (tokenKind) {
            case SELECT_KEYWORD:
            case LET_KEYWORD:
            case WHERE_KEYWORD:
            case OUTER_KEYWORD:
            case JOIN_KEYWORD:
            case ORDER_KEYWORD:
            case DO_KEYWORD:
            case FROM_KEYWORD:
            case LIMIT_KEYWORD:
                return true;
            case IDENTIFIER_TOKEN:
                return isGroupOrCollectKeyword(nextToken);
            default:
                return false;
        }
    }

    private STNode parseGroupingKeyListMemberEnd() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return consume();
            case EOF_TOKEN:
                return null;
            default:
                if (isQueryClauseStartToken(nextToken)) {
                    
                    return null;
                }
                recover(peek(), ParserRuleContext.GROUPING_KEY_LIST_ELEMENT_END);
                return parseGroupingKeyListMemberEnd();
        }
    }

    private STNode parseOrderKeyListMemberEnd() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return parseComma();
            case EOF_TOKEN:
                return null;
            default:
                if (isQueryClauseStartToken(nextToken)) {
                    
                    return null;
                }

                recover(peek(), ParserRuleContext.ORDER_KEY_LIST_END);
                return parseOrderKeyListMemberEnd();
        }
    }

    private STNode parseGroupingKeyVariableDeclaration(boolean isRhsExpr) {
        STNode groupingKeyElementTypeDesc =
                parseTypeDescriptor(ParserRuleContext.TYPE_DESC_BEFORE_IDENTIFIER_IN_GROUPING_KEY);
        startContext(ParserRuleContext.BINDING_PATTERN_STARTING_IDENTIFIER);
        STNode groupingKeySimpleBP = createCaptureOrWildcardBP(parseVariableName());
        endContext();
        STNode equalsToken = parseAssignOp();
        STNode groupingKeyExpression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        return STNodeFactory.createGroupingKeyVarDeclarationNode(groupingKeyElementTypeDesc, groupingKeySimpleBP,
                equalsToken, groupingKeyExpression);
    }

    /**
     * Parse grouping key.
     * <code>grouping-key := variable-name | inferable-type-descriptor variable-name "=" expression</code>
     *
     * @return Parsed node
     */
    private STNode parseGroupingKey(boolean isRhsExpr) {
        STToken nextToken = peek();
        SyntaxKind nextTokenKind = nextToken.kind;
        if (nextTokenKind == SyntaxKind.IDENTIFIER_TOKEN && !isPossibleGroupingKeyVarDeclaration()) {
            return STNodeFactory.createSimpleNameReferenceNode(parseVariableName());
        } else if (isTypeStartingToken(nextTokenKind, nextToken)) {
            return parseGroupingKeyVariableDeclaration(isRhsExpr);
        }

        recover(nextToken, ParserRuleContext.GROUPING_KEY_LIST_ELEMENT);
        return parseGroupingKey(isRhsExpr);
    }

    private boolean isPossibleGroupingKeyVarDeclaration() {
        SyntaxKind nextNextTokenKind = getNextNextToken().kind;
        return nextNextTokenKind == SyntaxKind.EQUAL_TOKEN ||
                nextNextTokenKind == SyntaxKind.IDENTIFIER_TOKEN && peek(3).kind == SyntaxKind.EQUAL_TOKEN;
    }

    /**
     * Parse order key.
     * <p>
     * <code>order-key := expression (ascending | descending)?</code>
     *
     * @return Parsed node
     */
    private STNode parseOrderKey(boolean isRhsExpr) {
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);

        STNode orderDirection;
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case ASCENDING_KEYWORD:
            case DESCENDING_KEYWORD:
                orderDirection = consume();
                break;
            default:
                orderDirection = STNodeFactory.createEmptyNode();
        }

        return STNodeFactory.createOrderKeyNode(expression, orderDirection);
    }

    /**
     * Parse select clause.
     * <p>
     * <code>select-clause := select expression</code>
     *
     * @return Parsed node
     */
    private STNode parseSelectClause(boolean isRhsExpr, boolean allowActions) {
        startContext(ParserRuleContext.SELECT_CLAUSE);
        STNode selectKeyword = parseSelectKeyword();
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, allowActions);
        endContext();
        return STNodeFactory.createSelectClauseNode(selectKeyword, expression);
    }

    /**
     * Parse select-keyword.
     *
     * @return Select-keyword node
     */
    private STNode parseSelectKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.SELECT_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.SELECT_KEYWORD);
            return parseSelectKeyword();
        }
    }

    /**
     * Parse on-conflict clause.
     * <p>
     * <code>
     * onConflictClause := on conflict expression
     * </code>
     *
     * @return On conflict clause node
     */
    private STNode parseOnConflictClause(boolean isRhsExpr) {
        STToken nextToken = peek();
        if (nextToken.kind != SyntaxKind.ON_KEYWORD && nextToken.kind != SyntaxKind.CONFLICT_KEYWORD) {
            return STNodeFactory.createEmptyNode();
        }

        startContext(ParserRuleContext.ON_CONFLICT_CLAUSE);
        STNode onKeyword = parseOnKeyword();
        STNode conflictKeyword = parseConflictKeyword();
        endContext();
        STNode expr = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        return STNodeFactory.createOnConflictClauseNode(onKeyword, conflictKeyword, expr);
    }

    /**
     * Parse conflict keyword.
     *
     * @return Conflict keyword node
     */
    private STNode parseConflictKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.CONFLICT_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.CONFLICT_KEYWORD);
            return parseConflictKeyword();
        }
    }

    /**
     * Parse limit clause.
     * <p>
     * <code>limitClause := limit expression</code>
     *
     * @return Limit expression node
     */
    private STNode parseLimitClause(boolean isRhsExpr) {
        STNode limitKeyword = parseLimitKeyword();
        
        
        STNode expr = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        return STNodeFactory.createLimitClauseNode(limitKeyword, expr);
    }

    /**
     * Parse join clause.
     * <p>
     * <code>
     * join-clause := (join-var-decl | outer-join-var-decl) in expression on-clause
     * <br/>
     * join-var-decl := join (typeName | var) bindingPattern
     * <br/>
     * outer-join-var-decl := outer join var binding-pattern
     * </code>
     *
     * @return Join clause
     */
    private STNode parseJoinClause(boolean isRhsExpr) {
        startContext(ParserRuleContext.JOIN_CLAUSE);
        STNode outerKeyword;
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.OUTER_KEYWORD) {
            outerKeyword = consume();
        } else {
            outerKeyword = STNodeFactory.createEmptyNode();
        }

        STNode joinKeyword = parseJoinKeyword();
        STNode typedBindingPattern = parseTypedBindingPattern(ParserRuleContext.JOIN_CLAUSE);
        STNode inKeyword = parseInKeyword();
        
        
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        endContext();
        STNode onCondition = parseOnClause(isRhsExpr);
        return STNodeFactory.createJoinClauseNode(outerKeyword, joinKeyword, typedBindingPattern, inKeyword, expression,
                onCondition);
    }

    /**
     * Parse on clause.
     * <p>
     * <code>on clause := `on` expression `equals` expression</code>
     *
     * @return On clause node
     */
    private STNode parseOnClause(boolean isRhsExpr) {
        STToken nextToken = peek();
        if (isQueryClauseStartToken(nextToken)) {
            return createMissingOnClauseNode();
        }

        startContext(ParserRuleContext.ON_CLAUSE);
        STNode onKeyword = parseOnKeyword();
        STNode lhsExpression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        STNode equalsKeyword = parseEqualsKeyword();
        endContext();
        STNode rhsExpression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        return STNodeFactory.createOnClauseNode(onKeyword, lhsExpression, equalsKeyword, rhsExpression);
    }

    private STNode createMissingOnClauseNode() {
        STNode onKeyword = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.ON_KEYWORD,
                DiagnosticErrorCode.ERROR_MISSING_ON_KEYWORD);
        STNode identifier = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                DiagnosticErrorCode.ERROR_MISSING_IDENTIFIER);
        STNode equalsKeyword = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.EQUALS_KEYWORD,
                DiagnosticErrorCode.ERROR_MISSING_EQUALS_KEYWORD);

        STNode lhsExpression = STNodeFactory.createSimpleNameReferenceNode(identifier);
        STNode rhsExpression = STNodeFactory.createSimpleNameReferenceNode(identifier);
        return STNodeFactory.createOnClauseNode(onKeyword, lhsExpression, equalsKeyword, rhsExpression);
    }

    /**
     * Parse start action.
     * <p>
     * <code>start-action := [annots] start (function-call-expr|method-call-expr|remote-method-call-action)</code>
     *
     * @return Start action node
     */
    private STNode parseStartAction(STNode annots) {
        STNode startKeyword = parseStartKeyword();
        STNode expr = parseActionOrExpression();

        
        switch (expr.kind) {
            case FUNCTION_CALL:
            case METHOD_CALL:
            case REMOTE_METHOD_CALL_ACTION:
                break;
            case SIMPLE_NAME_REFERENCE:
            case QUALIFIED_NAME_REFERENCE:
            case FIELD_ACCESS:
            case ASYNC_SEND_ACTION:
                expr = generateValidExprForStartAction(expr);
                break;
            default:
                startKeyword = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(startKeyword, expr,
                        DiagnosticErrorCode.ERROR_INVALID_EXPRESSION_IN_START_ACTION);
                STNode funcName = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
                funcName = STNodeFactory.createSimpleNameReferenceNode(funcName);
                STNode openParenToken = SyntaxErrors.createMissingToken(SyntaxKind.OPEN_PAREN_TOKEN);
                STNode closeParenToken = SyntaxErrors.createMissingToken(SyntaxKind.CLOSE_PAREN_TOKEN);
                expr = STNodeFactory.createFunctionCallExpressionNode(funcName, openParenToken,
                        STNodeFactory.createEmptyNodeList(), closeParenToken);
                break;
        }

        return STNodeFactory.createStartActionNode(getAnnotations(annots), startKeyword, expr);
    }

    private STNode generateValidExprForStartAction(STNode expr) {
        STNode openParenToken = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.OPEN_PAREN_TOKEN,
                DiagnosticErrorCode.ERROR_MISSING_OPEN_PAREN_TOKEN);
        STNode arguments = STNodeFactory.createEmptyNodeList();
        STNode closeParenToken = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.CLOSE_PAREN_TOKEN,
                DiagnosticErrorCode.ERROR_MISSING_CLOSE_PAREN_TOKEN);

        switch (expr.kind) {
            case FIELD_ACCESS:
                STFieldAccessExpressionNode fieldAccessExpr = (STFieldAccessExpressionNode) expr;
                return STNodeFactory.createMethodCallExpressionNode(fieldAccessExpr.expression,
                        fieldAccessExpr.dotToken, fieldAccessExpr.fieldName, openParenToken, arguments,
                        closeParenToken);
            case ASYNC_SEND_ACTION:
                STAsyncSendActionNode asyncSendAction = (STAsyncSendActionNode) expr;
                return STNodeFactory.createRemoteMethodCallActionNode(asyncSendAction.expression,
                        asyncSendAction.rightArrowToken, asyncSendAction.peerWorker, openParenToken, arguments,
                        closeParenToken);
            default: 
                return STNodeFactory.createFunctionCallExpressionNode(expr, openParenToken, arguments, closeParenToken);
        }
    }

    /**
     * Parse start keyword.
     *
     * @return Start keyword node
     */
    private STNode parseStartKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.START_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.START_KEYWORD);
            return parseStartKeyword();
        }
    }

    /**
     * Parse flush action.
     * <p>
     * <code>flush-action := flush [peer-worker]</code>
     *
     * @return flush action node
     */
    private STNode parseFlushAction() {
        STNode flushKeyword = parseFlushKeyword();
        STNode peerWorker = parseOptionalPeerWorkerName();
        return STNodeFactory.createFlushActionNode(flushKeyword, peerWorker);
    }

    /**
     * Parse flush keyword.
     *
     * @return flush keyword node
     */
    private STNode parseFlushKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FLUSH_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FLUSH_KEYWORD);
            return parseFlushKeyword();
        }
    }

    /**
     * Parse peer worker.
     * <p>
     * <code>peer-worker := worker-name | function</code>
     *
     * @return peer worker name node
     */
    private STNode parseOptionalPeerWorkerName() {
        STToken token = peek();
        switch (token.kind) {
            case IDENTIFIER_TOKEN:
            case FUNCTION_KEYWORD:
                return STNodeFactory.createSimpleNameReferenceNode(consume());
            default:
                return STNodeFactory.createEmptyNode();
        }
    }

    /**
     * Parse intersection type descriptor.
     * <p>
     * intersection-type-descriptor := type-descriptor & type-descriptor
     * </p>
     *
     * @return Parsed node
     */
    private STNode parseIntersectionTypeDescriptor(STNode leftTypeDesc, ParserRuleContext context,
                                                   boolean isTypedBindingPattern) {
        
        STNode bitwiseAndToken = consume();
        STNode rightTypeDesc = parseTypeDescriptorInternal(new ArrayList<>(), context, isTypedBindingPattern, false,
                TypePrecedence.INTERSECTION);
        return mergeTypesWithIntersection(leftTypeDesc, bitwiseAndToken, rightTypeDesc);
    }

    /**
     * Creates an intersection type descriptor after validating lhs and rhs types.
     * <p>
     * <i>Note: Since type precedence and associativity are not taken into account here,
     * this method should not be called directly when types are unknown.
     * <br/>
     * Call {@link 
     *
     * @param leftTypeDesc    lhs type
     * @param bitwiseAndToken bitwise-and token
     * @param rightTypeDesc   rhs type
     * @return an IntersectionTypeDescriptorNode
     */
    private STNode createIntersectionTypeDesc(STNode leftTypeDesc, STNode bitwiseAndToken, STNode rightTypeDesc) {
        leftTypeDesc = validateForUsageOfVar(leftTypeDesc);
        rightTypeDesc = validateForUsageOfVar(rightTypeDesc);
        return STNodeFactory.createIntersectionTypeDescriptorNode(leftTypeDesc, bitwiseAndToken, rightTypeDesc);
    }

    /**
     * Parse singleton type descriptor.
     * <p>
     * singleton-type-descriptor := simple-const-expr
     * simple-const-expr :=
     * nil-literal
     * | boolean-literal
     * | [Sign] int-literal
     * | [Sign] floating-point-literal
     * | string-literal
     * | constant-reference-expr
     * </p>
     */
    private STNode parseSingletonTypeDesc() {
        STNode simpleContExpr = parseSimpleConstExpr();
        return STNodeFactory.createSingletonTypeDescriptorNode(simpleContExpr);
    }

    
    private STNode parseSignedIntOrFloat() {
        STNode operator = parseUnaryOperator();
        STNode literal;
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
                literal = parseBasicLiteral();
                break;
            default: 
                literal = parseDecimalIntLiteral(ParserRuleContext.DECIMAL_INTEGER_LITERAL_TOKEN);
                literal = STNodeFactory.createBasicLiteralNode(SyntaxKind.NUMERIC_LITERAL, literal);
        }
        return STNodeFactory.createUnaryExpressionNode(operator, literal);
    }

    private static boolean isSingletonTypeDescStart(SyntaxKind tokenKind, STToken nextNextToken) {
        switch (tokenKind) {
            case STRING_LITERAL_TOKEN:
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case NULL_KEYWORD:
                return true;
            case PLUS_TOKEN:
            case MINUS_TOKEN:
                return isIntOrFloat(nextNextToken);
            default:
                return false;
        }
    }

    static boolean isIntOrFloat(STToken token) {
        switch (token.kind) {
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Check whether the parser reached to a valid expression start.
     *
     * @param nextTokenKind  Kind of the next immediate token.
     * @param nextTokenIndex Index to the next token.
     * @return <code>true</code> if this is a start of a valid expression. <code>false</code> otherwise
     */
    private boolean isValidExpressionStart(SyntaxKind nextTokenKind, int nextTokenIndex) {
        nextTokenIndex++;
        switch (nextTokenKind) {
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case STRING_LITERAL_TOKEN:
            case NULL_KEYWORD:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
                SyntaxKind nextNextTokenKind = peek(nextTokenIndex).kind;
                if (nextNextTokenKind == SyntaxKind.PIPE_TOKEN || nextNextTokenKind == SyntaxKind.BITWISE_AND_TOKEN) {
                    
                    nextTokenIndex++;
                    return isValidExpressionStart(peek(nextTokenIndex).kind, nextTokenIndex);
                }

                return nextNextTokenKind == SyntaxKind.SEMICOLON_TOKEN ||
                        nextNextTokenKind == SyntaxKind.COMMA_TOKEN ||
                        nextNextTokenKind == SyntaxKind.CLOSE_BRACKET_TOKEN ||
                        isValidExprRhsStart(nextNextTokenKind, SyntaxKind.SIMPLE_NAME_REFERENCE);
            case IDENTIFIER_TOKEN:
                return isValidExprRhsStart(peek(nextTokenIndex).kind, SyntaxKind.SIMPLE_NAME_REFERENCE);
            case OPEN_PAREN_TOKEN:
            case CHECK_KEYWORD:
            case CHECKPANIC_KEYWORD:
            case OPEN_BRACE_TOKEN:
            case TYPEOF_KEYWORD:
            case NEGATION_TOKEN:
            case EXCLAMATION_MARK_TOKEN:
            case TRAP_KEYWORD:
            case OPEN_BRACKET_TOKEN:
            case LT_TOKEN:
            case FROM_KEYWORD:
            case LET_KEYWORD:
            case BACKTICK_TOKEN:
            case NEW_KEYWORD:
            case LEFT_ARROW_TOKEN:
            case FUNCTION_KEYWORD:
            case TRANSACTIONAL_KEYWORD:
            case ISOLATED_KEYWORD:
            case BASE16_KEYWORD:
            case BASE64_KEYWORD:
                return true;
            case PLUS_TOKEN:
            case MINUS_TOKEN:
                return isValidExpressionStart(peek(nextTokenIndex).kind, nextTokenIndex);
            case TABLE_KEYWORD:
            case MAP_KEYWORD:
                return peek(nextTokenIndex).kind == SyntaxKind.FROM_KEYWORD;
            case STREAM_KEYWORD:
                STToken nextNextToken = peek(nextTokenIndex);
                return nextNextToken.kind == SyntaxKind.KEY_KEYWORD ||
                        nextNextToken.kind == SyntaxKind.OPEN_BRACKET_TOKEN ||
                        nextNextToken.kind == SyntaxKind.FROM_KEYWORD;
            case ERROR_KEYWORD:
                return peek(nextTokenIndex).kind == SyntaxKind.OPEN_PAREN_TOKEN;
            case XML_KEYWORD:
            case STRING_KEYWORD:
            case RE_KEYWORD:
                return peek(nextTokenIndex).kind == SyntaxKind.BACKTICK_TOKEN;

            
            case START_KEYWORD:
            case FLUSH_KEYWORD:
            case WAIT_KEYWORD:
            default:
                return false;
        }
    }

    /**
     * Parse sync send action.
     * <p>
     * <code>sync-send-action := expression ->> peer-worker</code>
     *
     * @param expression LHS expression of the sync send action
     * @return Sync send action node
     */
    private STNode parseSyncSendAction(STNode expression) {
        STNode syncSendToken = parseSyncSendToken();
        STNode peerWorker = parsePeerWorkerName();
        return STNodeFactory.createSyncSendActionNode(expression, syncSendToken, peerWorker);
    }

    /**
     * Parse peer worker.
     * <p>
     * <code>peer-worker := worker-name | function</code>
     *
     * @return peer worker name node
     */
    private STNode parsePeerWorkerName() {
        STToken token = peek();
        switch (token.kind) {
            case IDENTIFIER_TOKEN:
            case FUNCTION_KEYWORD:
                return STNodeFactory.createSimpleNameReferenceNode(consume());
            default:
                recover(token, ParserRuleContext.PEER_WORKER_NAME);
                return parsePeerWorkerName();
        }
    }

    /**
     * Parse sync send token.
     * <p>
     * <code>sync-send-token :=  ->> </code>
     *
     * @return sync send token
     */
    private STNode parseSyncSendToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.SYNC_SEND_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.SYNC_SEND_TOKEN);
            return parseSyncSendToken();
        }
    }

    /**
     * Parse receive action.
     * <p>
     * <code>receive-action := single-receive-action | multiple-receive-action</code>
     *
     * @return Receive action
     */
    private STNode parseReceiveAction() {
        STNode leftArrow = parseLeftArrowToken();
        STNode receiveWorkers = parseReceiveWorkers();
        return STNodeFactory.createReceiveActionNode(leftArrow, receiveWorkers);
    }

    private STNode parseReceiveWorkers() {
        switch (peek().kind) {
            case FUNCTION_KEYWORD:
            case IDENTIFIER_TOKEN:
                return parsePeerWorkerName();
            case OPEN_BRACE_TOKEN:
                return parseMultipleReceiveWorkers();
            default:
                recover(peek(), ParserRuleContext.RECEIVE_WORKERS);
                return parseReceiveWorkers();
        }
    }

    /**
     * Parse multiple worker receivers.
     * <p>
     * <code>{ receive-field (, receive-field)* }</code>
     *
     * @return Multiple worker receiver node
     */
    private STNode parseMultipleReceiveWorkers() {
        startContext(ParserRuleContext.MULTI_RECEIVE_WORKERS);
        STNode openBrace = parseOpenBrace();
        STNode receiveFields = parseReceiveFields();
        STNode closeBrace = parseCloseBrace();
        endContext();

        openBrace = cloneWithDiagnosticIfListEmpty(receiveFields, openBrace,
                DiagnosticErrorCode.ERROR_MISSING_RECEIVE_FIELD_IN_RECEIVE_ACTION);
        return STNodeFactory.createReceiveFieldsNode(openBrace, receiveFields, closeBrace);
    }

    private STNode parseReceiveFields() {
        List<STNode> receiveFields = new ArrayList<>();
        STToken nextToken = peek();

        
        if (isEndOfReceiveFields(nextToken.kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode receiveField = parseReceiveField();
        receiveFields.add(receiveField);

        
        nextToken = peek();
        STNode recieveFieldEnd;
        while (!isEndOfReceiveFields(nextToken.kind)) {
            recieveFieldEnd = parseReceiveFieldEnd();
            if (recieveFieldEnd == null) {
                break;
            }

            receiveFields.add(recieveFieldEnd);
            receiveField = parseReceiveField();
            receiveFields.add(receiveField);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(receiveFields);
    }

    private boolean isEndOfReceiveFields(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACE_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseReceiveFieldEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACE_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.RECEIVE_FIELD_END);
                return parseReceiveFieldEnd();
        }
    }

    /**
     * Parse receive field.
     * <p>
     * <code>receive-field := peer-worker | field-name : peer-worker</code>
     *
     * @return Receiver field node
     */
    private STNode parseReceiveField() {
        switch (peek().kind) {
            case FUNCTION_KEYWORD:
                STNode functionKeyword = consume();
                return STNodeFactory.createSimpleNameReferenceNode(functionKeyword);
            case IDENTIFIER_TOKEN:
                STNode identifier = parseIdentifier(ParserRuleContext.RECEIVE_FIELD_NAME);
                return createQualifiedReceiveField(identifier);
            default:
                recover(peek(), ParserRuleContext.RECEIVE_FIELD);
                return parseReceiveField();
        }
    }

    private STNode createQualifiedReceiveField(STNode identifier) {
        if (peek().kind != SyntaxKind.COLON_TOKEN) {
            return identifier;
        }

        STNode colon = parseColon();
        STNode peerWorker = parsePeerWorkerName();
        return createQualifiedNameReferenceNode(identifier, colon, peerWorker);
    }

    /**
     * Parse left arrow (<-) token.
     *
     * @return left arrow token
     */
    private STNode parseLeftArrowToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.LEFT_ARROW_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.LEFT_ARROW_TOKEN);
            return parseLeftArrowToken();
        }
    }

    /**
     * Parse signed right shift token (>>).
     * This method should only be called by seeing a `DOUBLE_GT_TOKEN` or
     * by seeing a `GT_TOKEN` followed by a `GT_TOKEN`
     *
     * @return Parsed node
     */
    private STNode parseSignedRightShiftToken() {
        STNode firstToken = consume();
        if (firstToken.kind == SyntaxKind.DOUBLE_GT_TOKEN) {
            return firstToken;
        }

        STToken endLGToken = consume();
        STNode doubleGTToken = STNodeFactory.createToken(SyntaxKind.DOUBLE_GT_TOKEN, firstToken.leadingMinutiae(),
                endLGToken.trailingMinutiae());

        if (hasTrailingMinutiae(firstToken)) {
            doubleGTToken = SyntaxErrors.addDiagnostic(doubleGTToken,
                    DiagnosticErrorCode.ERROR_NO_WHITESPACES_ALLOWED_IN_RIGHT_SHIFT_OP);
        }
        return doubleGTToken;
    }

    /**
     * Parse unsigned right shift token (>>>).
     * This method should only be called by seeing a `TRIPPLE_GT_TOKEN` or
     * by seeing a `GT_TOKEN` followed by two `GT_TOKEN`s
     *
     * @return Parsed node
     */
    private STNode parseUnsignedRightShiftToken() {
        STNode firstToken = consume();
        if (firstToken.kind == SyntaxKind.TRIPPLE_GT_TOKEN) {
            return firstToken;
        }

        STNode middleGTToken = consume();
        STNode endLGToken = consume();
        STNode unsignedRightShiftToken = STNodeFactory.createToken(SyntaxKind.TRIPPLE_GT_TOKEN,
                firstToken.leadingMinutiae(), endLGToken.trailingMinutiae());

        boolean validOpenGTToken = !hasTrailingMinutiae(firstToken);
        boolean validMiddleGTToken = !hasTrailingMinutiae(middleGTToken);
        if (validOpenGTToken && validMiddleGTToken) {
            return unsignedRightShiftToken;
        }

        unsignedRightShiftToken = SyntaxErrors.addDiagnostic(unsignedRightShiftToken,
                DiagnosticErrorCode.ERROR_NO_WHITESPACES_ALLOWED_IN_UNSIGNED_RIGHT_SHIFT_OP);
        return unsignedRightShiftToken;
    }

    /**
     * Parse wait action.
     * <p>
     * <code>wait-action := single-wait-action | multiple-wait-action | alternate-wait-action </code>
     *
     * @return Wait action node
     */
    private STNode parseWaitAction() {
        STNode waitKeyword = parseWaitKeyword();
        if (peek().kind == SyntaxKind.OPEN_BRACE_TOKEN) {
            return parseMultiWaitAction(waitKeyword);
        }

        return parseSingleOrAlternateWaitAction(waitKeyword);
    }

    /**
     * Parse wait keyword.
     *
     * @return wait keyword
     */
    private STNode parseWaitKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.WAIT_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.WAIT_KEYWORD);
            return parseWaitKeyword();
        }
    }

    /**
     * Parse single or alternate wait actions.
     * <p>
     * <code>
     * alternate-or-single-wait-action := wait wait-future-expr (| wait-future-expr)+
     * <br/>
     * wait-future-expr := expression but not mapping-constructor-expr
     * </code>
     *
     * @param waitKeyword wait keyword
     * @return Single or alternate wait action node
     */
    private STNode parseSingleOrAlternateWaitAction(STNode waitKeyword) {
        startContext(ParserRuleContext.ALTERNATE_WAIT_EXPRS);
        STToken nextToken = peek();

        
        if (isEndOfWaitFutureExprList(nextToken.kind)) {
            endContext();
            STNode waitFutureExprs = STNodeFactory
                    .createSimpleNameReferenceNode(STNodeFactory.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN));
            waitFutureExprs = SyntaxErrors.addDiagnostic(waitFutureExprs,
                    DiagnosticErrorCode.ERROR_MISSING_WAIT_FUTURE_EXPRESSION);
            return STNodeFactory.createWaitActionNode(waitKeyword, waitFutureExprs);
        }

        
        List<STNode> waitFutureExprList = new ArrayList<>();
        STNode waitField = parseWaitFutureExpr();
        waitFutureExprList.add(waitField);

        
        nextToken = peek();
        STNode waitFutureExprEnd;
        while (!isEndOfWaitFutureExprList(nextToken.kind)) {
            waitFutureExprEnd = parseWaitFutureExprEnd();
            if (waitFutureExprEnd == null) {
                break;
            }

            waitFutureExprList.add(waitFutureExprEnd);
            waitField = parseWaitFutureExpr();
            waitFutureExprList.add(waitField);
            nextToken = peek();
        }

        
        
        endContext();
        return STNodeFactory.createWaitActionNode(waitKeyword, waitFutureExprList.get(0));
    }

    private boolean isEndOfWaitFutureExprList(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case SEMICOLON_TOKEN:
            case OPEN_BRACE_TOKEN:
                return true;
            case PIPE_TOKEN:
            default:
                return false;
        }
    }

    private STNode parseWaitFutureExpr() {
        STNode waitFutureExpr = parseActionOrExpression();
        if (waitFutureExpr.kind == SyntaxKind.MAPPING_CONSTRUCTOR) {
            waitFutureExpr = SyntaxErrors.addDiagnostic(waitFutureExpr,
                    DiagnosticErrorCode.ERROR_MAPPING_CONSTRUCTOR_EXPR_AS_A_WAIT_EXPR);
        } else if (isAction(waitFutureExpr)) {
            waitFutureExpr =
                    SyntaxErrors.addDiagnostic(waitFutureExpr, DiagnosticErrorCode.ERROR_ACTION_AS_A_WAIT_EXPR);
        }
        return waitFutureExpr;
    }

    private STNode parseWaitFutureExprEnd() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case PIPE_TOKEN:
                return parsePipeToken();
            default:
                if (isEndOfWaitFutureExprList(nextToken.kind) || !isValidExpressionStart(nextToken.kind, 1)) {
                    return null;
                }

                recover(peek(), ParserRuleContext.WAIT_FUTURE_EXPR_END);
                return parseWaitFutureExprEnd();
        }
    }

    /**
     * Parse multiple wait action.
     * <p>
     * <code>multiple-wait-action := wait { wait-field (, wait-field)* }</code>
     *
     * @param waitKeyword Wait keyword
     * @return Multiple wait action node
     */
    private STNode parseMultiWaitAction(STNode waitKeyword) {
        startContext(ParserRuleContext.MULTI_WAIT_FIELDS);
        STNode openBrace = parseOpenBrace();
        STNode waitFields = parseWaitFields();
        STNode closeBrace = parseCloseBrace();
        endContext();

        openBrace = cloneWithDiagnosticIfListEmpty(waitFields, openBrace,
                DiagnosticErrorCode.ERROR_MISSING_WAIT_FIELD_IN_WAIT_ACTION);
        STNode waitFieldsNode = STNodeFactory.createWaitFieldsListNode(openBrace, waitFields, closeBrace);
        return STNodeFactory.createWaitActionNode(waitKeyword, waitFieldsNode);
    }

    private STNode parseWaitFields() {
        List<STNode> waitFields = new ArrayList<>();
        STToken nextToken = peek();

        
        if (isEndOfWaitFields(nextToken.kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode waitField = parseWaitField();
        waitFields.add(waitField);

        
        nextToken = peek();
        STNode waitFieldEnd;
        while (!isEndOfWaitFields(nextToken.kind)) {
            waitFieldEnd = parseWaitFieldEnd();
            if (waitFieldEnd == null) {
                break;
            }

            waitFields.add(waitFieldEnd);
            waitField = parseWaitField();
            waitFields.add(waitField);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(waitFields);
    }

    private boolean isEndOfWaitFields(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACE_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseWaitFieldEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACE_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.WAIT_FIELD_END);
                return parseWaitFieldEnd();
        }
    }

    /**
     * Parse wait field.
     * <p>
     * <code>wait-field := variable-name | field-name : wait-future-expr</code>
     *
     * @return Receiver field node
     */
    private STNode parseWaitField() {
        switch (peek().kind) {
            case IDENTIFIER_TOKEN:
                STNode identifier = parseIdentifier(ParserRuleContext.WAIT_FIELD_NAME);
                identifier = STNodeFactory.createSimpleNameReferenceNode(identifier);
                return createQualifiedWaitField(identifier);
            default:
                recover(peek(), ParserRuleContext.WAIT_FIELD_NAME);
                return parseWaitField();
        }
    }

    private STNode createQualifiedWaitField(STNode identifier) {
        if (peek().kind != SyntaxKind.COLON_TOKEN) {
            return identifier;
        }

        STNode colon = parseColon();
        STNode waitFutureExpr = parseWaitFutureExpr();
        return STNodeFactory.createWaitFieldNode(identifier, colon, waitFutureExpr);
    }

    /**
     * Parse annot access expression.
     * <p>
     * <code>
     * annot-access-expr := expression .@ annot-tag-reference
     * <br/>
     * annot-tag-reference := qualified-identifier | identifier
     * </code>
     *
     * @param lhsExpr Preceding expression of the annot access access
     * @return Parsed node
     */
    private STNode parseAnnotAccessExpression(STNode lhsExpr, boolean isInConditionalExpr) {
        STNode annotAccessToken = parseAnnotChainingToken();
        STNode annotTagReference = parseFieldAccessIdentifier(isInConditionalExpr);
        return STNodeFactory.createAnnotAccessExpressionNode(lhsExpr, annotAccessToken, annotTagReference);
    }

    /**
     * Parse annot-chaining-token.
     *
     * @return Parsed node
     */
    private STNode parseAnnotChainingToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ANNOT_CHAINING_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ANNOT_CHAINING_TOKEN);
            return parseAnnotChainingToken();
        }
    }

    /**
     * Parse field access identifier.
     * <p>
     * <code>field-access-identifier := qualified-identifier | identifier</code>
     *
     * @return Parsed node
     */
    private STNode parseFieldAccessIdentifier(boolean isInConditionalExpr) {
        STToken nextToken = peek();
        if (!isPredeclaredIdentifier(nextToken.kind)) {
            
            STNode identifier = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                    DiagnosticErrorCode.ERROR_MISSING_IDENTIFIER);
            return parseQualifiedIdentifier(identifier, isInConditionalExpr);
        }

        return parseQualifiedIdentifier(ParserRuleContext.FIELD_ACCESS_IDENTIFIER, isInConditionalExpr);
    }

    /**
     * Parse query action.
     * <p>
     * <code>query-action := query-pipeline do-clause
     * <br/>
     * do-clause := do block-stmt
     * </code>
     *
     * @param queryConstructType Query construct type. This is only for validation
     * @param queryPipeline      Query pipeline
     * @param selectClause       Select clause if any This is only for validation.
     * @return Query action node
     */
    private STNode parseQueryAction(STNode queryConstructType, STNode queryPipeline, STNode selectClause) {
        if (queryConstructType != null) {
            queryPipeline = SyntaxErrors.cloneWithLeadingInvalidNodeMinutiae(queryPipeline, queryConstructType,
                    DiagnosticErrorCode.ERROR_QUERY_CONSTRUCT_TYPE_IN_QUERY_ACTION);
        }
        if (selectClause != null) {
            queryPipeline = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(queryPipeline, selectClause,
                    DiagnosticErrorCode.ERROR_SELECT_CLAUSE_IN_QUERY_ACTION);
        }

        startContext(ParserRuleContext.DO_CLAUSE);
        STNode doKeyword = parseDoKeyword();
        STNode blockStmt = parseBlockNode();
        endContext();

        return STNodeFactory.createQueryActionNode(queryPipeline, doKeyword, blockStmt);
    }

    /**
     * Parse 'do' keyword.
     *
     * @return do keyword node
     */
    private STNode parseDoKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.DO_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.DO_KEYWORD);
            return parseDoKeyword();
        }
    }

    /**
     * Parse optional field access or xml optional attribute access expression.
     * <p>
     * <code>
     * optional-field-access-expr := expression ?. field-name
     * <br/>
     * xml-optional-attribute-access-expr := expression ?. xml-attribute-name
     * <br/>
     * xml-attribute-name := xml-qualified-name | qualified-identifier | identifier
     * <br/>
     * xml-qualified-name := xml-namespace-prefix : identifier
     * <br/>
     * xml-namespace-prefix := identifier
     * </code>
     *
     * @param lhsExpr Preceding expression of the optional access
     * @return Parsed node
     */
    private STNode parseOptionalFieldAccessExpression(STNode lhsExpr, boolean isInConditionalExpr) {
        STNode optionalFieldAccessToken = parseOptionalChainingToken();
        STNode fieldName = parseFieldAccessIdentifier(isInConditionalExpr);
        return STNodeFactory.createOptionalFieldAccessExpressionNode(lhsExpr, optionalFieldAccessToken, fieldName);
    }

    /**
     * Parse optional chaining token.
     *
     * @return parsed node
     */
    private STNode parseOptionalChainingToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.OPTIONAL_CHAINING_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.OPTIONAL_CHAINING_TOKEN);
            return parseOptionalChainingToken();
        }
    }

    /**
     * Parse conditional expression.
     * <p>
     * <code>conditional-expr := expression ? expression : expression</code>
     *
     * @param lhsExpr             Preceding expression of the question mark
     * @param isInConditionalExpr whether calling from a conditional-expr
     * @return Parsed node
     */
    private STNode parseConditionalExpression(STNode lhsExpr, boolean isInConditionalExpr) {
        startContext(ParserRuleContext.CONDITIONAL_EXPRESSION);
        STNode questionMark = parseQuestionMark();
        
        
        STNode middleExpr = parseExpression(OperatorPrecedence.ANON_FUNC_OR_LET, true, false, true);

        
        
        if (peek().kind != SyntaxKind.COLON_TOKEN) {
            if (middleExpr.kind == SyntaxKind.CONDITIONAL_EXPRESSION) {
                STConditionalExpressionNode innerConditionalExpr = (STConditionalExpressionNode) middleExpr;
                STNode innerMiddleExpr = innerConditionalExpr.middleExpression;

                
                
                
                STNode rightMostQNameRef = ConditionalExprResolver.getQualifiedNameRefNode(innerMiddleExpr, false);
                if (rightMostQNameRef != null) {
                    middleExpr = generateConditionalExprForRightMost(innerConditionalExpr.lhsExpression,
                            innerConditionalExpr.questionMarkToken, innerMiddleExpr, rightMostQNameRef);
                    endContext();
                    return STNodeFactory.createConditionalExpressionNode(lhsExpr, questionMark, middleExpr,
                            innerConditionalExpr.colonToken, innerConditionalExpr.endExpression);
                }

                STNode leftMostQNameRef = ConditionalExprResolver.getQualifiedNameRefNode(innerMiddleExpr, true);
                if (leftMostQNameRef != null) {
                    middleExpr = generateConditionalExprForLeftMost(innerConditionalExpr.lhsExpression,
                            innerConditionalExpr.questionMarkToken, innerMiddleExpr, leftMostQNameRef);
                    endContext();
                    return STNodeFactory.createConditionalExpressionNode(lhsExpr, questionMark, middleExpr,
                            innerConditionalExpr.colonToken, innerConditionalExpr.endExpression);
                }
            }

            
            
            
            STNode rightMostQNameRef = ConditionalExprResolver.getQualifiedNameRefNode(middleExpr, false);
            if (rightMostQNameRef != null) {
                endContext();
                return generateConditionalExprForRightMost(lhsExpr, questionMark, middleExpr, rightMostQNameRef);
            }

            STNode leftMostQNameRef = ConditionalExprResolver.getQualifiedNameRefNode(middleExpr, true);
            if (leftMostQNameRef != null) {
                endContext();
                return generateConditionalExprForLeftMost(lhsExpr, questionMark, middleExpr, leftMostQNameRef);
            }
        }

        return parseConditionalExprRhs(lhsExpr, questionMark, middleExpr, isInConditionalExpr);
    }

    private STNode generateConditionalExprForRightMost(STNode lhsExpr, STNode questionMark, STNode middleExpr,
                                                       STNode rightMostQualifiedNameRef) {
        STQualifiedNameReferenceNode qualifiedNameRef =
                (STQualifiedNameReferenceNode) rightMostQualifiedNameRef;
        STNode endExpr = STNodeFactory.createSimpleNameReferenceNode(qualifiedNameRef.identifier);

        STNode simpleNameRef =
                ConditionalExprResolver.getSimpleNameRefNode(qualifiedNameRef.modulePrefix);
        middleExpr = middleExpr.replace(rightMostQualifiedNameRef, simpleNameRef);
        return STNodeFactory.createConditionalExpressionNode(lhsExpr, questionMark, middleExpr, qualifiedNameRef.colon,
                endExpr);
    }

    private STNode generateConditionalExprForLeftMost(STNode lhsExpr, STNode questionMark, STNode middleExpr,
                                                      STNode leftMostQualifiedNameRef) {
        STQualifiedNameReferenceNode qualifiedNameRef = (STQualifiedNameReferenceNode) leftMostQualifiedNameRef;
        STNode simpleNameRef = STNodeFactory.createSimpleNameReferenceNode(qualifiedNameRef.identifier);
        STNode endExpr = middleExpr.replace(leftMostQualifiedNameRef, simpleNameRef);
        middleExpr = ConditionalExprResolver.getSimpleNameRefNode(qualifiedNameRef.modulePrefix);
        return STNodeFactory.createConditionalExpressionNode(lhsExpr, questionMark, middleExpr, qualifiedNameRef.colon,
                endExpr);
    }

    private STNode parseConditionalExprRhs(STNode lhsExpr, STNode questionMark, STNode middleExpr,
                                           boolean isInConditionalExpr) {
        STNode colon = parseColon();
        endContext();
        
        
        STNode endExpr = parseExpression(OperatorPrecedence.ANON_FUNC_OR_LET, true, false,
                isInConditionalExpr);
        return STNodeFactory.createConditionalExpressionNode(lhsExpr, questionMark, middleExpr, colon, endExpr);
    }

    /**
     * Parse enum declaration.
     * <p>
     * module-enum-decl :=
     * metadata
     * [public] enum identifier { enum-member (, enum-member)* } [;]
     * enum-member := metadata identifier [= const-expr]
     * </p>
     *
     * @param metadata
     * @param qualifier
     * @return Parsed enum node.
     */
    private STNode parseEnumDeclaration(STNode metadata, STNode qualifier) {
        startContext(ParserRuleContext.MODULE_ENUM_DECLARATION);
        STNode enumKeywordToken = parseEnumKeyword();
        STNode identifier = parseIdentifier(ParserRuleContext.MODULE_ENUM_NAME);
        STNode openBraceToken = parseOpenBrace();
        STNode enumMemberList = parseEnumMemberList();
        STNode closeBraceToken = parseCloseBrace();
        STNode semicolon = parseOptionalSemicolon();

        endContext();
        openBraceToken = cloneWithDiagnosticIfListEmpty(enumMemberList, openBraceToken,
                DiagnosticErrorCode.ERROR_MISSING_ENUM_MEMBER);
        return STNodeFactory.createEnumDeclarationNode(metadata, qualifier, enumKeywordToken, identifier,
                openBraceToken, enumMemberList, closeBraceToken, semicolon);
    }

    /**
     * Parse 'enum' keyword.
     *
     * @return enum keyword node
     */
    private STNode parseEnumKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ENUM_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ENUM_KEYWORD);
            return parseEnumKeyword();
        }
    }

    /**
     * Parse enum member list.
     * <p>
     * enum-member := metadata identifier [= const-expr]
     * </p>
     *
     * @return enum member list node.
     */
    private STNode parseEnumMemberList() {
        startContext(ParserRuleContext.ENUM_MEMBER_LIST);

        
        if (peek().kind == SyntaxKind.CLOSE_BRACE_TOKEN) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        List<STNode> enumMemberList = new ArrayList<>();
        STNode enumMember = parseEnumMember();

        
        STNode enumMemberRhs;
        while (peek().kind != SyntaxKind.CLOSE_BRACE_TOKEN) {
            enumMemberRhs = parseEnumMemberEnd();
            if (enumMemberRhs == null) {
                break;
            }
            enumMemberList.add(enumMember);
            enumMemberList.add(enumMemberRhs);
            enumMember = parseEnumMember();
        }

        enumMemberList.add(enumMember);

        endContext();
        return STNodeFactory.createNodeList(enumMemberList);
    }

    /**
     * Parse enum member.
     * <p>
     * enum-member := metadata identifier [= const-expr]
     * </p>
     *
     * @return Parsed enum member node.
     */
    private STNode parseEnumMember() {
        STNode metadata;
        switch (peek().kind) {
            case DOCUMENTATION_STRING:
            case AT_TOKEN:
                metadata = parseMetaData();
                break;
            default:
                metadata = STNodeFactory.createEmptyNode();
        }

        STNode identifierNode = parseIdentifier(ParserRuleContext.ENUM_MEMBER_NAME);
        return parseEnumMemberRhs(metadata, identifierNode);
    }

    private STNode parseEnumMemberRhs(STNode metadata, STNode identifierNode) {
        STNode equalToken, constExprNode;
        switch (peek().kind) {
            case EQUAL_TOKEN:
                equalToken = parseAssignOp();
                constExprNode = parseExpression();
                break;
            case COMMA_TOKEN:
            case CLOSE_BRACE_TOKEN:
                equalToken = STNodeFactory.createEmptyNode();
                constExprNode = STNodeFactory.createEmptyNode();
                break;
            default:
                recover(peek(), ParserRuleContext.ENUM_MEMBER_RHS);
                return parseEnumMemberRhs(metadata, identifierNode);
        }

        return STNodeFactory.createEnumMemberNode(metadata, identifierNode, equalToken, constExprNode);
    }

    private STNode parseEnumMemberEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACE_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.ENUM_MEMBER_END);
                return parseEnumMemberEnd();
        }
    }

    private STNode parseTransactionStmtOrVarDecl(STNode annots, List<STNode> qualifiers, STToken transactionKeyword) {
        switch (peek().kind) {
            case OPEN_BRACE_TOKEN:
                reportInvalidStatementAnnots(annots, qualifiers);
                reportInvalidQualifierList(qualifiers);
                return parseTransactionStatement(transactionKeyword);
            case COLON_TOKEN:
                if (getNextNextToken().kind == SyntaxKind.IDENTIFIER_TOKEN) {
                    STNode typeDesc = parseQualifiedIdentifierWithPredeclPrefix(transactionKeyword, false);
                    return parseVarDeclTypeDescRhs(typeDesc, annots, qualifiers, true, false);
                }
                
            default:
                Solution solution = recover(peek(), ParserRuleContext.TRANSACTION_STMT_RHS_OR_TYPE_REF);

                if (solution.action == Action.KEEP ||
                        (solution.action == Action.INSERT && solution.tokenKind == SyntaxKind.COLON_TOKEN)) {
                    
                    STNode typeDesc = parseQualifiedIdentifierWithPredeclPrefix(transactionKeyword, false);
                    return parseVarDeclTypeDescRhs(typeDesc, annots, qualifiers, true, false);
                }

                return parseTransactionStmtOrVarDecl(annots, qualifiers, transactionKeyword);
        }
    }

    /**
     * Parse transaction statement.
     * <p>
     * <code>transaction-stmt := `transaction` block-stmt [on-fail-clause]</code>
     *
     * @return Transaction statement node
     */
    private STNode parseTransactionStatement(STNode transactionKeyword) {
        startContext(ParserRuleContext.TRANSACTION_STMT);
        STNode blockStmt = parseBlockNode();
        endContext();
        STNode onFailClause = parseOptionalOnFailClause();
        return STNodeFactory.createTransactionStatementNode(transactionKeyword, blockStmt, onFailClause);
    }

    /**
     * Parse commit action.
     * <p>
     * <code>commit-action := "commit"</code>
     *
     * @return Commit action node
     */
    private STNode parseCommitAction() {
        STNode commitKeyword = parseCommitKeyword();
        return STNodeFactory.createCommitActionNode(commitKeyword);
    }

    /**
     * Parse commit keyword.
     *
     * @return parsed node
     */
    private STNode parseCommitKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.COMMIT_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.COMMIT_KEYWORD);
            return parseCommitKeyword();
        }
    }

    /**
     * Parse retry statement.
     * <p>
     * <code>
     * retry-stmt := `retry` retry-spec block-stmt [on-fail-clause]
     * <br/>
     * retry-spec :=  [type-parameter] [ `(` arg-list `)` ]
     * </code>
     *
     * @return Retry statement node
     */
    private STNode parseRetryStatement() {
        startContext(ParserRuleContext.RETRY_STMT);
        STNode retryKeyword = parseRetryKeyword();
        
        STNode retryStmt = parseRetryKeywordRhs(retryKeyword);
        return retryStmt;
    }

    private STNode parseRetryKeywordRhs(STNode retryKeyword) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case LT_TOKEN:
                STNode typeParam = parseTypeParameter();
                return parseRetryTypeParamRhs(retryKeyword, typeParam);
            case OPEN_PAREN_TOKEN:
            case OPEN_BRACE_TOKEN:
            case TRANSACTION_KEYWORD:
                typeParam = STNodeFactory.createEmptyNode();
                return parseRetryTypeParamRhs(retryKeyword, typeParam);
            default:
                recover(peek(), ParserRuleContext.RETRY_KEYWORD_RHS);
                return parseRetryKeywordRhs(retryKeyword);
        }
    }

    private STNode parseRetryTypeParamRhs(STNode retryKeyword, STNode typeParam) {
        STNode args;
        switch (peek().kind) {
            case OPEN_PAREN_TOKEN:
                args = parseParenthesizedArgList();
                break;
            case OPEN_BRACE_TOKEN:
            case TRANSACTION_KEYWORD:
                args = STNodeFactory.createEmptyNode();
                break;
            default:
                recover(peek(), ParserRuleContext.RETRY_TYPE_PARAM_RHS);
                return parseRetryTypeParamRhs(retryKeyword, typeParam);
        }

        STNode blockStmt = parseRetryBody();
        endContext(); 
        STNode onFailClause = parseOptionalOnFailClause();
        return STNodeFactory.createRetryStatementNode(retryKeyword, typeParam, args, blockStmt, onFailClause);
    }

    private STNode parseRetryBody() {
        switch (peek().kind) {
            case OPEN_BRACE_TOKEN:
                return parseBlockNode();
            case TRANSACTION_KEYWORD:
                return parseTransactionStatement(consume());
            default:
                recover(peek(), ParserRuleContext.RETRY_BODY);
                return parseRetryBody();
        }
    }

    /**
     * Parse optional on fail clause.
     *
     * @return Parsed node
     */
    private STNode parseOptionalOnFailClause() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.ON_KEYWORD) {
            return parseOnFailClause();
        }

        if (isEndOfRegularCompoundStmt(nextToken.kind)) {
            return STNodeFactory.createEmptyNode();
        }

        recover(nextToken, ParserRuleContext.REGULAR_COMPOUND_STMT_RHS);
        return parseOptionalOnFailClause();
    }

    private boolean isEndOfRegularCompoundStmt(SyntaxKind nodeKind) {
        switch (nodeKind) {
            case CLOSE_BRACE_TOKEN:
            case SEMICOLON_TOKEN:
            case AT_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return isStatementStartingToken(nodeKind);
        }
    }

    private boolean isStatementStartingToken(SyntaxKind nodeKind) {
        switch (nodeKind) {
            case FINAL_KEYWORD:

                
            case IF_KEYWORD:
            case WHILE_KEYWORD:
            case DO_KEYWORD:
            case PANIC_KEYWORD:
            case CONTINUE_KEYWORD:
            case BREAK_KEYWORD:
            case RETURN_KEYWORD:
            case LOCK_KEYWORD:
            case OPEN_BRACE_TOKEN:
            case FORK_KEYWORD:
            case FOREACH_KEYWORD:
            case XMLNS_KEYWORD:
            case TRANSACTION_KEYWORD:
            case RETRY_KEYWORD:
            case ROLLBACK_KEYWORD:
            case MATCH_KEYWORD:
            case FAIL_KEYWORD:

                
            case CHECK_KEYWORD:
            case CHECKPANIC_KEYWORD:
            case TRAP_KEYWORD:
            case START_KEYWORD:
            case FLUSH_KEYWORD:
            case LEFT_ARROW_TOKEN:
            case WAIT_KEYWORD:
            case COMMIT_KEYWORD:

                
                
                
            case WORKER_KEYWORD:
            case TYPE_KEYWORD: 
            case CONST_KEYWORD: 
                return true;
            default:
                
                if (isTypeStartingToken(nodeKind)) {
                    return true;
                }

                
                if (isValidExpressionStart(nodeKind, 1)) {
                    return true;
                }

                return false;
        }
    }

    /**
     * Parse on fail clause.
     * <p>
     * <code>
     * on-fail-clause := on fail [typed-binding-pattern] statement-block
     * </code>
     *
     * @return On fail clause node
     */
    private STNode parseOnFailClause() {
        startContext(ParserRuleContext.ON_FAIL_CLAUSE);
        STNode onKeyword = parseOnKeyword();
        STNode failKeyword = parseFailKeyword();
        STToken token = peek();
        STNode typeDescriptor = STNodeFactory.createEmptyNode();
        STNode identifier = STNodeFactory.createEmptyNode();
        if (token.kind != SyntaxKind.OPEN_BRACE_TOKEN) {
            typeDescriptor = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true, false,
                    TypePrecedence.DEFAULT);
            identifier = parseIdentifier(ParserRuleContext.VARIABLE_NAME);
        }
        STNode blockStatement = parseBlockNode();
        endContext();
        return STNodeFactory.createOnFailClauseNode(onKeyword, failKeyword, typeDescriptor, identifier,
                blockStatement);
    }

    /**
     * Parse retry keyword.
     *
     * @return parsed node
     */
    private STNode parseRetryKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.RETRY_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.RETRY_KEYWORD);
            return parseRetryKeyword();
        }
    }

    /**
     * Parse transaction statement.
     * <p>
     * <code>rollback-stmt := "rollback" [expression] ";"</code>
     *
     * @return Rollback statement node
     */
    private STNode parseRollbackStatement() {
        startContext(ParserRuleContext.ROLLBACK_STMT);
        STNode rollbackKeyword = parseRollbackKeyword();
        STNode expression;
        if (peek().kind == SyntaxKind.SEMICOLON_TOKEN) {
            expression = STNodeFactory.createEmptyNode();
        } else {
            expression = parseExpression();
        }

        STNode semicolon = parseSemicolon();
        endContext();
        return STNodeFactory.createRollbackStatementNode(rollbackKeyword, expression, semicolon);
    }

    /**
     * Parse rollback keyword.
     *
     * @return Rollback keyword node
     */
    private STNode parseRollbackKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ROLLBACK_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ROLLBACK_KEYWORD);
            return parseRollbackKeyword();
        }
    }

    /**
     * Parse transactional expression.
     * <p>
     * <code>transactional-expr := "transactional"</code>
     *
     * @return Transactional expression node
     */
    private STNode parseTransactionalExpression() {
        STNode transactionalKeyword = parseTransactionalKeyword();
        return STNodeFactory.createTransactionalExpressionNode(transactionalKeyword);
    }

    /**
     * Parse transactional keyword.
     *
     * @return Transactional keyword node
     */
    private STNode parseTransactionalKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.TRANSACTIONAL_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.TRANSACTIONAL_KEYWORD);
            return parseTransactionalKeyword();
        }
    }

    /**
     * Parse base16 literal.
     * <p>
     * <code>
     * byte-array-literal := Base16Literal | Base64Literal
     * <br/>
     * Base16Literal := base16 WS ` HexGroup* WS `
     * <br/>
     * Base64Literal := base64 WS ` Base64Group* [PaddedBase64Group] WS `
     * </code>
     *
     * @return parsed node
     */
    private STNode parseByteArrayLiteral() {
        STNode type;
        if (peek().kind == SyntaxKind.BASE16_KEYWORD) {
            type = parseBase16Keyword();
        } else {
            type = parseBase64Keyword();
        }

        STNode startingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);
        if (startingBackTick.isMissing()) {
            
            startingBackTick = SyntaxErrors.createMissingToken(SyntaxKind.BACKTICK_TOKEN);
            STNode endingBackTick = SyntaxErrors.createMissingToken(SyntaxKind.BACKTICK_TOKEN);
            STNode content = STNodeFactory.createEmptyNode();
            STNode byteArrayLiteral =
                    STNodeFactory.createByteArrayLiteralNode(type, startingBackTick, content, endingBackTick);
            byteArrayLiteral =
                    SyntaxErrors.addDiagnostic(byteArrayLiteral, DiagnosticErrorCode.ERROR_MISSING_BYTE_ARRAY_CONTENT);
            return byteArrayLiteral;
        }

        STNode content = parseByteArrayContent();
        return parseByteArrayLiteral(type, startingBackTick, content);
    }

    /**
     * Parse byte array literal.
     *
     * @param typeKeyword      keyword token, possible values are `base16` and `base64`
     * @param startingBackTick starting backtick token
     * @param byteArrayContent byte array literal content to be validated
     * @return parsed byte array literal node
     */
    private STNode parseByteArrayLiteral(STNode typeKeyword, STNode startingBackTick, STNode byteArrayContent) {
        STNode content = STNodeFactory.createEmptyNode();
        STNode newStartingBackTick = startingBackTick;
        STNodeList items = (STNodeList) byteArrayContent;
        if (items.size() == 1) {
            STNode item = items.get(0);
            if (typeKeyword.kind == SyntaxKind.BASE16_KEYWORD && !isValidBase16LiteralContent(item.toString())) {
                newStartingBackTick = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(startingBackTick, item,
                        DiagnosticErrorCode.ERROR_INVALID_BASE16_CONTENT_IN_BYTE_ARRAY_LITERAL);
            } else if (typeKeyword.kind == SyntaxKind.BASE64_KEYWORD && !isValidBase64LiteralContent(item.toString())) {
                newStartingBackTick = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(startingBackTick, item,
                        DiagnosticErrorCode.ERROR_INVALID_BASE64_CONTENT_IN_BYTE_ARRAY_LITERAL);
            } else if (item.kind != SyntaxKind.TEMPLATE_STRING) {
                newStartingBackTick = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(startingBackTick, item,
                        DiagnosticErrorCode.ERROR_INVALID_CONTENT_IN_BYTE_ARRAY_LITERAL);
            } else {
                content = item;
            }
        } else if (items.size() > 1) {
            
            STNode clonedStartingBackTick = startingBackTick;
            for (int index = 0; index < items.size(); index++) {
                STNode item = items.get(index);
                clonedStartingBackTick =
                        SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(clonedStartingBackTick, item);
            }
            newStartingBackTick = SyntaxErrors.addDiagnostic(clonedStartingBackTick,
                    DiagnosticErrorCode.ERROR_INVALID_CONTENT_IN_BYTE_ARRAY_LITERAL);
        }

        STNode endingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_END);
        return STNodeFactory.createByteArrayLiteralNode(typeKeyword, newStartingBackTick, content, endingBackTick);
    }

    /**
     * Parse <code>base16</code> keyword.
     *
     * @return base16 keyword node
     */
    private STNode parseBase16Keyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.BASE16_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.BASE16_KEYWORD);
            return parseBase16Keyword();
        }
    }

    /**
     * Parse <code>base64</code> keyword.
     *
     * @return base64 keyword node
     */
    private STNode parseBase64Keyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.BASE64_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.BASE64_KEYWORD);
            return parseBase64Keyword();
        }
    }

    /**
     * Validate and parse byte array literal content.
     * An error is reported, if the content is invalid.
     *
     * @return parsed node
     */
    private STNode parseByteArrayContent() {
        STToken nextToken = peek();

        List<STNode> items = new ArrayList<>();
        while (!isEndOfBacktickContent(nextToken.kind)) {
            STNode content = parseTemplateItem();
            items.add(content);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(items);
    }

    /**
     * Validate base16 literal content.
     * <p>
     * <code>
     * Base16Literal := base16 WS ` HexGroup* WS `
     * <br/>
     * HexGroup := WS HexDigit WS HexDigit
     * <br/>
     * WS := WhiteSpaceChar*
     * <br/>
     * WhiteSpaceChar := 0x9 | 0xA | 0xD | 0x20
     * </code>
     *
     * @param content the string surrounded by the backticks
     * @return <code>true</code>, if the string content is valid. <code>false</code> otherwise.
     */
    static boolean isValidBase16LiteralContent(String content) {
        char[] charArray = content.toCharArray();
        int hexDigitCount = 0;

        for (char c : charArray) {
            switch (c) {
                case LexerTerminals.TAB:
                case LexerTerminals.NEWLINE:
                case LexerTerminals.CARRIAGE_RETURN:
                case LexerTerminals.SPACE:
                    break;
                default:
                    if (isHexDigit(c)) {
                        hexDigitCount++;
                    } else {
                        return false;
                    }
                    break;
            }
        }

        return hexDigitCount % 2 == 0;
    }

    /**
     * Validate base64 literal content.
     * <p>
     * <code>
     * Base64Literal := base64 WS ` Base64Group* [PaddedBase64Group] WS `
     * <br/>
     * Base64Group := WS Base64Char WS Base64Char WS Base64Char WS Base64Char
     * <br/>
     * PaddedBase64Group :=
     * WS Base64Char WS Base64Char WS Base64Char WS PaddingChar
     * | WS Base64Char WS Base64Char WS PaddingChar WS PaddingChar
     * <br/>
     * Base64Char := A .. Z | a .. z | 0 .. 9 | + | /
     * <br/>
     * PaddingChar := =
     * <br/>
     * WS := WhiteSpaceChar*
     * <br/>
     * WhiteSpaceChar := 0x9 | 0xA | 0xD | 0x20
     * </code>
     *
     * @param content the string surrounded by the backticks
     * @return <code>true</code>, if the string content is valid. <code>false</code> otherwise.
     */
    static boolean isValidBase64LiteralContent(String content) {
        char[] charArray = content.toCharArray();
        int base64CharCount = 0;
        int paddingCharCount = 0;

        for (char c : charArray) {
            switch (c) {
                case LexerTerminals.TAB:
                case LexerTerminals.NEWLINE:
                case LexerTerminals.CARRIAGE_RETURN:
                case LexerTerminals.SPACE:
                    break;
                case LexerTerminals.EQUAL:
                    paddingCharCount++;
                    break;
                default:
                    if (isBase64Char(c)) {
                        if (paddingCharCount == 0) {
                            base64CharCount++;
                        } else {
                            return false;
                        }
                    } else {
                        return false;
                    }
                    break;
            }
        }

        if (paddingCharCount > 2) {
            return false;
        } else if (paddingCharCount == 0) {
            return base64CharCount % 4 == 0;
        } else {
            return base64CharCount % 4 == 4 - paddingCharCount;
        }
    }

    /**
     * <p>
     * Check whether a given char is a base64 char.
     * </p>
     * <code>Base64Char := A .. Z | a .. z | 0 .. 9 | + | /</code>
     *
     * @param c character to check
     * @return <code>true</code>, if the character represents a base64 char. <code>false</code> otherwise.
     */
    static boolean isBase64Char(int c) {
        if ('a' <= c && c <= 'z') {
            return true;
        }
        if ('A' <= c && c <= 'Z') {
            return true;
        }
        if (c == '+' || c == '/') {
            return true;
        }
        return isDigit(c);
    }

    static boolean isHexDigit(int c) {
        if ('a' <= c && c <= 'f') {
            return true;
        }
        if ('A' <= c && c <= 'F') {
            return true;
        }
        return isDigit(c);
    }

    static boolean isDigit(int c) {
        return ('0' <= c && c <= '9');
    }

    /**
     * Parse xml filter expression.
     * <p>
     * <code>xml-filter-expr := expression .< xml-name-pattern ></code>
     *
     * @param lhsExpr Preceding expression of .< token
     * @return Parsed node
     */
    private STNode parseXMLFilterExpression(STNode lhsExpr) {
        STNode xmlNamePatternChain = parseXMLFilterExpressionRhs();
        return STNodeFactory.createXMLFilterExpressionNode(lhsExpr, xmlNamePatternChain);
    }

    /**
     * Parse xml filter expression rhs.
     * <p>
     * <code>filer-expression-rhs := .< xml-name-pattern ></code>
     *
     * @return Parsed node
     */
    private STNode parseXMLFilterExpressionRhs() {
        STNode dotLTToken = parseDotLTToken();
        return parseXMLNamePatternChain(dotLTToken);
    }

    /**
     * Parse xml name pattern chain.
     * <p>
     * <code>
     * xml-name-pattern-chain := filer-expression-rhs | xml-element-children-step | xml-element-descendants-step
     * <br/>
     * filer-expression-rhs := .< xml-name-pattern >
     * <br/>
     * xml-element-children-step := /< xml-name-pattern >
     * <br/>
     * xml-element-descendants-step := /**\/<xml-name-pattern >
     * </code>
     *
     * @param startToken Preceding token of xml name pattern
     * @return Parsed node
     */
    private STNode parseXMLNamePatternChain(STNode startToken) {
        startContext(ParserRuleContext.XML_NAME_PATTERN);
        STNode xmlNamePattern = parseXMLNamePattern();
        STNode gtToken = parseGTToken();
        endContext();

        startToken = cloneWithDiagnosticIfListEmpty(xmlNamePattern, startToken,
                DiagnosticErrorCode.ERROR_MISSING_XML_ATOMIC_NAME_PATTERN);
        return STNodeFactory.createXMLNamePatternChainingNode(startToken, xmlNamePattern, gtToken);
    }

    /**
     * Parse <code> .< </code> token.
     *
     * @return Parsed node
     */
    private STNode parseDotLTToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.DOT_LT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.DOT_LT_TOKEN);
            return parseDotLTToken();
        }
    }

    /**
     * Parse xml name pattern.
     * <p>
     * <code>xml-name-pattern := xml-atomic-name-pattern [| xml-atomic-name-pattern]*</code>
     *
     * @return Parsed node
     */
    private STNode parseXMLNamePattern() {
        List<STNode> xmlAtomicNamePatternList = new ArrayList<>();
        STToken nextToken = peek();

        
        if (isEndOfXMLNamePattern(nextToken.kind)) {
            return STNodeFactory.createNodeList(xmlAtomicNamePatternList);
        }

        
        STNode xmlAtomicNamePattern = parseXMLAtomicNamePattern();
        xmlAtomicNamePatternList.add(xmlAtomicNamePattern);

        
        STNode separator;
        while (!isEndOfXMLNamePattern(peek().kind)) {
            separator = parseXMLNamePatternSeparator();
            if (separator == null) {
                break;
            }
            xmlAtomicNamePatternList.add(separator);

            xmlAtomicNamePattern = parseXMLAtomicNamePattern();
            xmlAtomicNamePatternList.add(xmlAtomicNamePattern);
        }

        return STNodeFactory.createNodeList(xmlAtomicNamePatternList);
    }

    private boolean isEndOfXMLNamePattern(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case GT_TOKEN:
            case EOF_TOKEN:
                return true;
            case IDENTIFIER_TOKEN:
            case ASTERISK_TOKEN:
            case COLON_TOKEN:
            default:
                return false;
        }
    }

    private STNode parseXMLNamePatternSeparator() {
        STToken token = peek();
        switch (token.kind) {
            case PIPE_TOKEN:
                return consume();
            case GT_TOKEN:
            case EOF_TOKEN:
                return null;
            default:
                recover(token, ParserRuleContext.XML_NAME_PATTERN_RHS);
                return parseXMLNamePatternSeparator();
        }
    }

    /**
     * Parse xml atomic name pattern.
     * <p>
     * <code>
     * xml-atomic-name-pattern :=
     * *
     * | identifier
     * | xml-namespace-prefix : identifier
     * | xml-namespace-prefix : *
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseXMLAtomicNamePattern() {
        startContext(ParserRuleContext.XML_ATOMIC_NAME_PATTERN);
        STNode atomicNamePattern = parseXMLAtomicNamePatternBody();
        endContext();
        return atomicNamePattern;
    }

    private STNode parseXMLAtomicNamePatternBody() {
        STToken token = peek();
        STNode identifier;
        switch (token.kind) {
            case ASTERISK_TOKEN:
                return consume();
            case IDENTIFIER_TOKEN:
                identifier = consume();
                break;
            default:
                recover(token, ParserRuleContext.XML_ATOMIC_NAME_PATTERN_START);
                return parseXMLAtomicNamePatternBody();
        }

        return parseXMLAtomicNameIdentifier(identifier);
    }

    private STNode parseXMLAtomicNameIdentifier(STNode identifier) {
        STToken token = peek();
        if (token.kind == SyntaxKind.COLON_TOKEN) {
            STNode colon = consume();
            STToken nextToken = peek();
            if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN || nextToken.kind == SyntaxKind.ASTERISK_TOKEN) {
                STToken endToken = consume();
                return STNodeFactory.createXMLAtomicNamePatternNode(identifier, colon, endToken);
            }
        }
        return STNodeFactory.createSimpleNameReferenceNode(identifier);
    }

    /**
     * Parse xml step expression.
     * <p>
     * <code>xml-step-expr := expression xml-step-start</code>
     *
     * @param lhsExpr Preceding expression of /*, /<, or /**\/< token
     * @return Parsed node
     */
    private STNode parseXMLStepExpression(STNode lhsExpr) {
        STNode xmlStepStart = parseXMLStepStart();
        return STNodeFactory.createXMLStepExpressionNode(lhsExpr, xmlStepStart);
    }

    /**
     * Parse xml filter expression rhs.
     * <p>
     * <code>
     * xml-step-start :=
     * xml-all-children-step
     * | xml-element-children-step
     * | xml-element-descendants-step
     * <br/>
     * xml-all-children-step := /*
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseXMLStepStart() {
        STToken token = peek();
        STNode startToken;

        switch (token.kind) {
            case SLASH_ASTERISK_TOKEN:
                return consume();
            case DOUBLE_SLASH_DOUBLE_ASTERISK_LT_TOKEN:
                startToken = parseDoubleSlashDoubleAsteriskLTToken();
                break;
            case SLASH_LT_TOKEN:
            default:
                startToken = parseSlashLTToken();
                break;
        }
        return parseXMLNamePatternChain(startToken);
    }

    /**
     * Parse <code> /< </code> token.
     *
     * @return Parsed node
     */
    private STNode parseSlashLTToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.SLASH_LT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.SLASH_LT_TOKEN);
            return parseSlashLTToken();
        }
    }

    /**
     * Parse <code> /< </code> token.
     *
     * @return Parsed node
     */
    private STNode parseDoubleSlashDoubleAsteriskLTToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.DOUBLE_SLASH_DOUBLE_ASTERISK_LT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.DOUBLE_SLASH_DOUBLE_ASTERISK_LT_TOKEN);
            return parseDoubleSlashDoubleAsteriskLTToken();
        }
    }

    /**
     * Parse match statement.
     * <p>
     * <code>match-stmt := match action-or-expr { match-clause+ } [on-fail-clause]</code>
     *
     * @return Match statement
     */
    private STNode parseMatchStatement() {
        startContext(ParserRuleContext.MATCH_STMT);
        STNode matchKeyword = parseMatchKeyword();
        STNode actionOrExpr = parseActionOrExpression();
        startContext(ParserRuleContext.MATCH_BODY);
        STNode openBrace = parseOpenBrace();
        
        List<STNode> matchClausesList = new ArrayList<>();
        while (!isEndOfMatchClauses(peek().kind)) {
            STNode clause = parseMatchClause();
            matchClausesList.add(clause);
        }
        STNode matchClauses =  STNodeFactory.createNodeList(matchClausesList);
        
        if (isNodeListEmpty(matchClauses)) {
            openBrace = SyntaxErrors.addDiagnostic(openBrace,
                    DiagnosticErrorCode.ERROR_MATCH_STATEMENT_SHOULD_HAVE_ONE_OR_MORE_MATCH_CLAUSES);
        }

        STNode closeBrace = parseCloseBrace();
        endContext();
        endContext();
        STNode onFailClause = parseOptionalOnFailClause();
        return STNodeFactory.createMatchStatementNode(matchKeyword, actionOrExpr, openBrace, matchClauses, closeBrace,
                onFailClause);
    }

    /**
     * Parse match keyword.
     *
     * @return Match keyword node
     */
    private STNode parseMatchKeyword() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.MATCH_KEYWORD) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.MATCH_KEYWORD);
            return parseMatchKeyword();
        }
    }

    private boolean isEndOfMatchClauses(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case TYPE_KEYWORD:
                return true;
            default:
                return isEndOfStatements();
        }
    }

    /**
     * Parse a single match match clause.
     * <p>
     * <code>
     * match-clause := match-pattern-list [match-guard] => block-stmt
     * <br/>
     * match-guard := if expression
     * </code>
     *
     * @return A match clause
     */
    private STNode parseMatchClause() {
        STNode matchPatterns = parseMatchPatternList();
        STNode matchGuard = parseMatchGuard();
        STNode rightDoubleArrow = parseDoubleRightArrow();
        STNode blockStmt = parseBlockNode();
        
        if (isNodeListEmpty(matchPatterns)) {
            STToken identifier = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
            STNode constantPattern = STNodeFactory.createSimpleNameReferenceNode(identifier);
            matchPatterns = STNodeFactory.createNodeList(constantPattern);
            
            DiagnosticErrorCode errorCode = DiagnosticErrorCode.ERROR_MISSING_MATCH_PATTERN;
            if (matchGuard != null) {
                matchGuard = SyntaxErrors.addDiagnostic(matchGuard, errorCode);
            } else {
                rightDoubleArrow = SyntaxErrors.addDiagnostic(rightDoubleArrow, errorCode);
            }
        }
        
        return STNodeFactory.createMatchClauseNode(matchPatterns, matchGuard, rightDoubleArrow, blockStmt);
    }

    /**
     * Parse match guard.
     * <p>
     * <code>match-guard := if expression</code>
     *
     * @return Match guard
     */
    private STNode parseMatchGuard() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IF_KEYWORD:
                STNode ifKeyword = parseIfKeyword();
                STNode expr = parseExpression(DEFAULT_OP_PRECEDENCE, true, false, true, false);
                return STNodeFactory.createMatchGuardNode(ifKeyword, expr);
            case RIGHT_DOUBLE_ARROW_TOKEN:
                return STNodeFactory.createEmptyNode();
            default:
                recover(nextToken, ParserRuleContext.OPTIONAL_MATCH_GUARD);
                return parseMatchGuard();
        }
    }

    /**
     * Parse match patterns list.
     * <p>
     * <code>match-pattern-list := match-pattern (| match-pattern)*</code>
     *
     * @return Match patterns list
     */
    private STNode parseMatchPatternList() {
        startContext(ParserRuleContext.MATCH_PATTERN);
        List<STNode> matchClauses = new ArrayList<>();
        while (!isEndOfMatchPattern(peek().kind)) {
            STNode clause = parseMatchPattern();
            if (clause == null) {
                break;
            }
            matchClauses.add(clause);

            STNode seperator = parseMatchPatternListMemberRhs();
            if (seperator == null) {
                break;
            }
            matchClauses.add(seperator);
        }

        endContext();
        return STNodeFactory.createNodeList(matchClauses);
    }

    private boolean isEndOfMatchPattern(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case PIPE_TOKEN:
            case IF_KEYWORD:
            case RIGHT_DOUBLE_ARROW_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse match pattern.
     * <p>
     * <code>
     * match-pattern := var binding-pattern
     * | wildcard-match-pattern
     * | const-pattern
     * | list-match-pattern
     * | mapping-match-pattern
     * | error-match-pattern
     * </code>
     *
     * @return Match pattern
     */
    private STNode parseMatchPattern() {
        STToken nextToken = peek();
        if (isPredeclaredIdentifier(nextToken.kind)) {
            
            STNode typeRefOrConstExpr = parseQualifiedIdentifier(ParserRuleContext.MATCH_PATTERN);
            return parseErrorMatchPatternOrConsPattern(typeRefOrConstExpr);
        }

        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
            case NULL_KEYWORD:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case PLUS_TOKEN:
            case MINUS_TOKEN:
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
            case STRING_LITERAL_TOKEN:
                return parseSimpleConstExpr();
            case VAR_KEYWORD:
                return parseVarTypedBindingPattern();
            case OPEN_BRACKET_TOKEN:
                return parseListMatchPattern();
            case OPEN_BRACE_TOKEN:
                return parseMappingMatchPattern();
            case ERROR_KEYWORD:
                return parseErrorMatchPattern();
            default:
                recover(nextToken, ParserRuleContext.MATCH_PATTERN_START);
                return parseMatchPattern();
        }
    }

    private STNode parseMatchPatternListMemberRhs() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case PIPE_TOKEN:
                return parsePipeToken();
            case IF_KEYWORD:
            case RIGHT_DOUBLE_ARROW_TOKEN:
                
                return null;
            default:
                recover(nextToken, ParserRuleContext.MATCH_PATTERN_LIST_MEMBER_RHS);
                return parseMatchPatternListMemberRhs();
        }
    }

    /**
     * Parse var typed binding pattern.
     * <p>
     * <code>var binding-pattern</code>
     * </p>
     *
     * @return Parsed typed binding pattern node
     */
    private STNode parseVarTypedBindingPattern() {
        STNode varKeyword = parseVarKeyword();
        STNode varTypeDesc = createBuiltinSimpleNameReference(varKeyword);
        STNode bindingPattern = parseBindingPattern();
        return STNodeFactory.createTypedBindingPatternNode(varTypeDesc, bindingPattern);
    }

    /**
     * Parse var keyword.
     *
     * @return Var keyword node
     */
    private STNode parseVarKeyword() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.VAR_KEYWORD) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.VAR_KEYWORD);
            return parseVarKeyword();
        }
    }

    /**
     * Parse list match pattern.
     * <p>
     * <code>
     * list-match-pattern := [ list-member-match-patterns ]
     * list-member-match-patterns :=
     * match-pattern (, match-pattern)* [, rest-match-pattern]
     * | [ rest-match-pattern ]
     * </code>
     * </p>
     *
     * @return Parsed list match pattern node
     */
    private STNode parseListMatchPattern() {
        startContext(ParserRuleContext.LIST_MATCH_PATTERN);
        STNode openBracketToken = parseOpenBracket();
        List<STNode> matchPatternList = new ArrayList<>();
        STNode listMatchPatternMemberRhs = null;
        boolean isEndOfFields = false;

        while (!isEndOfListMatchPattern()) {
            STNode listMatchPatternMember = parseListMatchPatternMember();
            matchPatternList.add(listMatchPatternMember);
            listMatchPatternMemberRhs = parseListMatchPatternMemberRhs();

            if (listMatchPatternMember.kind == SyntaxKind.REST_MATCH_PATTERN) {
                isEndOfFields = true;
                break;
            }

            if (listMatchPatternMemberRhs != null) {
                matchPatternList.add(listMatchPatternMemberRhs);
            } else {
                break;
            }
        }

        
        
        while (isEndOfFields && listMatchPatternMemberRhs != null) {
            updateLastNodeInListWithInvalidNode(matchPatternList, listMatchPatternMemberRhs, null);

            if (peek().kind == SyntaxKind.CLOSE_BRACKET_TOKEN) {
                break;
            }

            STNode invalidField = parseListMatchPatternMember();
            updateLastNodeInListWithInvalidNode(matchPatternList, invalidField,
                    DiagnosticErrorCode.ERROR_MATCH_PATTERN_AFTER_REST_MATCH_PATTERN);
            listMatchPatternMemberRhs = parseListMatchPatternMemberRhs();
        }

        STNode matchPatternListNode = STNodeFactory.createNodeList(matchPatternList);
        STNode closeBracketToken = parseCloseBracket();
        endContext();

        return STNodeFactory.createListMatchPatternNode(openBracketToken, matchPatternListNode, closeBracketToken);
    }

    public boolean isEndOfListMatchPattern() {
        switch (peek().kind) {
            case CLOSE_BRACKET_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseListMatchPatternMember() {
        STNode nextToken = peek();
        switch (nextToken.kind) {
            case ELLIPSIS_TOKEN:
                return parseRestMatchPattern();
            default:
                
                return parseMatchPattern();
        }
    }

    /**
     * Parse rest match pattern.
     * <p>
     * <code>
     * rest-match-pattern := ... var variable-name
     * </code>
     * </p>
     *
     * @return Parsed rest match pattern node
     */
    private STNode parseRestMatchPattern() {
        startContext(ParserRuleContext.REST_MATCH_PATTERN);
        STNode ellipsisToken = parseEllipsis();
        STNode varKeywordToken = parseVarKeyword();
        STNode variableName = parseVariableName();
        endContext();

        STSimpleNameReferenceNode simpleNameReferenceNode =
                (STSimpleNameReferenceNode) STNodeFactory.createSimpleNameReferenceNode(variableName);
        return STNodeFactory.createRestMatchPatternNode(ellipsisToken, varKeywordToken, simpleNameReferenceNode);
    }

    private STNode parseListMatchPatternMemberRhs() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACKET_TOKEN:
            case EOF_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.LIST_MATCH_PATTERN_MEMBER_RHS);
                return parseListMatchPatternMemberRhs();
        }
    }

    /**
     * Parse mapping match pattern.
     * <p>
     * mapping-match-pattern := { field-match-patterns }
     * <br/>
     * field-match-patterns := field-match-pattern (, field-match-pattern)* [, rest-match-pattern]
     * | [ rest-match-pattern ]
     * <br/>
     * field-match-pattern := field-name : match-pattern
     * <br/>
     * rest-match-pattern := ... var variable-name
     * </p>
     *
     * @return Parsed Node.
     */
    private STNode parseMappingMatchPattern() {
        startContext(ParserRuleContext.MAPPING_MATCH_PATTERN);
        STNode openBraceToken = parseOpenBrace();
        STNode fieldMatchPatterns = parseFieldMatchPatternList();
        STNode closeBraceToken = parseCloseBrace();
        endContext();
        return STNodeFactory.createMappingMatchPatternNode(openBraceToken, fieldMatchPatterns, closeBraceToken);
    }

    private STNode parseFieldMatchPatternList() {
        List<STNode> fieldMatchPatterns = new ArrayList<>();

        STNode fieldMatchPatternMember = parseFieldMatchPatternMember();
        if (fieldMatchPatternMember == null) {
            return STNodeFactory.createEmptyNodeList();
        }

        fieldMatchPatterns.add(fieldMatchPatternMember);
        if (fieldMatchPatternMember.kind == SyntaxKind.REST_MATCH_PATTERN) {
            invalidateExtraFieldMatchPatterns(fieldMatchPatterns);
            return STNodeFactory.createNodeList(fieldMatchPatterns);
        }

        return parseFieldMatchPatternList(fieldMatchPatterns);
    }

    private STNode parseFieldMatchPatternList(List<STNode> fieldMatchPatterns) {
        while (!isEndOfMappingMatchPattern()) {
            STNode fieldMatchPatternRhs = parseFieldMatchPatternRhs();
            if (fieldMatchPatternRhs == null) {
                break;
            }

            fieldMatchPatterns.add(fieldMatchPatternRhs);
            STNode fieldMatchPatternMember = parseFieldMatchPatternMember();
            if (fieldMatchPatternMember == null) {
                fieldMatchPatternMember = createMissingFieldMatchPattern();
            }

            fieldMatchPatterns.add(fieldMatchPatternMember);
            if (fieldMatchPatternMember.kind == SyntaxKind.REST_MATCH_PATTERN) {
                invalidateExtraFieldMatchPatterns(fieldMatchPatterns);
                break;
            }
        }

        return STNodeFactory.createNodeList(fieldMatchPatterns);
    }

    private STNode createMissingFieldMatchPattern() {
        STNode fieldName = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
        STNode colon = SyntaxErrors.createMissingToken(SyntaxKind.COLON_TOKEN);
        STNode identifier = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
        STNode matchPattern = STNodeFactory.createSimpleNameReferenceNode(identifier);
        STNode fieldMatchPatternMember = STNodeFactory.createFieldMatchPatternNode(fieldName, colon, matchPattern);
        fieldMatchPatternMember = SyntaxErrors.addDiagnostic(fieldMatchPatternMember,
                DiagnosticErrorCode.ERROR_MISSING_FIELD_MATCH_PATTERN_MEMBER);
        return fieldMatchPatternMember;
    }

    /**
     * Parse and invalidate all field match pattern members after a rest-match-pattern.
     *
     * @param fieldMatchPatterns field-match-patterns list
     */
    private void invalidateExtraFieldMatchPatterns(List<STNode> fieldMatchPatterns) {
        while (!isEndOfMappingMatchPattern()) {
            STNode fieldMatchPatternRhs = parseFieldMatchPatternRhs();
            if (fieldMatchPatternRhs == null) {
                break;
            }

            STNode fieldMatchPatternMember = parseFieldMatchPatternMember();
            if (fieldMatchPatternMember == null) {
                updateLastNodeInListWithInvalidNode(fieldMatchPatterns, fieldMatchPatternRhs,
                        DiagnosticErrorCode.ERROR_INVALID_TOKEN, ((STToken) fieldMatchPatternRhs).text());
            } else {
                updateLastNodeInListWithInvalidNode(fieldMatchPatterns, fieldMatchPatternRhs, null);
                updateLastNodeInListWithInvalidNode(fieldMatchPatterns, fieldMatchPatternMember,
                        DiagnosticErrorCode.ERROR_MATCH_PATTERN_AFTER_REST_MATCH_PATTERN);
            }
        }
    }

    private STNode parseFieldMatchPatternMember() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
                return parseFieldMatchPattern();
            case ELLIPSIS_TOKEN:
                return parseRestMatchPattern();
            case CLOSE_BRACE_TOKEN:
            case EOF_TOKEN:
                
                return null;
            default:
                recover(nextToken, ParserRuleContext.FIELD_MATCH_PATTERNS_START);
                return parseFieldMatchPatternMember();
        }
    }

    /**
     * Parse filed match pattern.
     * <p>
     * field-match-pattern := field-name : match-pattern
     * </p>
     *
     * @return Parsed field match pattern node
     */
    public STNode parseFieldMatchPattern() {
        STNode fieldNameNode = parseVariableName();
        STNode colonToken = parseColon();
        STNode matchPattern = parseMatchPattern();
        return STNodeFactory.createFieldMatchPatternNode(fieldNameNode, colonToken, matchPattern);
    }

    public boolean isEndOfMappingMatchPattern() {
        switch (peek().kind) {
            case CLOSE_BRACE_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseFieldMatchPatternRhs() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACE_TOKEN:
            case EOF_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.FIELD_MATCH_PATTERN_MEMBER_RHS);
                return parseFieldMatchPatternRhs();
        }
    }

    private STNode parseErrorMatchPatternOrConsPattern(STNode typeRefOrConstExpr) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                STNode errorKeyword = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.ERROR_KEYWORD,
                        ParserRuleContext.ERROR_KEYWORD);
                startContext(ParserRuleContext.ERROR_MATCH_PATTERN); 
                return parseErrorMatchPattern(errorKeyword, typeRefOrConstExpr);
            default:
                if (isMatchPatternEnd(peek().kind)) {
                    return typeRefOrConstExpr;
                }
                recover(peek(), ParserRuleContext.ERROR_MATCH_PATTERN_OR_CONST_PATTERN);
                return parseErrorMatchPatternOrConsPattern(typeRefOrConstExpr);
        }
    }

    private boolean isMatchPatternEnd(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case RIGHT_DOUBLE_ARROW_TOKEN:
            case COMMA_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case CLOSE_BRACKET_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case PIPE_TOKEN:
            case IF_KEYWORD:
            case EOF_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse functional match pattern.
     * <p>
     * error-match-pattern := error [error-type-reference] ( error-arg-list-match-pattern )
     * error-arg-list-match-pattern :=
     * error-message-match-pattern [, error-cause-match-pattern] [, error-field-match-patterns]
     * | [error-field-match-patterns]
     * error-message-match-pattern := simple-match-pattern
     * error-cause-match-pattern := simple-match-pattern | error-match-pattern
     * simple-match-pattern :=
     * wildcard-match-pattern
     * | const-pattern
     * | var variable-name
     * error-field-match-patterns :=
     * named-arg-match-pattern (, named-arg-match-pattern)* [, rest-match-pattern]
     * | rest-match-pattern
     * named-arg-match-pattern := arg-name = match-pattern
     * </p>
     *
     * @return Parsed functional match pattern node.
     */
    private STNode parseErrorMatchPattern() {
        startContext(ParserRuleContext.ERROR_MATCH_PATTERN);
        STNode errorKeyword = consume();
        return parseErrorMatchPattern(errorKeyword);
    }

    private STNode parseErrorMatchPattern(STNode errorKeyword) {
        STToken nextToken = peek();
        STNode typeRef;
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                typeRef = STNodeFactory.createEmptyNode();
                break;
            default:
                if (isPredeclaredIdentifier(nextToken.kind)) {
                    typeRef = parseTypeReference();
                    break;
                }
                recover(peek(), ParserRuleContext.ERROR_MATCH_PATTERN_ERROR_KEYWORD_RHS);
                return parseErrorMatchPattern(errorKeyword);
        }
        return parseErrorMatchPattern(errorKeyword, typeRef);
    }

    private STNode parseErrorMatchPattern(STNode errorKeyword, STNode typeRef) {
        STNode openParenthesisToken = parseOpenParenthesis();
        STNode argListMatchPatternNode = parseErrorArgListMatchPatterns();
        STNode closeParenthesisToken = parseCloseParenthesis();
        endContext();
        return STNodeFactory.createErrorMatchPatternNode(errorKeyword, typeRef, openParenthesisToken,
                argListMatchPatternNode, closeParenthesisToken);
    }

    private STNode parseErrorArgListMatchPatterns() {
        List<STNode> argListMatchPatterns = new ArrayList<>();

        if (isEndOfErrorFieldMatchPatterns()) {
            return STNodeFactory.createNodeList(argListMatchPatterns);
        }
        startContext(ParserRuleContext.ERROR_ARG_LIST_MATCH_PATTERN_FIRST_ARG);
        STNode firstArg = parseErrorArgListMatchPattern(ParserRuleContext.ERROR_ARG_LIST_MATCH_PATTERN_START);
        endContext();

        if (isSimpleMatchPattern(firstArg.kind)) {

            argListMatchPatterns.add(firstArg);
            STNode argEnd = parseErrorArgListMatchPatternEnd(ParserRuleContext.ERROR_MESSAGE_MATCH_PATTERN_END);
            if (argEnd != null) {
                
                STNode secondArg = parseErrorArgListMatchPattern(ParserRuleContext.ERROR_MESSAGE_MATCH_PATTERN_RHS);
                if (isValidSecondArgMatchPattern(secondArg.kind)) {
                    argListMatchPatterns.add(argEnd);
                    argListMatchPatterns.add(secondArg);
                } else {
                    updateLastNodeInListWithInvalidNode(argListMatchPatterns, argEnd, null);
                    updateLastNodeInListWithInvalidNode(argListMatchPatterns, secondArg,
                            DiagnosticErrorCode.ERROR_MATCH_PATTERN_NOT_ALLOWED);
                }
            }
        } else {
            if (firstArg.kind != SyntaxKind.NAMED_ARG_MATCH_PATTERN &&
                    firstArg.kind != SyntaxKind.REST_MATCH_PATTERN) {
                addInvalidNodeToNextToken(firstArg, DiagnosticErrorCode.ERROR_MATCH_PATTERN_NOT_ALLOWED);
            } else {
                argListMatchPatterns.add(firstArg);
            }
        }

        parseErrorFieldMatchPatterns(argListMatchPatterns);
        return STNodeFactory.createNodeList(argListMatchPatterns);
    }

    private boolean isSimpleMatchPattern(SyntaxKind matchPatternKind) {
        switch (matchPatternKind) {
            case IDENTIFIER_TOKEN:
            case SIMPLE_NAME_REFERENCE:
            case QUALIFIED_NAME_REFERENCE:
            case NUMERIC_LITERAL:
            case STRING_LITERAL:
            case NULL_LITERAL:
            case NIL_LITERAL:
            case BOOLEAN_LITERAL:
            case TYPED_BINDING_PATTERN:
            case UNARY_EXPRESSION:
                return true;
            default:
                return false;
        }
    }

    private boolean isValidSecondArgMatchPattern(SyntaxKind syntaxKind) {
        switch (syntaxKind) {
            case ERROR_MATCH_PATTERN:
            case NAMED_ARG_MATCH_PATTERN:
            case REST_MATCH_PATTERN:
                return true;
            default:
                if (isSimpleMatchPattern(syntaxKind)) {
                    return true;
                }
                return false;
        }
    }

    /**
     * Parse error field match patterns.
     * error-field-match-patterns :=
     * named-arg-match-pattern (, named-arg-match-pattern)* [, rest-match-pattern]
     * | rest-match-pattern
     * named-arg-match-pattern := arg-name = match-pattern
     * @param argListMatchPatterns
     */
    private void parseErrorFieldMatchPatterns(List<STNode> argListMatchPatterns) {
        SyntaxKind lastValidArgKind = SyntaxKind.NAMED_ARG_MATCH_PATTERN;
        while (!isEndOfErrorFieldMatchPatterns()) {
            STNode argEnd = parseErrorArgListMatchPatternEnd(ParserRuleContext.ERROR_FIELD_MATCH_PATTERN_RHS);
            if (argEnd == null) {
                
                break;
            }
            STNode currentArg = parseErrorArgListMatchPattern(ParserRuleContext.ERROR_FIELD_MATCH_PATTERN);
            DiagnosticErrorCode errorCode = validateErrorFieldMatchPatternOrder(lastValidArgKind, currentArg.kind);
            if (errorCode == null) {
                argListMatchPatterns.add(argEnd);
                argListMatchPatterns.add(currentArg);
                lastValidArgKind = currentArg.kind;
            } else if (argListMatchPatterns.size() == 0) {
                addInvalidNodeToNextToken(argEnd, null);
                addInvalidNodeToNextToken(currentArg, errorCode);
            } else {
                updateLastNodeInListWithInvalidNode(argListMatchPatterns, argEnd, null);
                updateLastNodeInListWithInvalidNode(argListMatchPatterns, currentArg, errorCode);
            }
        }
    }

    private boolean isEndOfErrorFieldMatchPatterns() {
        
        return isEndOfErrorFieldBindingPatterns();
    }

    private STNode parseErrorArgListMatchPatternEnd(ParserRuleContext currentCtx) {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return consume();
            case CLOSE_PAREN_TOKEN:
                return null;
            default:
                recover(peek(), currentCtx);
                return parseErrorArgListMatchPatternEnd(currentCtx);
        }
    }

    private STNode parseErrorArgListMatchPattern(ParserRuleContext context) {
        STToken nextToken = peek();
        if (isPredeclaredIdentifier(nextToken.kind)) {
            return parseNamedArgOrSimpleMatchPattern();
        }

        switch (nextToken.kind) {
            case ELLIPSIS_TOKEN:
                return parseRestMatchPattern();
            case OPEN_PAREN_TOKEN:
            case NULL_KEYWORD:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case PLUS_TOKEN:
            case MINUS_TOKEN:
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
            case STRING_LITERAL_TOKEN:
            case OPEN_BRACKET_TOKEN:
            case OPEN_BRACE_TOKEN:
            case ERROR_KEYWORD:
                return parseMatchPattern();
            case VAR_KEYWORD:
                STNode varType = createBuiltinSimpleNameReference(consume());
                STNode variableName = createCaptureOrWildcardBP(parseVariableName());
                return STNodeFactory.createTypedBindingPatternNode(varType, variableName);
            case CLOSE_PAREN_TOKEN:
                return SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                        DiagnosticErrorCode.ERROR_MISSING_MATCH_PATTERN);
            default:
                recover(nextToken, context);
                return parseErrorArgListMatchPattern(context);
        }
    }

    private STNode parseNamedArgOrSimpleMatchPattern() {
        STNode constRefExpr = parseQualifiedIdentifier(ParserRuleContext.MATCH_PATTERN);
        if (constRefExpr.kind == SyntaxKind.QUALIFIED_NAME_REFERENCE || peek().kind != SyntaxKind.EQUAL_TOKEN) {
            return constRefExpr;
        }

        
        return parseNamedArgMatchPattern(((STSimpleNameReferenceNode) constRefExpr).name);
    }

    /**
     * Parses the next named arg match pattern.
     * <br/>
     * <code>named-arg-match-pattern := arg-name = match-pattern</code>
     * <br/>
     * <br/>
     *
     * @return arg match pattern list node added the new arg match pattern
     */
    private STNode parseNamedArgMatchPattern(STNode identifier) {
        startContext(ParserRuleContext.NAMED_ARG_MATCH_PATTERN);
        STNode equalToken = parseAssignOp();
        STNode matchPattern = parseMatchPattern();
        endContext();
        return STNodeFactory.createNamedArgMatchPatternNode(identifier, equalToken, matchPattern);
    }

    private DiagnosticErrorCode validateErrorFieldMatchPatternOrder(SyntaxKind prevArgKind, SyntaxKind currentArgKind) {
        switch (currentArgKind) {
            case NAMED_ARG_MATCH_PATTERN:
            case REST_MATCH_PATTERN:
                
                if (prevArgKind == SyntaxKind.REST_MATCH_PATTERN) {
                    return DiagnosticErrorCode.ERROR_REST_ARG_FOLLOWED_BY_ANOTHER_ARG;
                }
                return null;
            default:
                return DiagnosticErrorCode.ERROR_MATCH_PATTERN_NOT_ALLOWED;
        }
    }

    /**
     * Parse markdown documentation.
     *
     * @return markdown documentation node
     */
    private STNode parseMarkdownDocumentation() {
        List<STNode> markdownDocLineList = new ArrayList<>();

        
        
        
        
        
        STToken nextToken = peek();
        while (nextToken.kind == SyntaxKind.DOCUMENTATION_STRING) {
            STToken documentationString = consume();
            STNode parsedDocLines = parseDocumentationString(documentationString);
            appendParsedDocumentationLines(markdownDocLineList, parsedDocLines);
            nextToken = peek();
        }

        STNode markdownDocLines = STNodeFactory.createNodeList(markdownDocLineList);
        return STNodeFactory.createMarkdownDocumentationNode(markdownDocLines);
    }

    /**
     * Parse documentation string.
     *
     * @return markdown documentation line list node
     */
    private STNode parseDocumentationString(STToken documentationStringToken) {
        List<STNode> leadingTriviaList = getLeadingTriviaList(documentationStringToken.leadingMinutiae());
        Collection<STNodeDiagnostic> diagnostics = new ArrayList<>((documentationStringToken.diagnostics()));

        CharReader charReader = CharReader.from(documentationStringToken.text());
        DocumentationLexer documentationLexer = new DocumentationLexer(charReader, leadingTriviaList, diagnostics);
        AbstractTokenReader tokenReader = new TokenReader(documentationLexer);
        DocumentationParser documentationParser = new DocumentationParser(tokenReader);
        return documentationParser.parse();
    }

    private List<STNode> getLeadingTriviaList(STNode leadingMinutiaeNode) {
        List<STNode> leadingTriviaList = new ArrayList<>();
        int bucketCount = leadingMinutiaeNode.bucketCount();
        for (int i = 0; i < bucketCount; i++) {
            leadingTriviaList.add(leadingMinutiaeNode.childInBucket(i));
        }

        return leadingTriviaList;
    }

    private void appendParsedDocumentationLines(List<STNode> markdownDocLineList, STNode parsedDocLines) {
        int bucketCount = parsedDocLines.bucketCount();
        for (int i = 0; i < bucketCount; i++) {
            STNode markdownDocLine = parsedDocLines.childInBucket(i);
            markdownDocLineList.add(markdownDocLine);
        }
    }

    

    /**
     * Parse any statement that starts with a token that has ambiguity between being
     * a type-desc or an expression.
     *
     * @param annots Annotations
     * @param qualifiers
     * @return Statement node
     */
    private STNode parseStmtStartsWithTypeOrExpr(STNode annots, List<STNode> qualifiers) {
        startContext(ParserRuleContext.AMBIGUOUS_STMT);
        STNode typeOrExpr = parseTypedBindingPatternOrExpr(qualifiers, true);
        return parseStmtStartsWithTypedBPOrExprRhs(annots, typeOrExpr);
    }

    private STNode parseStmtStartsWithTypedBPOrExprRhs(STNode annots, STNode typedBindingPatternOrExpr) {
        if (typedBindingPatternOrExpr.kind == SyntaxKind.TYPED_BINDING_PATTERN) {
            List<STNode> varDeclQualifiers = new ArrayList<>();
            switchContext(ParserRuleContext.VAR_DECL_STMT);
            return parseVarDeclRhs(annots, varDeclQualifiers, typedBindingPatternOrExpr, false);
        }

        STNode expr = getExpression(typedBindingPatternOrExpr);
        expr = getExpression(parseExpressionRhs(DEFAULT_OP_PRECEDENCE, expr, false, true));
        return parseStatementStartWithExprRhs(expr);
    }

    private STNode parseTypedBindingPatternOrExpr(boolean allowAssignment) {
        List<STNode> typeDescQualifiers = new ArrayList<>();
        return parseTypedBindingPatternOrExpr(typeDescQualifiers, allowAssignment);
    }

    private STNode parseTypedBindingPatternOrExpr(List<STNode> qualifiers, boolean allowAssignment) {
        parseTypeDescQualifiers(qualifiers);
        STToken nextToken = peek();
        STNode typeOrExpr;
        if (isPredeclaredIdentifier(nextToken.kind)) {
            reportInvalidQualifierList(qualifiers);
            typeOrExpr = parseQualifiedIdentifier(ParserRuleContext.TYPE_NAME_OR_VAR_NAME);
            return parseTypedBindingPatternOrExprRhs(typeOrExpr, allowAssignment);
        }
        
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                reportInvalidQualifierList(qualifiers);
                return parseTypedBPOrExprStartsWithOpenParenthesis();
            case FUNCTION_KEYWORD:
                return parseAnonFuncExprOrTypedBPWithFuncType(qualifiers);
            case OPEN_BRACKET_TOKEN:
                reportInvalidQualifierList(qualifiers);
                typeOrExpr = parseTupleTypeDescOrListConstructor(STNodeFactory.createEmptyNodeList());
                return parseTypedBindingPatternOrExprRhs(typeOrExpr, allowAssignment);
            
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case STRING_LITERAL_TOKEN:
            case NULL_KEYWORD:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
                reportInvalidQualifierList(qualifiers);
                STNode basicLiteral = parseBasicLiteral();
                return parseTypedBindingPatternOrExprRhs(basicLiteral, allowAssignment);
            default:
                if (isValidExpressionStart(nextToken.kind, 1)) {
                    reportInvalidQualifierList(qualifiers);
                    return parseActionOrExpressionInLhs(STNodeFactory.createEmptyNodeList());
                }

                return parseTypedBindingPattern(qualifiers, ParserRuleContext.VAR_DECL_STMT);
        }
    }

    /**
     * Parse the component after the ambiguous starting node. Ambiguous node could be either an expr
     * or a type-desc. The component followed by this ambiguous node could be the binding-pattern or
     * the expression-rhs.
     *
     * @param typeOrExpr      Type desc or the expression
     * @param allowAssignment Flag indicating whether to allow assignment. i.e.: whether this is a
     *                        valid lvalue expression
     * @return Typed-binding-pattern node or an expression node
     */
    private STNode parseTypedBindingPatternOrExprRhs(STNode typeOrExpr, boolean allowAssignment) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case PIPE_TOKEN:
            case BITWISE_AND_TOKEN:
                STToken nextNextToken = peek(2);
                if (nextNextToken.kind == SyntaxKind.EQUAL_TOKEN) {
                    return typeOrExpr;
                }

                STNode pipeOrAndToken = parseBinaryOperator();
                STNode rhsTypedBPOrExpr = parseTypedBindingPatternOrExpr(allowAssignment);
                if (rhsTypedBPOrExpr.kind == SyntaxKind.TYPED_BINDING_PATTERN) {
                    STTypedBindingPatternNode typedBP = (STTypedBindingPatternNode) rhsTypedBPOrExpr;
                    typeOrExpr = getTypeDescFromExpr(typeOrExpr);

                    STNode newTypeDesc = mergeTypes(typeOrExpr, pipeOrAndToken, typedBP.typeDescriptor);
                    return STNodeFactory.createTypedBindingPatternNode(newTypeDesc, typedBP.bindingPattern);
                }

                
                if (peek().kind == SyntaxKind.EQUAL_TOKEN) {
                    return createCaptureBPWithMissingVarName(typeOrExpr, pipeOrAndToken, rhsTypedBPOrExpr);
                }

                return STNodeFactory.createBinaryExpressionNode(SyntaxKind.BINARY_EXPRESSION, typeOrExpr,
                        pipeOrAndToken, rhsTypedBPOrExpr);
            case SEMICOLON_TOKEN:
                if (isExpression(typeOrExpr.kind)) {
                    return typeOrExpr;
                }

                if (isDefiniteTypeDesc(typeOrExpr.kind) || !isAllBasicLiterals(typeOrExpr)) {
                    
                    STNode typeDesc = getTypeDescFromExpr(typeOrExpr);
                    return parseTypeBindingPatternStartsWithAmbiguousNode(typeDesc);
                }

                return typeOrExpr;
            case IDENTIFIER_TOKEN:
            case QUESTION_MARK_TOKEN:
                if (isAmbiguous(typeOrExpr) || isDefiniteTypeDesc(typeOrExpr.kind)) {
                    
                    STNode typeDesc = getTypeDescFromExpr(typeOrExpr);
                    return parseTypeBindingPatternStartsWithAmbiguousNode(typeDesc);
                }

                return typeOrExpr;
            case EQUAL_TOKEN:
                return typeOrExpr;
            case OPEN_BRACKET_TOKEN:
                return parseTypedBindingPatternOrMemberAccess(typeOrExpr, false, allowAssignment,
                        ParserRuleContext.AMBIGUOUS_STMT);
            case OPEN_BRACE_TOKEN: 
            case ERROR_KEYWORD: 
                STNode typeDesc = getTypeDescFromExpr(typeOrExpr);
                return parseTypeBindingPatternStartsWithAmbiguousNode(typeDesc);
            default:
                
                if (isCompoundAssignment(nextToken.kind)) {
                    return typeOrExpr;
                }

                
                
                if (isValidExprRhsStart(nextToken.kind, typeOrExpr.kind)) {
                    return typeOrExpr;
                }

                STToken token = peek();
                SyntaxKind typeOrExprKind = typeOrExpr.kind;
                if (typeOrExprKind == SyntaxKind.QUALIFIED_NAME_REFERENCE ||
                        typeOrExprKind == SyntaxKind.SIMPLE_NAME_REFERENCE) {
                    recover(token, ParserRuleContext.BINDING_PATTERN_OR_VAR_REF_RHS);
                } else {
                    recover(token, ParserRuleContext.BINDING_PATTERN_OR_EXPR_RHS);
                }

                return parseTypedBindingPatternOrExprRhs(typeOrExpr, allowAssignment);
        }
    }

    private STNode createCaptureBPWithMissingVarName(STNode lhsType, STNode separatorToken, STNode rhsType) {
        lhsType = getTypeDescFromExpr(lhsType);
        rhsType = getTypeDescFromExpr(rhsType);

        STNode newTypeDesc = mergeTypes(lhsType, separatorToken, rhsType);

        STNode identifier = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                ParserRuleContext.VARIABLE_NAME);
        STNode captureBP = STNodeFactory.createCaptureBindingPatternNode(identifier);

        return STNodeFactory.createTypedBindingPatternNode(newTypeDesc, captureBP);
    }

    private STNode parseTypeBindingPatternStartsWithAmbiguousNode(STNode typeDesc) {
        

        
        
        typeDesc = parseComplexTypeDescriptor(typeDesc, ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true);
        return parseTypedBindingPatternTypeRhs(typeDesc, ParserRuleContext.VAR_DECL_STMT);
    }

    private STNode parseTypedBPOrExprStartsWithOpenParenthesis() {
        STNode exprOrTypeDesc = parseTypedDescOrExprStartsWithOpenParenthesis();
        if (isDefiniteTypeDesc(exprOrTypeDesc.kind)) {
            return parseTypeBindingPatternStartsWithAmbiguousNode(exprOrTypeDesc);
        }

        return parseTypedBindingPatternOrExprRhs(exprOrTypeDesc, false);
    }

    private boolean isDefiniteTypeDesc(SyntaxKind kind) {
        return kind.compareTo(SyntaxKind.RECORD_TYPE_DESC) >= 0 && kind.compareTo(SyntaxKind.FUTURE_TYPE_DESC) <= 0;
    }

    private boolean isDefiniteExpr(SyntaxKind kind) {
        if (kind == SyntaxKind.QUALIFIED_NAME_REFERENCE || kind == SyntaxKind.SIMPLE_NAME_REFERENCE) {
            return false;
        }

        return kind.compareTo(SyntaxKind.BINARY_EXPRESSION) >= 0 && kind.compareTo(SyntaxKind.ERROR_CONSTRUCTOR) <= 0;
    }

    private boolean isDefiniteAction(SyntaxKind kind) {
        return kind.compareTo(SyntaxKind.REMOTE_METHOD_CALL_ACTION) >= 0 && 
                kind.compareTo(SyntaxKind.CLIENT_RESOURCE_ACCESS_ACTION) <= 0;
    }

    /**
     * Parse type or expression that starts with open parenthesis. Possible options are:
     * 1) () - nil type-desc or nil-literal
     * 2) (T) - Parenthesized type-desc
     * 3) (expr) - Parenthesized expression
     * 4) (param, param, ..) - Anon function params
     *
     * @return Type-desc or expression node
     */
    private STNode parseTypedDescOrExprStartsWithOpenParenthesis() {
        STNode openParen = parseOpenParenthesis();
        STToken nextToken = peek();

        if (nextToken.kind == SyntaxKind.CLOSE_PAREN_TOKEN) {
            STNode closeParen = parseCloseParenthesis();
            return parseTypeOrExprStartWithEmptyParenthesis(openParen, closeParen);
        }

        STNode typeOrExpr = parseTypeDescOrExpr();
        if (isAction(typeOrExpr)) {
            STNode closeParen = parseCloseParenthesis();
            return STNodeFactory.createBracedExpressionNode(SyntaxKind.BRACED_ACTION, openParen, typeOrExpr,
                    closeParen);
        }

        if (isExpression(typeOrExpr.kind)) {
            startContext(ParserRuleContext.BRACED_EXPR_OR_ANON_FUNC_PARAMS);
            return parseBracedExprOrAnonFuncParamRhs(openParen, typeOrExpr, false);
        }

        STNode typeDescNode = getTypeDescFromExpr(typeOrExpr);
        typeDescNode = parseComplexTypeDescriptor(typeDescNode, ParserRuleContext.TYPE_DESC_IN_PARENTHESIS, false);

        STNode closeParen = parseCloseParenthesis();
        return STNodeFactory.createParenthesisedTypeDescriptorNode(openParen, typeDescNode, closeParen);
    }

    /**
     * Parse type-desc or expression. This method does not handle binding patterns.
     *
     * @return Type-desc node or expression node
     */
    private STNode parseTypeDescOrExpr() {
        List<STNode> typeDescQualifiers = new ArrayList<>();
        return parseTypeDescOrExpr(typeDescQualifiers);
    }

    private STNode parseTypeDescOrExpr(List<STNode> qualifiers) {
        parseTypeDescQualifiers(qualifiers);
        STToken nextToken = peek();
        STNode typeOrExpr;
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                reportInvalidQualifierList(qualifiers);
                typeOrExpr = parseTypedDescOrExprStartsWithOpenParenthesis();
                break;
            case FUNCTION_KEYWORD:
                typeOrExpr = parseAnonFuncExprOrFuncTypeDesc(qualifiers);
                break;
            case IDENTIFIER_TOKEN:
                reportInvalidQualifierList(qualifiers);
                typeOrExpr = parseQualifiedIdentifier(ParserRuleContext.TYPE_NAME_OR_VAR_NAME);
                return parseTypeDescOrExprRhs(typeOrExpr);
            case OPEN_BRACKET_TOKEN:
                reportInvalidQualifierList(qualifiers);
                typeOrExpr = parseTupleTypeDescOrListConstructor(STNodeFactory.createEmptyNodeList());
                break;
            
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case STRING_LITERAL_TOKEN:
            case NULL_KEYWORD:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
                reportInvalidQualifierList(qualifiers);
                STNode basicLiteral = parseBasicLiteral();
                return parseTypeDescOrExprRhs(basicLiteral);
            default:
                if (isValidExpressionStart(nextToken.kind, 1)) {
                    reportInvalidQualifierList(qualifiers);
                    return parseActionOrExpressionInLhs(STNodeFactory.createEmptyNodeList());
                }
                return parseTypeDescriptor(qualifiers, ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN);
        }

        if (isDefiniteTypeDesc(typeOrExpr.kind)) {
            return parseComplexTypeDescriptor(typeOrExpr, ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true);
        }

        return parseTypeDescOrExprRhs(typeOrExpr);
    }

    private boolean isExpression(SyntaxKind kind) {
        switch (kind) {
            case NUMERIC_LITERAL:
            case STRING_LITERAL_TOKEN:
            case NIL_LITERAL:
            case NULL_LITERAL:
            case BOOLEAN_LITERAL:
                return true;
            default:
                return kind.compareTo(SyntaxKind.BINARY_EXPRESSION) >= 0 &&
                        kind.compareTo(SyntaxKind.ERROR_CONSTRUCTOR) <= 0;
        }
    }

    /**
     * Parse statement that starts with an empty parenthesis. Empty parenthesis can be
     * 1) Nil literal
     * 2) Nil type-desc
     * 3) Anon-function params
     *
     * @param openParen  Open parenthesis
     * @param closeParen Close parenthesis
     * @return Parsed node
     */
    private STNode parseTypeOrExprStartWithEmptyParenthesis(STNode openParen, STNode closeParen) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case RIGHT_DOUBLE_ARROW_TOKEN:
                STNode params = STNodeFactory.createEmptyNodeList();
                STNode anonFuncParam =
                        STNodeFactory.createImplicitAnonymousFunctionParameters(openParen, params, closeParen);
                return parseImplicitAnonFunc(anonFuncParam, false);
            default:
                return STNodeFactory.createNilLiteralNode(openParen, closeParen);
        }
    }

    private STNode parseAnonFuncExprOrTypedBPWithFuncType(List<STNode> qualifiers) {
        STNode exprOrTypeDesc = parseAnonFuncExprOrFuncTypeDesc(qualifiers);
        if (isAction(exprOrTypeDesc) || isExpression(exprOrTypeDesc.kind)) {
            return exprOrTypeDesc;
        }

        return parseTypedBindingPatternTypeRhs(exprOrTypeDesc, ParserRuleContext.VAR_DECL_STMT);
    }

    /**
     * Parse anon-func-expr or function-type-desc, by resolving the ambiguity.
     *
     * @param qualifiers Preceding qualifiers
     * @return Anon-func-expr or function-type-desc
     */
    private STNode parseAnonFuncExprOrFuncTypeDesc(List<STNode> qualifiers) {
        startContext(ParserRuleContext.FUNC_TYPE_DESC_OR_ANON_FUNC);
        STNode qualifierList;
        STNode functionKeyword = parseFunctionKeyword();
        STNode funcSignature;

        if (peek().kind == SyntaxKind.OPEN_PAREN_TOKEN) {
            funcSignature = parseFuncSignature(true);

            
            STNode[] nodes = createFuncTypeQualNodeList(qualifiers, functionKeyword, true);
            qualifierList = nodes[0];
            functionKeyword = nodes[1];

            endContext();
            return parseAnonFuncExprOrFuncTypeDesc(qualifierList, functionKeyword, funcSignature);
        }

        funcSignature = STNodeFactory.createEmptyNode();

        
        STNode[] nodes = createFuncTypeQualNodeList(qualifiers, functionKeyword, false);
        qualifierList = nodes[0];
        functionKeyword = nodes[1];

        STNode funcTypeDesc = STNodeFactory.createFunctionTypeDescriptorNode(qualifierList, functionKeyword,
                                                                             funcSignature);
        if (getCurrentContext() != ParserRuleContext.STMT_START_BRACKETED_LIST) {
            switchContext(ParserRuleContext.VAR_DECL_STMT);
            return parseComplexTypeDescriptor(funcTypeDesc, ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true);
        }
        return parseComplexTypeDescriptor(funcTypeDesc, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
    }

    private STNode parseAnonFuncExprOrFuncTypeDesc(STNode qualifierList, STNode functionKeyword, STNode funcSignature) {
        ParserRuleContext currentCtx = getCurrentContext();
        switch (peek().kind) {
            case OPEN_BRACE_TOKEN:
            case RIGHT_DOUBLE_ARROW_TOKEN:
                if (currentCtx != ParserRuleContext.STMT_START_BRACKETED_LIST) {
                    switchContext(ParserRuleContext.EXPRESSION_STATEMENT);
                }
                startContext(ParserRuleContext.ANON_FUNC_EXPRESSION);
                
                funcSignature = validateAndGetFuncParams((STFunctionSignatureNode) funcSignature);

                STNode funcBody = parseAnonFuncBody(false);
                STNode annots = STNodeFactory.createEmptyNodeList();
                STNode anonFunc = STNodeFactory.createExplicitAnonymousFunctionExpressionNode(annots, qualifierList,
                        functionKeyword, funcSignature, funcBody);
                return parseExpressionRhs(DEFAULT_OP_PRECEDENCE, anonFunc, false, true);
            case IDENTIFIER_TOKEN:
            default:
                STNode funcTypeDesc = STNodeFactory.createFunctionTypeDescriptorNode(qualifierList, functionKeyword,
                        funcSignature);
                if (currentCtx != ParserRuleContext.STMT_START_BRACKETED_LIST) {
                    switchContext(ParserRuleContext.VAR_DECL_STMT);
                    return parseComplexTypeDescriptor(funcTypeDesc, ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN,
                            true);
                }
                return parseComplexTypeDescriptor(funcTypeDesc, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
        }
    }

    private STNode parseTypeDescOrExprRhs(STNode typeOrExpr) {
        STToken nextToken = peek();
        STNode typeDesc;
        switch (nextToken.kind) {
            case PIPE_TOKEN:
            case BITWISE_AND_TOKEN:
                STToken nextNextToken = peek(2);
                if (nextNextToken.kind == SyntaxKind.EQUAL_TOKEN) {
                    return typeOrExpr;
                }

                STNode pipeOrAndToken = parseBinaryOperator();
                STNode rhsTypeDescOrExpr = parseTypeDescOrExpr();
                if (isExpression(rhsTypeDescOrExpr.kind)) {
                    return STNodeFactory.createBinaryExpressionNode(SyntaxKind.BINARY_EXPRESSION, typeOrExpr,
                            pipeOrAndToken, rhsTypeDescOrExpr);
                }

                typeDesc = getTypeDescFromExpr(typeOrExpr);
                rhsTypeDescOrExpr = getTypeDescFromExpr(rhsTypeDescOrExpr);
                return mergeTypes(typeDesc, pipeOrAndToken, rhsTypeDescOrExpr);
            case IDENTIFIER_TOKEN:
            case QUESTION_MARK_TOKEN:
                
                
                
                typeDesc = parseComplexTypeDescriptor(getTypeDescFromExpr(typeOrExpr), 
                        ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, false);
                return typeDesc;
            case SEMICOLON_TOKEN:
                return getTypeDescFromExpr(typeOrExpr);
            case EQUAL_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case CLOSE_BRACKET_TOKEN:
            case EOF_TOKEN:
            case COMMA_TOKEN:
                return typeOrExpr;
            case OPEN_BRACKET_TOKEN:
                return parseTypedBindingPatternOrMemberAccess(typeOrExpr, false, true,
                        ParserRuleContext.AMBIGUOUS_STMT);
            case ELLIPSIS_TOKEN:
                STNode ellipsis = parseEllipsis();
                typeOrExpr = getTypeDescFromExpr(typeOrExpr);
                return STNodeFactory.createRestDescriptorNode(typeOrExpr, ellipsis);
            default:
                
                if (isCompoundAssignment(nextToken.kind)) {
                    return typeOrExpr;
                }

                
                
                if (isValidExprRhsStart(nextToken.kind, typeOrExpr.kind)) {
                    return parseExpressionRhs(DEFAULT_OP_PRECEDENCE, typeOrExpr, false, false, false, false);
                }

                recover(peek(), ParserRuleContext.TYPE_DESC_OR_EXPR_RHS);
                return parseTypeDescOrExprRhs(typeOrExpr);
        }
    }

    private boolean isAmbiguous(STNode node) {
        switch (node.kind) {
            case SIMPLE_NAME_REFERENCE:
            case QUALIFIED_NAME_REFERENCE:
            case NIL_LITERAL:
            case NULL_LITERAL:
            case NUMERIC_LITERAL:
            case STRING_LITERAL:
            case BOOLEAN_LITERAL:
            case BRACKETED_LIST:
                return true;
            case BINARY_EXPRESSION:
                STBinaryExpressionNode binaryExpr = (STBinaryExpressionNode) node;
                if (binaryExpr.operator.kind != SyntaxKind.PIPE_TOKEN ||
                        binaryExpr.operator.kind == SyntaxKind.BITWISE_AND_TOKEN) {
                    return false;
                }
                return isAmbiguous(binaryExpr.lhsExpr) && isAmbiguous(binaryExpr.rhsExpr);
            case BRACED_EXPRESSION:
                return isAmbiguous(((STBracedExpressionNode) node).expression);
            case INDEXED_EXPRESSION:
                STIndexedExpressionNode indexExpr = (STIndexedExpressionNode) node;
                if (!isAmbiguous(indexExpr.containerExpression)) {
                    return false;
                }

                STNode keys = indexExpr.keyExpression;
                for (int i = 0; i < keys.bucketCount(); i++) {
                    STNode item = keys.childInBucket(i);
                    if (item.kind == SyntaxKind.COMMA_TOKEN) {
                        continue;
                    }

                    if (!isAmbiguous(item)) {
                        return false;
                    }
                }
                return true;
            default:
                return false;
        }
    }

    private boolean isAllBasicLiterals(STNode node) {
        switch (node.kind) {
            case NIL_LITERAL:
            case NULL_LITERAL:
            case NUMERIC_LITERAL:
            case STRING_LITERAL:
            case BOOLEAN_LITERAL:
                return true;
            case BINARY_EXPRESSION:
                STBinaryExpressionNode binaryExpr = (STBinaryExpressionNode) node;
                if (binaryExpr.operator.kind != SyntaxKind.PIPE_TOKEN ||
                        binaryExpr.operator.kind == SyntaxKind.BITWISE_AND_TOKEN) {
                    return false;
                }
                return isAmbiguous(binaryExpr.lhsExpr) && isAmbiguous(binaryExpr.rhsExpr);
            case BRACED_EXPRESSION:
                return isAmbiguous(((STBracedExpressionNode) node).expression);
            case BRACKETED_LIST:
                STAmbiguousCollectionNode list = (STAmbiguousCollectionNode) node;
                for (STNode member : list.members) {
                    if (member.kind == SyntaxKind.COMMA_TOKEN) {
                        continue;
                    }

                    if (!isAllBasicLiterals(member)) {
                        return false;
                    }
                }

                return true;
            case UNARY_EXPRESSION:
                STUnaryExpressionNode unaryExpr = (STUnaryExpressionNode) node;
                if (unaryExpr.unaryOperator.kind != SyntaxKind.PLUS_TOKEN &&
                        unaryExpr.unaryOperator.kind != SyntaxKind.MINUS_TOKEN) {
                    return false;
                }

                return isNumericLiteral(unaryExpr.expression);
            default:
                return false;
        }
    }

    private boolean isNumericLiteral(STNode node) {
        switch (node.kind) {
            case NUMERIC_LITERAL:
                return true;
            default:
                return false;
        }
    }

    

    /**
     * Parse binding-patterns.
     * <p>
     * <code>
     * binding-pattern := capture-binding-pattern
     * | wildcard-binding-pattern
     * | list-binding-pattern
     * | mapping-binding-pattern
     * | functional-binding-pattern
     * <br/><br/>
     * <p>
     * capture-binding-pattern := variable-name
     * variable-name := identifier
     * <br/><br/>
     * <p>
     * wildcard-binding-pattern := _
     * list-binding-pattern := [ list-member-binding-patterns ]
     * <br/>
     * list-member-binding-patterns := binding-pattern (, binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * <br/><br/>
     * <p>
     * mapping-binding-pattern := { field-binding-patterns }
     * field-binding-patterns := field-binding-pattern (, field-binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * <br/>
     * field-binding-pattern := field-name : binding-pattern | variable-name
     * <br/>
     * rest-binding-pattern := ... variable-name
     * <p>
     * <br/><br/>
     * functional-binding-pattern := functionally-constructible-type-reference ( arg-list-binding-pattern )
     * <br/>
     * arg-list-binding-pattern := positional-arg-binding-patterns [, other-arg-binding-patterns]
     * | other-arg-binding-patterns
     * <br/>
     * positional-arg-binding-patterns := positional-arg-binding-pattern (, positional-arg-binding-pattern)*
     * <br/>
     * positional-arg-binding-pattern := binding-pattern
     * <br/>
     * other-arg-binding-patterns := named-arg-binding-patterns [, rest-binding-pattern]
     * | [rest-binding-pattern]
     * <br/>
     * named-arg-binding-patterns := named-arg-binding-pattern (, named-arg-binding-pattern)*
     * <br/>
     * named-arg-binding-pattern := arg-name = binding-pattern
     * </code>
     *
     * @return binding-pattern node
     */
    private STNode parseBindingPattern() {
        switch (peek().kind) {
            case OPEN_BRACKET_TOKEN:
                return parseListBindingPattern();
            case IDENTIFIER_TOKEN:
                return parseBindingPatternStartsWithIdentifier();
            case OPEN_BRACE_TOKEN:
                return parseMappingBindingPattern();
            case ERROR_KEYWORD:
                return parseErrorBindingPattern();
            default:
                recover(peek(), ParserRuleContext.BINDING_PATTERN);
                return parseBindingPattern();
        }
    }

    private STNode parseBindingPatternStartsWithIdentifier() {
        STNode argNameOrBindingPattern =
                parseQualifiedIdentifier(ParserRuleContext.BINDING_PATTERN_STARTING_IDENTIFIER);
        STToken secondToken = peek();
        if (secondToken.kind == SyntaxKind.OPEN_PAREN_TOKEN) {
            startContext(ParserRuleContext.ERROR_BINDING_PATTERN);
            STNode errorKeyword = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.ERROR_KEYWORD,
                    ParserRuleContext.ERROR_KEYWORD);
            return parseErrorBindingPattern(errorKeyword, argNameOrBindingPattern);
        }

        if (argNameOrBindingPattern.kind != SyntaxKind.SIMPLE_NAME_REFERENCE) {
            STNode identifier = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
            identifier = SyntaxErrors.cloneWithLeadingInvalidNodeMinutiae(identifier, argNameOrBindingPattern,
                    DiagnosticErrorCode.ERROR_FIELD_BP_INSIDE_LIST_BP);
            return STNodeFactory.createCaptureBindingPatternNode(identifier);
        }

        return createCaptureOrWildcardBP(((STSimpleNameReferenceNode) argNameOrBindingPattern).name);
    }

    private STNode createCaptureOrWildcardBP(STNode varName) {
        STNode bindingPattern;
        if (isWildcardBP(varName)) {
            bindingPattern = getWildcardBindingPattern(varName);
        } else {
            bindingPattern = STNodeFactory.createCaptureBindingPatternNode(varName);
        }
        return bindingPattern;
    }

    /**
     * Parse list-binding-patterns.
     * <p>
     * <code>
     * list-binding-pattern := [ list-member-binding-patterns ]
     * <br/>
     * list-member-binding-patterns := binding-pattern (, binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * </code>
     *
     * @return list-binding-pattern node
     */
    private STNode parseListBindingPattern() {
        startContext(ParserRuleContext.LIST_BINDING_PATTERN);
        STNode openBracket = parseOpenBracket();
        List<STNode> bindingPatternsList = new ArrayList<>();
        STNode listBindingPattern = parseListBindingPattern(openBracket, bindingPatternsList);
        endContext();
        return listBindingPattern;
    }

    private STNode parseListBindingPattern(STNode openBracket, List<STNode> bindingPatternsList) {
        if (isEndOfListBindingPattern(peek().kind) && bindingPatternsList.size() == 0) {
            
            STNode closeBracket = parseCloseBracket();
            STNode bindingPatternsNode = STNodeFactory.createNodeList(bindingPatternsList);
            return STNodeFactory.createListBindingPatternNode(openBracket, bindingPatternsNode, closeBracket);
        }
        STNode listBindingPatternMember = parseListBindingPatternMember();
        bindingPatternsList.add(listBindingPatternMember);
        STNode listBindingPattern = parseListBindingPattern(openBracket, listBindingPatternMember, bindingPatternsList);
        return listBindingPattern;
    }

    private STNode parseListBindingPattern(STNode openBracket, STNode firstMember, List<STNode> bindingPatterns) {
        STNode member = firstMember;
        
        STToken token = peek(); 
        STNode listBindingPatternRhs = null;
        while (!isEndOfListBindingPattern(token.kind) && member.kind != SyntaxKind.REST_BINDING_PATTERN) {
            listBindingPatternRhs = parseListBindingPatternMemberRhs();
            if (listBindingPatternRhs == null) {
                break;
            }

            bindingPatterns.add(listBindingPatternRhs);
            member = parseListBindingPatternMember();
            bindingPatterns.add(member);
            token = peek();
        }

        STNode closeBracket = parseCloseBracket();
        STNode bindingPatternsNode = STNodeFactory.createNodeList(bindingPatterns);
        return STNodeFactory.createListBindingPatternNode(openBracket, bindingPatternsNode, closeBracket);
    }

    private STNode parseListBindingPatternMemberRhs() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACKET_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.LIST_BINDING_PATTERN_MEMBER_END);
                return parseListBindingPatternMemberRhs();
        }
    }

    private boolean isEndOfListBindingPattern(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case CLOSE_BRACKET_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse list-binding-pattern member.
     * <p>
     * <code>
     * list-binding-pattern := [ list-member-binding-patterns ]
     * <br/>
     * list-member-binding-patterns := binding-pattern (, binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * </code>
     *
     * @return List binding pattern member
     */
    private STNode parseListBindingPatternMember() {
        switch (peek().kind) {
            case ELLIPSIS_TOKEN:
                return parseRestBindingPattern();
            case OPEN_BRACKET_TOKEN:
            case IDENTIFIER_TOKEN:
            case OPEN_BRACE_TOKEN:
            case ERROR_KEYWORD:
                return parseBindingPattern();
            default:
                recover(peek(), ParserRuleContext.LIST_BINDING_PATTERN_MEMBER);
                return parseListBindingPatternMember();
        }
    }

    /**
     * Parse rest binding pattern.
     * <p>
     * <code>
     * rest-binding-pattern := ... variable-name
     * </code>
     *
     * @return Rest binding pattern node
     */
    private STNode parseRestBindingPattern() {
        startContext(ParserRuleContext.REST_BINDING_PATTERN);
        STNode ellipsis = parseEllipsis();
        STNode varName = parseVariableName();
        endContext();

        STSimpleNameReferenceNode simpleNameReferenceNode =
                (STSimpleNameReferenceNode) STNodeFactory.createSimpleNameReferenceNode(varName);
        return STNodeFactory.createRestBindingPatternNode(ellipsis, simpleNameReferenceNode);
    }

    /**
     * Parse Typed-binding-pattern.
     * <p>
     * <code>
     * typed-binding-pattern := inferable-type-descriptor binding-pattern
     * <br/><br/>
     * inferable-type-descriptor := type-descriptor | var
     * </code>
     *
     * @return Typed binding pattern node
     */
    private STNode parseTypedBindingPattern(ParserRuleContext context) {
        List<STNode> typeDescQualifiers = new ArrayList<>();
        return parseTypedBindingPattern(typeDescQualifiers, context);
    }

    private STNode parseTypedBindingPattern(List<STNode> qualifiers, ParserRuleContext context) {
        STNode typeDesc = parseTypeDescriptor(qualifiers,
                ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true, false, TypePrecedence.DEFAULT);
        STNode typeBindingPattern = parseTypedBindingPatternTypeRhs(typeDesc, context);
        return typeBindingPattern;
    }

    /**
     * Parse mapping-binding-patterns.
     * <p>
     * <code>
     * mapping-binding-pattern := { field-binding-patterns }
     * <br/><br/>
     * field-binding-patterns := field-binding-pattern (, field-binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * <br/><br/>
     * field-binding-pattern := field-name : binding-pattern | variable-name
     * </code>
     *
     * @return mapping-binding-pattern node
     */
    private STNode parseMappingBindingPattern() {
        startContext(ParserRuleContext.MAPPING_BINDING_PATTERN);
        STNode openBrace = parseOpenBrace();

        STToken token = peek();
        if (isEndOfMappingBindingPattern(token.kind)) {
            STNode closeBrace = parseCloseBrace();
            STNode bindingPatternsNode = STNodeFactory.createEmptyNodeList();
            endContext();
            return STNodeFactory.createMappingBindingPatternNode(openBrace, bindingPatternsNode, closeBrace);
        }

        List<STNode> bindingPatterns = new ArrayList<>();
        STNode prevMember = parseMappingBindingPatternMember();
        if (prevMember.kind != SyntaxKind.REST_BINDING_PATTERN) {
            bindingPatterns.add(prevMember);
        }
        return parseMappingBindingPattern(openBrace, bindingPatterns, prevMember);
    }

    private STNode parseMappingBindingPattern(STNode openBrace, List<STNode> bindingPatterns, STNode prevMember) {
        STToken token = peek(); 
        STNode mappingBindingPatternRhs = null;
        while (!isEndOfMappingBindingPattern(token.kind) && prevMember.kind != SyntaxKind.REST_BINDING_PATTERN) {
            mappingBindingPatternRhs = parseMappingBindingPatternEnd();
            if (mappingBindingPatternRhs == null) {
                break;
            }

            bindingPatterns.add(mappingBindingPatternRhs);
            prevMember = parseMappingBindingPatternMember();
            if (prevMember.kind == SyntaxKind.REST_BINDING_PATTERN) {
                break;
            }
            bindingPatterns.add(prevMember);
            token = peek();
        }

        if (prevMember.kind == SyntaxKind.REST_BINDING_PATTERN) {
            bindingPatterns.add(prevMember);
        }

        STNode closeBrace = parseCloseBrace();
        STNode bindingPatternsNode = STNodeFactory.createNodeList(bindingPatterns);
        endContext();
        return STNodeFactory.createMappingBindingPatternNode(openBrace, bindingPatternsNode, closeBrace);
    }

    /**
     * Parse mapping-binding-pattern entry.
     * <p>
     * <code>
     * mapping-binding-pattern := { field-binding-patterns }
     * <br/><br/>
     * field-binding-patterns := field-binding-pattern (, field-binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * <br/><br/>
     * field-binding-pattern := field-name : binding-pattern
     * | variable-name
     * </code>
     *
     * @return mapping-binding-pattern node
     */
    private STNode parseMappingBindingPatternMember() {
        STToken token = peek();
        switch (token.kind) {
            case ELLIPSIS_TOKEN:
                return parseRestBindingPattern();
            default:
                return parseFieldBindingPattern();
        }
    }

    private STNode parseMappingBindingPatternEnd() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACE_TOKEN:
                return null;
            default:
                recover(nextToken, ParserRuleContext.MAPPING_BINDING_PATTERN_END);
                return parseMappingBindingPatternEnd();
        }
    }

    /**
     * Parse field-binding-pattern.
     * <code>field-binding-pattern := field-name : binding-pattern | varname</code>
     *
     * @return field-binding-pattern node
     */
    private STNode parseFieldBindingPattern() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
                STNode identifier = parseIdentifier(ParserRuleContext.FIELD_BINDING_PATTERN_NAME);
                STNode simpleNameReference = STNodeFactory.createSimpleNameReferenceNode(identifier);
                return parseFieldBindingPattern(simpleNameReference);
            default:
                recover(nextToken, ParserRuleContext.FIELD_BINDING_PATTERN_NAME);
                return parseFieldBindingPattern();
        }
    }

    private STNode parseFieldBindingPattern(STNode simpleNameReference) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
            case CLOSE_BRACE_TOKEN:
                return STNodeFactory.createFieldBindingPatternVarnameNode(simpleNameReference);
            case COLON_TOKEN:
                STNode colon = parseColon();
                STNode bindingPattern = parseBindingPattern();
                return STNodeFactory.createFieldBindingPatternFullNode(simpleNameReference, colon, bindingPattern);
            default:
                recover(nextToken, ParserRuleContext.FIELD_BINDING_PATTERN_END);
                return parseFieldBindingPattern(simpleNameReference);
        }
    }

    private boolean isEndOfMappingBindingPattern(SyntaxKind nextTokenKind) {
        return nextTokenKind == SyntaxKind.CLOSE_BRACE_TOKEN || isEndOfModuleLevelNode(1);
    }

    private STNode parseErrorTypeDescOrErrorBP(STNode annots) {
        STToken nextNextToken = peek(2);
        switch (nextNextToken.kind) {
            case OPEN_PAREN_TOKEN:
                return parseAsErrorBindingPattern();
            case LT_TOKEN:
                return parseAsErrorTypeDesc(annots);
            case IDENTIFIER_TOKEN:
                
                SyntaxKind nextNextNextTokenKind = peek(3).kind;
                if (nextNextNextTokenKind == SyntaxKind.COLON_TOKEN ||
                        nextNextNextTokenKind == SyntaxKind.OPEN_PAREN_TOKEN) {
                    return parseAsErrorBindingPattern();
                }
                
            default:
                return parseAsErrorTypeDesc(annots);
        }
    }

    private STNode parseAsErrorBindingPattern() {
        startContext(ParserRuleContext.ASSIGNMENT_STMT);
        return parseAssignmentStmtRhs(parseErrorBindingPattern());
    }

    private STNode parseAsErrorTypeDesc(STNode annots) {
        STNode finalKeyword = STNodeFactory.createEmptyNode();
        return parseVariableDecl(getAnnotations(annots), finalKeyword);
    }

    /**
     * Parse error binding pattern node.
     * <p>
     * <code>error-binding-pattern := error [error-type-reference] ( error-arg-list-binding-pattern )</code>
     * <br/><br/>
     * error-arg-list-binding-pattern :=
     * error-message-binding-pattern [, error-cause-binding-pattern] [, error-field-binding-patterns]
     * | [error-field-binding-patterns]
     * <br/><br/>
     * error-message-binding-pattern := simple-binding-pattern
     * <br/><br/>
     * error-cause-binding-pattern := simple-binding-pattern | error-binding-pattern
     * <br/><br/>
     * simple-binding-pattern := capture-binding-pattern | wildcard-binding-pattern
     * <br/><br/>
     * error-field-binding-patterns :=
     * named-arg-binding-pattern (, named-arg-binding-pattern)* [, rest-binding-pattern]
     * | rest-binding-pattern
     * <br/><br/>
     * named-arg-binding-pattern := arg-name = binding-pattern
     *
     * @return Error binding pattern node.
     */
    private STNode parseErrorBindingPattern() {
        startContext(ParserRuleContext.ERROR_BINDING_PATTERN);
        STNode errorKeyword = parseErrorKeyword();
        return parseErrorBindingPattern(errorKeyword);
    }

    private STNode parseErrorBindingPattern(STNode errorKeyword) {
        STToken nextToken = peek();
        STNode typeRef;
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                typeRef = STNodeFactory.createEmptyNode();
                break;
            default:
                if (isPredeclaredIdentifier(nextToken.kind)) {
                    typeRef = parseTypeReference();
                    break;
                }
                recover(peek(), ParserRuleContext.ERROR_BINDING_PATTERN_ERROR_KEYWORD_RHS);
                return parseErrorBindingPattern(errorKeyword);
        }
        return parseErrorBindingPattern(errorKeyword, typeRef);
    }

    private STNode parseErrorBindingPattern(STNode errorKeyword, STNode typeRef) {
        STNode openParenthesis = parseOpenParenthesis();
        STNode argListBindingPatterns = parseErrorArgListBindingPatterns();
        STNode closeParenthesis = parseCloseParenthesis();
        endContext();
        return STNodeFactory.createErrorBindingPatternNode(errorKeyword, typeRef, openParenthesis,
                argListBindingPatterns, closeParenthesis);
    }

    /**
     * Parse error arg list binding pattern.
     * <p>
     * <code>
     * error-arg-list-binding-pattern :=
     * error-message-binding-pattern [, error-cause-binding-pattern] [, error-field-binding-patterns]
     * | [error-field-binding-patterns]
     * <br/><br/>
     * <p>
     * error-message-binding-pattern := simple-binding-pattern
     * <br/><br/>
     * <p>
     * error-cause-binding-pattern := simple-binding-pattern | error-binding-pattern
     * <br/><br/>
     * <p>
     * simple-binding-pattern := capture-binding-pattern | wildcard-binding-pattern
     * <br/><br/>
     * <p>
     * error-field-binding-patterns :=
     * named-arg-binding-pattern (, named-arg-binding-pattern)* [, rest-binding-pattern]
     * | rest-binding-pattern
     * <br/><br/>
     * <p>
     * named-arg-binding-pattern := arg-name = binding-pattern
     * </code>
     *
     * @return Error arg list binding patterns.
     */
    private STNode parseErrorArgListBindingPatterns() {
        List<STNode> argListBindingPatterns = new ArrayList<>();
        if (isEndOfErrorFieldBindingPatterns()) {
            return STNodeFactory.createNodeList(argListBindingPatterns);
        }
        return parseErrorArgListBindingPatterns(argListBindingPatterns);
    }

    private STNode parseErrorArgListBindingPatterns(List<STNode> argListBindingPatterns) {
        STNode firstArg = parseErrorArgListBindingPattern(ParserRuleContext.ERROR_ARG_LIST_BINDING_PATTERN_START, true);
        if (firstArg == null) {
            
            return STNodeFactory.createNodeList(argListBindingPatterns);
        }

        switch (firstArg.kind) {
            case CAPTURE_BINDING_PATTERN:
            case WILDCARD_BINDING_PATTERN:
                argListBindingPatterns.add(firstArg);
                return parseErrorArgListBPWithoutErrorMsg(argListBindingPatterns);
            case ERROR_BINDING_PATTERN:
                STNode missingIdentifier = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
                STNode missingErrorMsgBP = STNodeFactory.createCaptureBindingPatternNode(missingIdentifier);
                missingErrorMsgBP = SyntaxErrors.addDiagnostic(missingErrorMsgBP,
                        DiagnosticErrorCode.ERROR_MISSING_ERROR_MESSAGE_BINDING_PATTERN);
                STNode missingComma = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.COMMA_TOKEN,
                        DiagnosticErrorCode.ERROR_MISSING_COMMA_TOKEN);
                argListBindingPatterns.add(missingErrorMsgBP);
                argListBindingPatterns.add(missingComma);
                argListBindingPatterns.add(firstArg);
                return parseErrorArgListBPWithoutErrorMsgAndCause(argListBindingPatterns, firstArg.kind);
            case REST_BINDING_PATTERN:
            case NAMED_ARG_BINDING_PATTERN:
                argListBindingPatterns.add(firstArg);
                return parseErrorArgListBPWithoutErrorMsgAndCause(argListBindingPatterns, firstArg.kind);
            default:
                
                
                addInvalidNodeToNextToken(firstArg, DiagnosticErrorCode.ERROR_BINDING_PATTERN_NOT_ALLOWED);
                return parseErrorArgListBindingPatterns(argListBindingPatterns);
        }
    }

    private STNode parseErrorArgListBPWithoutErrorMsg(List<STNode> argListBindingPatterns) {
        STNode argEnd = parseErrorArgsBindingPatternEnd(ParserRuleContext.ERROR_MESSAGE_BINDING_PATTERN_END);
        if (argEnd == null) {
            
            return STNodeFactory.createNodeList(argListBindingPatterns);
        }

        STNode secondArg = parseErrorArgListBindingPattern(ParserRuleContext.ERROR_MESSAGE_BINDING_PATTERN_RHS, false);
        assert secondArg != null; 
        switch (secondArg.kind) {
            case CAPTURE_BINDING_PATTERN:
            case WILDCARD_BINDING_PATTERN:
            case ERROR_BINDING_PATTERN:
            case REST_BINDING_PATTERN:
            case NAMED_ARG_BINDING_PATTERN:
                argListBindingPatterns.add(argEnd);
                argListBindingPatterns.add(secondArg);
                return parseErrorArgListBPWithoutErrorMsgAndCause(argListBindingPatterns, secondArg.kind);
            default:
                
                
                updateLastNodeInListWithInvalidNode(argListBindingPatterns, argEnd, null);
                updateLastNodeInListWithInvalidNode(argListBindingPatterns, secondArg,
                        DiagnosticErrorCode.ERROR_BINDING_PATTERN_NOT_ALLOWED);
                return parseErrorArgListBPWithoutErrorMsg(argListBindingPatterns);
        }
    }

    private STNode parseErrorArgListBPWithoutErrorMsgAndCause(List<STNode> argListBindingPatterns,
                                                              SyntaxKind lastValidArgKind) {
        while (!isEndOfErrorFieldBindingPatterns()) {
            STNode argEnd = parseErrorArgsBindingPatternEnd(ParserRuleContext.ERROR_FIELD_BINDING_PATTERN_END);
            if (argEnd == null) {
                
                break;
            }
            STNode currentArg = parseErrorArgListBindingPattern(ParserRuleContext.ERROR_FIELD_BINDING_PATTERN, false);
            assert currentArg != null; 
            DiagnosticErrorCode errorCode = validateErrorFieldBindingPatternOrder(lastValidArgKind, currentArg.kind);
            if (errorCode == null) {
                argListBindingPatterns.add(argEnd);
                argListBindingPatterns.add(currentArg);
                lastValidArgKind = currentArg.kind;
            } else if (argListBindingPatterns.size() == 0) {
                addInvalidNodeToNextToken(argEnd, null);
                addInvalidNodeToNextToken(currentArg, errorCode);
            } else {
                updateLastNodeInListWithInvalidNode(argListBindingPatterns, argEnd, null);
                updateLastNodeInListWithInvalidNode(argListBindingPatterns, currentArg, errorCode);
            }
        }

        return STNodeFactory.createNodeList(argListBindingPatterns);
    }

    private boolean isEndOfErrorFieldBindingPatterns() {
        SyntaxKind nextTokenKind = peek().kind;
        switch (nextTokenKind) {
            case CLOSE_PAREN_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseErrorArgsBindingPatternEnd(ParserRuleContext currentCtx) {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return consume();
            case CLOSE_PAREN_TOKEN:
                return null;
            default:
                recover(peek(), currentCtx);
                return parseErrorArgsBindingPatternEnd(currentCtx);
        }
    }

    private STNode parseErrorArgListBindingPattern(ParserRuleContext context, boolean isFirstArg) {
        switch (peek().kind) {
            case ELLIPSIS_TOKEN:
                return parseRestBindingPattern();
            case IDENTIFIER_TOKEN:
                
                STNode argNameOrSimpleBindingPattern = consume();
                return parseNamedOrSimpleArgBindingPattern(argNameOrSimpleBindingPattern);
            case OPEN_BRACKET_TOKEN:
            case OPEN_BRACE_TOKEN:
            case ERROR_KEYWORD:
                return parseBindingPattern();
            case CLOSE_PAREN_TOKEN:
                if (isFirstArg) {
                    
                    return null;
                }
                
            default:
                recover(peek(), context);
                return parseErrorArgListBindingPattern(context, isFirstArg);
        }
    }

    private STNode parseNamedOrSimpleArgBindingPattern(STNode argNameOrSimpleBindingPattern) {
        STToken secondToken = peek();
        switch (secondToken.kind) {
            case EQUAL_TOKEN:
                STNode equal = consume();
                STNode bindingPattern = parseBindingPattern();
                return STNodeFactory.createNamedArgBindingPatternNode(argNameOrSimpleBindingPattern,
                        equal, bindingPattern);
            case COMMA_TOKEN:
            case CLOSE_PAREN_TOKEN:
            default:
                return createCaptureOrWildcardBP(argNameOrSimpleBindingPattern);
        }
    }

    private DiagnosticErrorCode validateErrorFieldBindingPatternOrder(SyntaxKind prevArgKind,
                                                                      SyntaxKind currentArgKind) {
        switch (currentArgKind) {
            case NAMED_ARG_BINDING_PATTERN:
            case REST_BINDING_PATTERN:
                
                if (prevArgKind == SyntaxKind.REST_BINDING_PATTERN) {
                    return DiagnosticErrorCode.ERROR_REST_ARG_FOLLOWED_BY_ANOTHER_ARG;
                }
                return null;
            case CAPTURE_BINDING_PATTERN:
            case WILDCARD_BINDING_PATTERN:
            case ERROR_BINDING_PATTERN:
            case LIST_BINDING_PATTERN:
            case MAPPING_BINDING_PATTERN:
            default:
                return DiagnosticErrorCode.ERROR_BINDING_PATTERN_NOT_ALLOWED;
        }
    }

    

    /*
     * This parses Typed binding patterns and deals with ambiguity between types,
     * and binding patterns. An example is 'T[a]'.
     * The ambiguity lies in between:
     * 1) Array Type
     * 2) List binding pattern
     * 3) Member access expression.
     */

    /**
     * Parse the component after the type-desc, of a typed-binding-pattern.
     *
     * @param typeDesc Starting type-desc of the typed-binding-pattern
     * @return Typed-binding pattern
     */
    private STNode parseTypedBindingPatternTypeRhs(STNode typeDesc, ParserRuleContext context) {
        return parseTypedBindingPatternTypeRhs(typeDesc, context, true);
    }

    private STNode parseTypedBindingPatternTypeRhs(STNode typeDesc, ParserRuleContext context, boolean isRoot) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN: 
            case OPEN_BRACE_TOKEN: 
            case ERROR_KEYWORD: 
                STNode bindingPattern = parseBindingPattern();
                return STNodeFactory.createTypedBindingPatternNode(typeDesc, bindingPattern);
            case OPEN_BRACKET_TOKEN:
                
                STNode typedBindingPattern = parseTypedBindingPatternOrMemberAccess(typeDesc, true, true, context);
                assert typedBindingPattern.kind == SyntaxKind.TYPED_BINDING_PATTERN;
                return typedBindingPattern;
            case CLOSE_PAREN_TOKEN:
            case COMMA_TOKEN:
            case CLOSE_BRACKET_TOKEN:
            case CLOSE_BRACE_TOKEN:
                if (!isRoot) {
                    return typeDesc;
                }
                
            default:
                recover(nextToken, ParserRuleContext.TYPED_BINDING_PATTERN_TYPE_RHS);
                return parseTypedBindingPatternTypeRhs(typeDesc, context, isRoot);
        }
    }

    /**
     * Parse typed-binding pattern with list, array-type-desc, or member-access-expr.
     *
     * @param typeDescOrExpr        Type desc or the expression at the start
     * @param isTypedBindingPattern Is this is a typed-binding-pattern. If this is `false`, then it's still ambiguous
     * @return Parsed node
     */
    private STNode parseTypedBindingPatternOrMemberAccess(STNode typeDescOrExpr, boolean isTypedBindingPattern,
                                                          boolean allowAssignment, ParserRuleContext context) {
        startContext(ParserRuleContext.BRACKETED_LIST);
        STNode openBracket = parseOpenBracket();

        
        if (isBracketedListEnd(peek().kind)) {
            return parseAsArrayTypeDesc(typeDescOrExpr, openBracket, STNodeFactory.createEmptyNode(), context);
        }

        
        STNode member = parseBracketedListMember(isTypedBindingPattern);
        SyntaxKind currentNodeType = getBracketedListNodeType(member, isTypedBindingPattern);
        switch (currentNodeType) {
            case ARRAY_TYPE_DESC:
                STNode typedBindingPattern = parseAsArrayTypeDesc(typeDescOrExpr, openBracket, member, context);
                return typedBindingPattern;
            case LIST_BINDING_PATTERN:
                
                
                STNode bindingPattern = parseAsListBindingPattern(openBracket, new ArrayList<>(), member, false);
                STNode typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                return STNodeFactory.createTypedBindingPatternNode(typeDesc, bindingPattern);
            case INDEXED_EXPRESSION:
                return parseAsMemberAccessExpr(typeDescOrExpr, openBracket, member);
            case ARRAY_TYPE_DESC_OR_MEMBER_ACCESS:
                break;
            case NONE:
            default:
                
                

                
                STNode memberEnd = parseBracketedListMemberEnd();
                if (memberEnd != null) {
                    
                    List<STNode> memberList = new ArrayList<>();
                    memberList.add(getBindingPattern(member, true));
                    memberList.add(memberEnd);
                    bindingPattern = parseAsListBindingPattern(openBracket, memberList);
                    typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                    return STNodeFactory.createTypedBindingPatternNode(typeDesc, bindingPattern);
                }
        }

        
        
        
        
        
        STNode closeBracket = parseCloseBracket();
        endContext();
        return parseTypedBindingPatternOrMemberAccessRhs(typeDescOrExpr, openBracket, member, closeBracket,
                isTypedBindingPattern, allowAssignment, context);
    }

    private STNode parseAsMemberAccessExpr(STNode typeNameOrExpr, STNode openBracket, STNode member) {
        member = parseExpressionRhs(DEFAULT_OP_PRECEDENCE, member, false, true);
        STNode closeBracket = parseCloseBracket();
        endContext();
        STNode keyExpr = STNodeFactory.createNodeList(member);
        STNode memberAccessExpr =
                STNodeFactory.createIndexedExpressionNode(typeNameOrExpr, openBracket, keyExpr, closeBracket);
        return parseExpressionRhs(DEFAULT_OP_PRECEDENCE, memberAccessExpr, false, false);
    }

    private boolean isBracketedListEnd(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACKET_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse a member of an ambiguous bracketed list. This member could be:
     * 1) Array length
     * 2) Key expression of a member-access-expr
     * 3) A member-binding pattern of a list-binding-pattern.
     *
     * @param isTypedBindingPattern Is this in a definite typed-binding pattern
     * @return Parsed member node
     */
    private STNode parseBracketedListMember(boolean isTypedBindingPattern) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case ASTERISK_TOKEN:
            case STRING_LITERAL_TOKEN:
                return parseBasicLiteral();
            case CLOSE_BRACKET_TOKEN:
                return STNodeFactory.createEmptyNode();
            case OPEN_BRACE_TOKEN:
            case ERROR_KEYWORD: 
            case ELLIPSIS_TOKEN: 
            case OPEN_BRACKET_TOKEN: 
                return parseStatementStartBracketedListMember();
            case IDENTIFIER_TOKEN:
                if (isTypedBindingPattern) {
                    return parseQualifiedIdentifier(ParserRuleContext.VARIABLE_REF);
                }
                break;
            default:
                if ((!isTypedBindingPattern && isValidExpressionStart(nextToken.kind, 1)) ||
                        isQualifiedIdentifierPredeclaredPrefix(nextToken.kind)) {
                    break;
                }

                ParserRuleContext recoverContext =
                        isTypedBindingPattern ? ParserRuleContext.LIST_BINDING_MEMBER_OR_ARRAY_LENGTH
                                : ParserRuleContext.BRACKETED_LIST_MEMBER;
                recover(peek(), recoverContext);
                return parseBracketedListMember(isTypedBindingPattern);
        }

        STNode expr = parseExpression();
        if (isWildcardBP(expr)) {
            return getWildcardBindingPattern(expr);
        }

        
        return expr;
    }

    /**
     * Treat the current node as an array, and parse the remainder of the binding pattern.
     *
     * @param typeDesc    Type-desc
     * @param openBracket Open bracket
     * @param member      Member
     * @return Parsed node
     */
    private STNode parseAsArrayTypeDesc(STNode typeDesc, STNode openBracket, STNode member, ParserRuleContext context) {
        
        
        typeDesc = getTypeDescFromExpr(typeDesc);
        switchContext(ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN);
        startContext(ParserRuleContext.ARRAY_TYPE_DESCRIPTOR);
        STNode closeBracket = parseCloseBracket();
        endContext();
        endContext();
        return parseTypedBindingPatternOrMemberAccessRhs(typeDesc, openBracket, member, closeBracket, true, true,
                context);
    }

    private STNode parseBracketedListMemberEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACKET_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.BRACKETED_LIST_MEMBER_END);
                return parseBracketedListMemberEnd();
        }
    }

    /**
     * We reach here to break ambiguity of T[a]. This could be:
     * 1) Array Type Desc
     * 2) Member access on LHS
     * 3) Typed-binding-pattern
     *
     * @param typeDescOrExpr        Type name or the expr that precede the open-bracket.
     * @param openBracket           Open bracket
     * @param member                Member
     * @param closeBracket          Open bracket
     * @param isTypedBindingPattern Is this is a typed-binding-pattern.
     * @return Specific node that matches to T[a], after solving ambiguity.
     */
    private STNode parseTypedBindingPatternOrMemberAccessRhs(STNode typeDescOrExpr, STNode openBracket, STNode member,
                                                             STNode closeBracket, boolean isTypedBindingPattern,
                                                             boolean allowAssignment, ParserRuleContext context) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN: 
            case OPEN_BRACE_TOKEN: 
            case ERROR_KEYWORD: 
                
                STNode typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                STNode arrayTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, typeDesc);
                return parseTypedBindingPatternTypeRhs(arrayTypeDesc, context);
            case OPEN_BRACKET_TOKEN: 
                if (isTypedBindingPattern) {
                    typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                    arrayTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, typeDesc);
                    return parseTypedBindingPatternTypeRhs(arrayTypeDesc, context);
                }

                
                STNode keyExpr = getKeyExpr(member);
                STNode expr =
                        STNodeFactory.createIndexedExpressionNode(typeDescOrExpr, openBracket, keyExpr, closeBracket);
                return parseTypedBindingPatternOrMemberAccess(expr, false, allowAssignment, context);
            case QUESTION_MARK_TOKEN:
                
                typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                arrayTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, typeDesc);
                typeDesc = parseComplexTypeDescriptor(arrayTypeDesc,
                        ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true);
                return parseTypedBindingPatternTypeRhs(typeDesc, context);
            case PIPE_TOKEN:
            case BITWISE_AND_TOKEN:
                
                return parseComplexTypeDescInTypedBPOrExprRhs(typeDescOrExpr, openBracket, member, closeBracket,
                        isTypedBindingPattern);
            case IN_KEYWORD:
                
                if (context != ParserRuleContext.FOREACH_STMT &&
                        context != ParserRuleContext.FROM_CLAUSE &&
                        context != ParserRuleContext.JOIN_CLAUSE) {
                    break;
                }
                return createTypedBindingPattern(typeDescOrExpr, openBracket, member, closeBracket);
            case EQUAL_TOKEN: 
                if (context == ParserRuleContext.FOREACH_STMT || context == ParserRuleContext.FROM_CLAUSE) {
                    
                    
                    break;
                }

                
                if (isTypedBindingPattern || !allowAssignment || !isValidLVExpr(typeDescOrExpr)) {
                    return createTypedBindingPattern(typeDescOrExpr, openBracket, member, closeBracket);
                }

                keyExpr = getKeyExpr(member);
                typeDescOrExpr = getExpression(typeDescOrExpr);
                return STNodeFactory.createIndexedExpressionNode(typeDescOrExpr, openBracket, keyExpr, closeBracket);
            case SEMICOLON_TOKEN: 
                if (context == ParserRuleContext.FOREACH_STMT || context == ParserRuleContext.FROM_CLAUSE) {
                    
                    
                    break;
                }

                return createTypedBindingPattern(typeDescOrExpr, openBracket, member, closeBracket);
            case CLOSE_BRACE_TOKEN: 
            case COMMA_TOKEN:
                if (context == ParserRuleContext.AMBIGUOUS_STMT) {
                    keyExpr = getKeyExpr(member);
                    return STNodeFactory.createIndexedExpressionNode(typeDescOrExpr, openBracket, keyExpr,
                            closeBracket);
                }
                
            default:
                if (!isTypedBindingPattern && isValidExprRhsStart(nextToken.kind, closeBracket.kind)) {
                    
                    keyExpr = getKeyExpr(member);
                    typeDescOrExpr = getExpression(typeDescOrExpr);
                    return STNodeFactory.createIndexedExpressionNode(typeDescOrExpr, openBracket, keyExpr,
                            closeBracket);
                }

                break;
        }

        ParserRuleContext recoveryCtx = ParserRuleContext.BRACKETED_LIST_RHS;
        if (isTypedBindingPattern) {
            recoveryCtx = ParserRuleContext.TYPE_DESC_RHS_OR_BP_RHS;
        }

        recover(peek(), recoveryCtx);
        return parseTypedBindingPatternOrMemberAccessRhs(typeDescOrExpr, openBracket, member, closeBracket,
                isTypedBindingPattern, allowAssignment, context);
    }

    private STNode getKeyExpr(STNode member) {
        if (member == null) {
            STToken keyIdentifier = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                    DiagnosticErrorCode.ERROR_MISSING_KEY_EXPR_IN_MEMBER_ACCESS_EXPR);
            STNode missingVarRef = STNodeFactory.createSimpleNameReferenceNode(keyIdentifier);

            return STNodeFactory.createNodeList(missingVarRef);
        }
        return STNodeFactory.createNodeList(member);
    }

    private STNode createTypedBindingPattern(STNode typeDescOrExpr, STNode openBracket, STNode member,
                                             STNode closeBracket) {
        STNode bindingPatterns = STNodeFactory.createEmptyNodeList();
        if (!isEmpty(member)) {
            SyntaxKind memberKind = member.kind;
            if (memberKind == SyntaxKind.NUMERIC_LITERAL || memberKind == SyntaxKind.ASTERISK_LITERAL) {
                STNode typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                STNode arrayTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, typeDesc);
                STToken identifierToken = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                        DiagnosticErrorCode.ERROR_MISSING_VARIABLE_NAME);
                STNode variableName = STNodeFactory.createCaptureBindingPatternNode(identifierToken);
                return STNodeFactory.createTypedBindingPatternNode(arrayTypeDesc, variableName);
            }

            STNode bindingPattern = getBindingPattern(member, true);
            bindingPatterns = STNodeFactory.createNodeList(bindingPattern);
        }

        STNode bindingPattern = STNodeFactory.createListBindingPatternNode(openBracket, bindingPatterns, closeBracket);
        STNode typeDesc = getTypeDescFromExpr(typeDescOrExpr);
        return STNodeFactory.createTypedBindingPatternNode(typeDesc, bindingPattern);
    }

    /**
     * Parse a union or intersection type-desc/binary-expression that involves ambiguous
     * bracketed list in lhs.
     * <p>
     * e.g: <code>(T[a] & R..)</code> or <code>(T[a] | R.. )</code>
     * <p>
     * Complexity occurs in scenarios such as <code>T[a] |/& R[b]</code>. If the token after this
     * is another binding-pattern, then <code>(T[a] |/& R[b])</code> becomes the type-desc. However,
     * if the token follows this is an equal or semicolon, then <code>(T[a] |/& R)</code> becomes
     * the type-desc, and <code>[b]</code> becomes the binding pattern.
     *
     * @param typeDescOrExpr Type desc or the expression
     * @param openBracket    Open bracket
     * @param member         Member
     * @param closeBracket   Close bracket
     * @return Parsed node
     */
    private STNode parseComplexTypeDescInTypedBPOrExprRhs(STNode typeDescOrExpr, STNode openBracket, STNode member,
                                                             STNode closeBracket, boolean isTypedBindingPattern) {
        STNode pipeOrAndToken = parseUnionOrIntersectionToken();
        STNode typedBindingPatternOrExpr = parseTypedBindingPatternOrExpr(false);

        if (typedBindingPatternOrExpr.kind == SyntaxKind.TYPED_BINDING_PATTERN) {
            
            
            STNode lhsTypeDesc = getTypeDescFromExpr(typeDescOrExpr);
            lhsTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, lhsTypeDesc);

            STTypedBindingPatternNode rhsTypedBindingPattern = (STTypedBindingPatternNode) typedBindingPatternOrExpr;
            STNode rhsTypeDesc = rhsTypedBindingPattern.typeDescriptor;

            STNode newTypeDesc = mergeTypes(lhsTypeDesc, pipeOrAndToken, rhsTypeDesc);
            return STNodeFactory.createTypedBindingPatternNode(newTypeDesc, rhsTypedBindingPattern.bindingPattern);
        }

        if (isTypedBindingPattern) {
            
            STNode lhsTypeDesc = getTypeDescFromExpr(typeDescOrExpr);
            lhsTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, lhsTypeDesc);
            return createCaptureBPWithMissingVarName(lhsTypeDesc, pipeOrAndToken, typedBindingPatternOrExpr);
        }

        STNode keyExpr = getExpression(member);
        STNode containerExpr = getExpression(typeDescOrExpr);
        STNode lhsExpr =
                STNodeFactory.createIndexedExpressionNode(containerExpr, openBracket, keyExpr, closeBracket);
        return STNodeFactory.createBinaryExpressionNode(SyntaxKind.BINARY_EXPRESSION, lhsExpr, pipeOrAndToken,
                typedBindingPatternOrExpr);

    }

    /**
     * Merges two types separated by <code>|</code> or <code>&</code> into one type, while taking precedence
     * and associativity into account.
     *
     * @param lhsTypeDesc    lhs type
     * @param pipeOrAndToken pipe or bitwise-and token
     * @param rhsTypeDesc    rhs type
     * @return a TypeDescriptorNode
     */
    private STNode mergeTypes(STNode lhsTypeDesc, STNode pipeOrAndToken, STNode rhsTypeDesc) {
        if (pipeOrAndToken.kind == SyntaxKind.PIPE_TOKEN) {
            return mergeTypesWithUnion(lhsTypeDesc, pipeOrAndToken, rhsTypeDesc);
        } else {
            return mergeTypesWithIntersection(lhsTypeDesc, pipeOrAndToken, rhsTypeDesc);
        }
    }

    /**
     * Merges two types separated by <code>|</code> into one type, while taking precedence
     * and associativity into account.
     *
     * @param lhsTypeDesc lhs type
     * @param pipeToken   pipe token
     * @param rhsTypeDesc rhs type
     * @return a TypeDescriptorNode
     */
    private STNode mergeTypesWithUnion(STNode lhsTypeDesc, STNode pipeToken, STNode rhsTypeDesc) {
        if (rhsTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            
            
            

            STUnionTypeDescriptorNode rhsUnionTypeDesc = (STUnionTypeDescriptorNode) rhsTypeDesc;
            return replaceLeftMostUnionWithAUnion(lhsTypeDesc, pipeToken, rhsUnionTypeDesc);
        } else {
            return createUnionTypeDesc(lhsTypeDesc, pipeToken, rhsTypeDesc);
        }
    }

    /**
     * Merges two types separated by <code>&</code> into one type, while taking precedence
     * and associativity into account.
     *
     * @param lhsTypeDesc     lhs type
     * @param bitwiseAndToken bitwise-and token
     * @param rhsTypeDesc     rhs type
     * @return a TypeDescriptorNode
     */
    private STNode mergeTypesWithIntersection(STNode lhsTypeDesc, STNode bitwiseAndToken, STNode rhsTypeDesc) {
        if (lhsTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            
            STUnionTypeDescriptorNode lhsUnionTypeDesc = (STUnionTypeDescriptorNode) lhsTypeDesc;
            if (rhsTypeDesc.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
                rhsTypeDesc = replaceLeftMostIntersectionWithAIntersection(lhsUnionTypeDesc.rightTypeDesc,
                        bitwiseAndToken, (STIntersectionTypeDescriptorNode) rhsTypeDesc);
                return createUnionTypeDesc(lhsUnionTypeDesc.leftTypeDesc, lhsUnionTypeDesc.pipeToken, rhsTypeDesc);
            } else if (rhsTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
                rhsTypeDesc = replaceLeftMostUnionWithAIntersection(lhsUnionTypeDesc.rightTypeDesc,
                        bitwiseAndToken, (STUnionTypeDescriptorNode) rhsTypeDesc);
                return replaceLeftMostUnionWithAUnion(lhsUnionTypeDesc.leftTypeDesc,
                        lhsUnionTypeDesc.pipeToken, (STUnionTypeDescriptorNode) rhsTypeDesc);
            } else {
                rhsTypeDesc = createIntersectionTypeDesc(lhsUnionTypeDesc.rightTypeDesc, bitwiseAndToken, rhsTypeDesc);
                return createUnionTypeDesc(lhsUnionTypeDesc.leftTypeDesc, lhsUnionTypeDesc.pipeToken, rhsTypeDesc);
            }
        }

        if (rhsTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            
            
            

            STUnionTypeDescriptorNode rhsUnionTypeDesc = (STUnionTypeDescriptorNode) rhsTypeDesc;
            return replaceLeftMostUnionWithAIntersection(lhsTypeDesc, bitwiseAndToken, rhsUnionTypeDesc);
        } else if (rhsTypeDesc.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
            
            
            

            STIntersectionTypeDescriptorNode rhsIntSecTypeDesc = (STIntersectionTypeDescriptorNode) rhsTypeDesc;
            return replaceLeftMostIntersectionWithAIntersection(lhsTypeDesc, bitwiseAndToken, rhsIntSecTypeDesc);
        } else {
            return createIntersectionTypeDesc(lhsTypeDesc, bitwiseAndToken, rhsTypeDesc);
        }
    }

    private STNode replaceLeftMostUnionWithAUnion(STNode typeDesc, STNode pipeToken,
                                                  STUnionTypeDescriptorNode unionTypeDesc) {
        STNode leftTypeDesc = unionTypeDesc.leftTypeDesc;

        
        
        if (leftTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            return unionTypeDesc.replace(unionTypeDesc.leftTypeDesc,
                    replaceLeftMostUnionWithAUnion(typeDesc, pipeToken, (STUnionTypeDescriptorNode) leftTypeDesc));
        }

        
        
        leftTypeDesc = createUnionTypeDesc(typeDesc, pipeToken, leftTypeDesc);
        return unionTypeDesc.replace(unionTypeDesc.leftTypeDesc, leftTypeDesc);
    }
    
    private STNode replaceLeftMostUnionWithAIntersection(STNode typeDesc, STNode bitwiseAndToken,
                                                         STUnionTypeDescriptorNode unionTypeDesc) {
        STNode leftTypeDesc = unionTypeDesc.leftTypeDesc;
        
        
        
        if (leftTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            return unionTypeDesc.replace(unionTypeDesc.leftTypeDesc,
                    replaceLeftMostUnionWithAIntersection(typeDesc, bitwiseAndToken,
                            (STUnionTypeDescriptorNode) leftTypeDesc));
        }

        
        
        if (leftTypeDesc.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
            return unionTypeDesc.replace(unionTypeDesc.leftTypeDesc, 
                    replaceLeftMostIntersectionWithAIntersection(typeDesc, bitwiseAndToken,
                            (STIntersectionTypeDescriptorNode) leftTypeDesc));
        }

        
        
        leftTypeDesc = createIntersectionTypeDesc(typeDesc, bitwiseAndToken, leftTypeDesc);
        return unionTypeDesc.replace(unionTypeDesc.leftTypeDesc, leftTypeDesc);
    }

    private STNode replaceLeftMostIntersectionWithAIntersection(STNode typeDesc,
                                                                STNode bitwiseAndToken,
                                                                STIntersectionTypeDescriptorNode intersectionTypeDesc) {
        STNode leftTypeDesc = intersectionTypeDesc.leftTypeDesc;

        
        
        if (leftTypeDesc.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
            return intersectionTypeDesc.replace(intersectionTypeDesc.leftTypeDesc,
                    replaceLeftMostIntersectionWithAIntersection(typeDesc, bitwiseAndToken,
                            (STIntersectionTypeDescriptorNode) leftTypeDesc));
        }

        
        
        leftTypeDesc = createIntersectionTypeDesc(typeDesc, bitwiseAndToken, leftTypeDesc);
        return intersectionTypeDesc.replace(intersectionTypeDesc.leftTypeDesc, leftTypeDesc);
    }
    
    private STNode getArrayTypeDesc(STNode openBracket, STNode member, STNode closeBracket, STNode lhsTypeDesc) {
        if (lhsTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            STUnionTypeDescriptorNode unionTypeDesc = (STUnionTypeDescriptorNode) lhsTypeDesc;
            STNode middleTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, unionTypeDesc.rightTypeDesc);
            lhsTypeDesc = mergeTypesWithUnion(unionTypeDesc.leftTypeDesc, unionTypeDesc.pipeToken, middleTypeDesc);
        } else if (lhsTypeDesc.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
            STIntersectionTypeDescriptorNode intersectionTypeDesc = (STIntersectionTypeDescriptorNode) lhsTypeDesc;
            STNode middleTypeDesc =
                    getArrayTypeDesc(openBracket, member, closeBracket, intersectionTypeDesc.rightTypeDesc);
            lhsTypeDesc = mergeTypesWithIntersection(intersectionTypeDesc.leftTypeDesc,
                    intersectionTypeDesc.bitwiseAndToken, middleTypeDesc);
        } else {
            lhsTypeDesc = createArrayTypeDesc(lhsTypeDesc, openBracket, member, closeBracket);
        }

        return lhsTypeDesc;
    }

    /**
     * Parse union (|) or intersection (&) type operator.
     *
     * @return pipe or bitwise and token
     */
    private STNode parseUnionOrIntersectionToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.PIPE_TOKEN || token.kind == SyntaxKind.BITWISE_AND_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.UNION_OR_INTERSECTION_TOKEN);
            return parseUnionOrIntersectionToken();
        }
    }

    /**
     * Infer the type of the ambiguous bracketed list, based on the type of the member.
     *
     * @param memberNode Member node
     * @return Inferred type of the bracketed list
     */
    private SyntaxKind getBracketedListNodeType(STNode memberNode, boolean isTypedBindingPattern) {
        if (isEmpty(memberNode)) {
            
            return SyntaxKind.NONE;
        }

        if (isDefiniteTypeDesc(memberNode.kind)) {
            return SyntaxKind.TUPLE_TYPE_DESC;
        }

        switch (memberNode.kind) {
            case ASTERISK_LITERAL:
                return SyntaxKind.ARRAY_TYPE_DESC;
            case CAPTURE_BINDING_PATTERN:
            case LIST_BINDING_PATTERN:
            case REST_BINDING_PATTERN:
            case MAPPING_BINDING_PATTERN:
            case WILDCARD_BINDING_PATTERN:
                return SyntaxKind.LIST_BINDING_PATTERN;
            case QUALIFIED_NAME_REFERENCE: 
            case REST_TYPE:
                return SyntaxKind.TUPLE_TYPE_DESC;
            case NUMERIC_LITERAL: 
                if (isTypedBindingPattern) {
                    return SyntaxKind.ARRAY_TYPE_DESC;
                }
                return SyntaxKind.ARRAY_TYPE_DESC_OR_MEMBER_ACCESS;
            case SIMPLE_NAME_REFERENCE: 
            case BRACKETED_LIST: 
            case MAPPING_BP_OR_MAPPING_CONSTRUCTOR:
                return SyntaxKind.NONE;
            case ERROR_CONSTRUCTOR:
                if (isPossibleErrorBindingPattern((STErrorConstructorExpressionNode) memberNode)) {
                    return SyntaxKind.NONE;
                }
                return SyntaxKind.INDEXED_EXPRESSION;
            default:
                if (isTypedBindingPattern) {
                    return SyntaxKind.NONE;
                }
                return SyntaxKind.INDEXED_EXPRESSION;
        }
    }

    

    /*
     * This section tries to break the ambiguity in parsing a statement that starts with a open-bracket.
     * The ambiguity lies in between:
     * 1) Assignment that starts with list binding pattern
     * 2) Var-decl statement that starts with tuple type
     * 3) Statement that starts with list constructor, such as sync-send, etc.
     */

    /**
     * Parse any statement that starts with an open-bracket.
     *
     * @param annots Annotations attached to the statement.
     * @return Parsed node
     */
    private STNode parseStatementStartsWithOpenBracket(STNode annots, boolean possibleMappingField) {
        startContext(ParserRuleContext.ASSIGNMENT_OR_VAR_DECL_STMT);
        return parseStatementStartsWithOpenBracket(annots, true, possibleMappingField);
    }

    private STNode parseMemberBracketedList() {
        STNode annots = STNodeFactory.createEmptyNodeList();
        return parseStatementStartsWithOpenBracket(annots, false, false);
    }

    /**
     * The bracketed list at the start of a statement can be one of the following.
     * 1) List binding pattern
     * 2) Tuple type
     * 3) List constructor
     *
     * @param isRoot Is this the root of the list
     * @return Parsed node
     */
    private STNode parseStatementStartsWithOpenBracket(STNode annots, boolean isRoot, boolean possibleMappingField) {
        startContext(ParserRuleContext.STMT_START_BRACKETED_LIST);
        STNode openBracket = parseOpenBracket();
        List<STNode> memberList = new ArrayList<>();
        while (!isBracketedListEnd(peek().kind)) {
            
            STNode member = parseStatementStartBracketedListMember();
            SyntaxKind currentNodeType = getStmtStartBracketedListType(member);

            switch (currentNodeType) {
                case TUPLE_TYPE_DESC:
                    member = parseComplexTypeDescriptor(member, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
                    member = createMemberOrRestNode(STNodeFactory.createEmptyNodeList(), member);
                    
                    
                    return parseAsTupleTypeDesc(annots, openBracket, memberList, member, isRoot);
                case MEMBER_TYPE_DESC:
                case REST_TYPE:
                    return parseAsTupleTypeDesc(annots, openBracket, memberList, member, isRoot);
                case LIST_BINDING_PATTERN:
                    
                    
                    return parseAsListBindingPattern(openBracket, memberList, member, isRoot);
                case LIST_CONSTRUCTOR:
                    
                    
                    return parseAsListConstructor(openBracket, memberList, member, isRoot);
                case LIST_BP_OR_LIST_CONSTRUCTOR:
                    return parseAsListBindingPatternOrListConstructor(openBracket, memberList, member, isRoot);
                case TUPLE_TYPE_DESC_OR_LIST_CONST:
                    return parseAsTupleTypeDescOrListConstructor(annots, openBracket, memberList, member, isRoot);
                case NONE:
                default:
                    memberList.add(member);
                    break;
            }

            
            STNode memberEnd = parseBracketedListMemberEnd();
            if (memberEnd == null) {
                break;
            }
            memberList.add(memberEnd);
        }

        
        STNode closeBracket = parseCloseBracket();
        STNode bracketedList = parseStatementStartBracketedListRhs(annots, openBracket, memberList, closeBracket,
                isRoot, possibleMappingField);
        return bracketedList;
    }

    /**
     * Parse a member of a list-binding-pattern, tuple-type-desc, or
     * list-constructor-expr, when the parent is ambiguous.
     *
     * @return Parsed node
     */
    private STNode parseStatementStartBracketedListMember() {
        List<STNode> typeDescQualifiers = new ArrayList<>();
        return parseStatementStartBracketedListMember(typeDescQualifiers);
    }

    private STNode parseStatementStartBracketedListMember(List<STNode> qualifiers) {
        parseTypeDescQualifiers(qualifiers);
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_BRACKET_TOKEN:
                reportInvalidQualifierList(qualifiers);
                return parseMemberBracketedList();
            case IDENTIFIER_TOKEN:
                reportInvalidQualifierList(qualifiers);
                STNode identifier = parseQualifiedIdentifier(ParserRuleContext.VARIABLE_REF);
                if (isWildcardBP(identifier)) {
                    STNode varName = ((STSimpleNameReferenceNode) identifier).name;
                    return getWildcardBindingPattern(varName);
                }

                nextToken = peek();
                if (nextToken.kind == SyntaxKind.ELLIPSIS_TOKEN) {
                    STNode ellipsis = parseEllipsis();
                    return STNodeFactory.createRestDescriptorNode(identifier, ellipsis);
                }

                if (nextToken.kind != SyntaxKind.OPEN_BRACKET_TOKEN && isValidTypeContinuationToken(nextToken)) {
                    
                    return parseComplexTypeDescriptor(identifier, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
                }

                return parseExpressionRhs(DEFAULT_OP_PRECEDENCE, identifier, false, true);
            case OPEN_BRACE_TOKEN:
                
                reportInvalidQualifierList(qualifiers);
                return parseMappingBindingPatterOrMappingConstructor();
            case ERROR_KEYWORD:
                reportInvalidQualifierList(qualifiers);
                STToken nextNextToken = getNextNextToken();
                if (nextNextToken.kind == SyntaxKind.OPEN_PAREN_TOKEN ||
                        nextNextToken.kind == SyntaxKind.IDENTIFIER_TOKEN) {
                    return parseErrorBindingPatternOrErrorConstructor();
                }
                
                return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
            case ELLIPSIS_TOKEN:
                
                reportInvalidQualifierList(qualifiers);
                return parseRestBindingOrSpreadMember();
            case XML_KEYWORD:
            case STRING_KEYWORD:
                reportInvalidQualifierList(qualifiers);
                if (getNextNextToken().kind == SyntaxKind.BACKTICK_TOKEN) {
                    return parseExpression(false);
                }
                return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
            case TABLE_KEYWORD:
            case STREAM_KEYWORD:
                reportInvalidQualifierList(qualifiers);
                if (getNextNextToken().kind == SyntaxKind.LT_TOKEN) {
                    return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
                }
                return parseExpression(false);
            case OPEN_PAREN_TOKEN:
                return parseTypeDescOrExpr(qualifiers);
            case FUNCTION_KEYWORD:
                return parseAnonFuncExprOrFuncTypeDesc(qualifiers);
            case AT_TOKEN:
                return parseTupleMember();
            default:
                if (isValidExpressionStart(nextToken.kind, 1)) {
                    reportInvalidQualifierList(qualifiers);
                    return parseExpression(false);
                }

                if (isTypeStartingToken(nextToken.kind)) {
                    return parseTypeDescriptor(qualifiers, ParserRuleContext.TYPE_DESC_IN_TUPLE);
                }

                recover(peek(), ParserRuleContext.STMT_START_BRACKETED_LIST_MEMBER);
                return parseStatementStartBracketedListMember(qualifiers);
        }
    }

    private STNode parseRestBindingOrSpreadMember() {
        STNode ellipsis = parseEllipsis();
        STNode expr = parseExpression();
        if (expr.kind == SyntaxKind.SIMPLE_NAME_REFERENCE) {
            return STNodeFactory.createRestBindingPatternNode(ellipsis, expr);
        } else {
            return STNodeFactory.createSpreadMemberNode(ellipsis, expr);
        }
    }

    private STNode parseAsTupleTypeDescOrListConstructor(STNode annots, STNode openBracket, List<STNode> memberList,
                                                         STNode member, boolean isRoot) {
        memberList.add(member);
        STNode memberEnd = parseBracketedListMemberEnd();

        STNode tupleTypeDescOrListCons;
        if (memberEnd == null) {
            
            STNode closeBracket = parseCloseBracket();
            tupleTypeDescOrListCons =
                    parseTupleTypeDescOrListConstructorRhs(openBracket, memberList, closeBracket, isRoot);
        } else {
            memberList.add(memberEnd);
            tupleTypeDescOrListCons = parseTupleTypeDescOrListConstructor(annots, openBracket, memberList, isRoot);
        }

        return tupleTypeDescOrListCons;
    }

    /**
     * Parse tuple type desc or list constructor.
     *
     * @return Parsed node
     */
    private STNode parseTupleTypeDescOrListConstructor(STNode annots) {
        startContext(ParserRuleContext.BRACKETED_LIST);
        STNode openBracket = parseOpenBracket();
        List<STNode> memberList = new ArrayList<>();
        return parseTupleTypeDescOrListConstructor(annots, openBracket, memberList, false);
    }

    private STNode parseTupleTypeDescOrListConstructor(STNode annots, STNode openBracket, List<STNode> memberList,
                                                       boolean isRoot) {
        
        STToken nextToken = peek();
        while (!isBracketedListEnd(nextToken.kind)) {
            
            STNode member = parseTupleTypeDescOrListConstructorMember(annots);
            SyntaxKind currentNodeType = getParsingNodeTypeOfTupleTypeOrListCons(member);

            switch (currentNodeType) {
                case LIST_CONSTRUCTOR:
                    
                    
                    return parseAsListConstructor(openBracket, memberList, member, isRoot);
                case REST_TYPE:
                case MEMBER_TYPE_DESC:
                    return parseAsTupleTypeDesc(annots, openBracket, memberList, member, isRoot);
                case TUPLE_TYPE_DESC:
                    member = parseComplexTypeDescriptor(member, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
                    member = createMemberOrRestNode(STNodeFactory.createEmptyNodeList(), member);
                    
                    
                    return parseAsTupleTypeDesc(annots, openBracket, memberList, member, isRoot);
                case TUPLE_TYPE_DESC_OR_LIST_CONST:
                default:
                    memberList.add(member);
                    break;
            }

            
            STNode memberEnd = parseBracketedListMemberEnd();
            if (memberEnd == null) {
                break;
            }
            memberList.add(memberEnd);
            nextToken = peek();
        }

        
        STNode closeBracket = parseCloseBracket();
        return parseTupleTypeDescOrListConstructorRhs(openBracket, memberList, closeBracket, isRoot);
    }

    private STNode parseTupleTypeDescOrListConstructorMember(STNode annots) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_BRACKET_TOKEN:
                
                return parseTupleTypeDescOrListConstructor(annots);
            case IDENTIFIER_TOKEN:
                STNode identifier = parseQualifiedIdentifier(ParserRuleContext.VARIABLE_REF);
                
                if (peek().kind == SyntaxKind.ELLIPSIS_TOKEN) {
                    STNode ellipsis = parseEllipsis();
                    return STNodeFactory.createRestDescriptorNode(identifier, ellipsis);
                }
                return parseExpressionRhs(DEFAULT_OP_PRECEDENCE, identifier, false, false);
            case OPEN_BRACE_TOKEN:
                
                return parseMappingConstructorExpr();
            case ERROR_KEYWORD:
                STToken nextNextToken = getNextNextToken();
                if (nextNextToken.kind == SyntaxKind.OPEN_PAREN_TOKEN ||
                        nextNextToken.kind == SyntaxKind.IDENTIFIER_TOKEN) {
                    return parseErrorConstructorExpr(false);
                }
                
                return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
            case XML_KEYWORD:
            case STRING_KEYWORD:
                if (getNextNextToken().kind == SyntaxKind.BACKTICK_TOKEN) {
                    return parseExpression(false);
                }
                return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
            case TABLE_KEYWORD:
            case STREAM_KEYWORD:
                if (getNextNextToken().kind == SyntaxKind.LT_TOKEN) {
                    return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
                }
                return parseExpression(false);
            case OPEN_PAREN_TOKEN:
                return parseTypeDescOrExpr();
            case AT_TOKEN:
                return parseTupleMember();
            default:
                if (isValidExpressionStart(nextToken.kind, 1)) {
                    return parseExpression(false);
                }

                if (isTypeStartingToken(nextToken.kind)) {
                    return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
                }

                recover(peek(), ParserRuleContext.TUPLE_TYPE_DESC_OR_LIST_CONST_MEMBER);
                return parseTupleTypeDescOrListConstructorMember(annots);
        }
    }

    private SyntaxKind getParsingNodeTypeOfTupleTypeOrListCons(STNode memberNode) {
        
        return getStmtStartBracketedListType(memberNode);
    }

    private STNode parseTupleTypeDescOrListConstructorRhs(STNode openBracket, List<STNode> members, STNode closeBracket,
                                                          boolean isRoot) {
        STNode tupleTypeOrListConst;
        switch (peek().kind) {
            case COMMA_TOKEN: 
            case CLOSE_BRACE_TOKEN: 
            case CLOSE_BRACKET_TOKEN: 
            case PIPE_TOKEN: 
            case BITWISE_AND_TOKEN: 
                if (!isRoot) {
                    endContext();
                    return new STAmbiguousCollectionNode(SyntaxKind.TUPLE_TYPE_DESC_OR_LIST_CONST, openBracket, members,
                            closeBracket);
                }
                
            default:
                if (isValidExprRhsStart(peek().kind, closeBracket.kind) ||
                        (isRoot && peek().kind == SyntaxKind.EQUAL_TOKEN)) {
                    members = getExpressionList(members, false);
                    STNode memberExpressions = STNodeFactory.createNodeList(members);
                    tupleTypeOrListConst = STNodeFactory.createListConstructorExpressionNode(openBracket,
                            memberExpressions, closeBracket);
                    break;
                }

                
                STNode memberTypeDescs = STNodeFactory.createNodeList(getTupleMemberList(members));
                STNode tupleTypeDesc =
                        STNodeFactory.createTupleTypeDescriptorNode(openBracket, memberTypeDescs, closeBracket);
                tupleTypeOrListConst =
                        parseComplexTypeDescriptor(tupleTypeDesc, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
        }

        endContext();

        if (!isRoot) {
            return tupleTypeOrListConst;
        }

        STNode annots = STNodeFactory.createEmptyNodeList();
        return parseStmtStartsWithTupleTypeOrExprRhs(annots, tupleTypeOrListConst, isRoot);

    }

Line commented on: switch (nextTokenKind) {

Revised code:
private boolean isServiceDeclStart(ParserRuleContext currentContext, int lookahead) {
        
        switch (peek(lookahead + 1).kind) {
            case IDENTIFIER_TOKEN:
                SyntaxKind tokenAfterIdentifier = peek(lookahead + 2).kind;
                switch (tokenAfterIdentifier) {
                    case ON_KEYWORD: 
                    case OPEN_BRACE_TOKEN: 
                        return true;
                    case EQUAL_TOKEN: 
                    case SEMICOLON_TOKEN: 
                    case QUESTION_MARK_TOKEN: 
                        return false;
                    default:
                        
                        return false;
                }
            case ON_KEYWORD:
                
                
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse listener declaration, given the qualifier.
     * <p>
     * <code>
     * listener-decl := metadata [public] listener [type-descriptor] variable-name = expression ;
     * </code>
     *
     * @param metadata  Metadata
     * @param qualifier Qualifier that precedes the listener declaration
     * @return Parsed node
     */
    private STNode parseListenerDeclaration(STNode metadata, STNode qualifier) {
        startContext(ParserRuleContext.LISTENER_DECL);
        STNode listenerKeyword = parseListenerKeyword();

        if (peek().kind == SyntaxKind.IDENTIFIER_TOKEN) {
            STNode listenerDecl =
                    parseConstantOrListenerDeclWithOptionalType(metadata, qualifier, listenerKeyword, true);
            endContext();
            return listenerDecl;
        }

        STNode typeDesc = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_BEFORE_IDENTIFIER);
        STNode variableName = parseVariableName();
        STNode equalsToken = parseAssignOp();
        STNode initializer = parseExpression();
        STNode semicolonToken = parseSemicolon();
        endContext();
        return STNodeFactory.createListenerDeclarationNode(metadata, qualifier, listenerKeyword, typeDesc, variableName,
                equalsToken, initializer, semicolonToken);
    }

    /**
     * Parse listener keyword.
     *
     * @return Parsed node
     */
    private STNode parseListenerKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.LISTENER_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.LISTENER_KEYWORD);
            return parseListenerKeyword();
        }
    }

    /**
     * Parse constant declaration, given the qualifier.
     * <p>
     * <code>module-const-decl := metadata [public] const [type-descriptor] identifier = const-expr ;</code>
     *
     * @param metadata  Metadata
     * @param qualifier Qualifier that precedes the listener declaration
     * @return Parsed node
     */
    private STNode parseConstantDeclaration(STNode metadata, STNode qualifier) {
        startContext(ParserRuleContext.CONSTANT_DECL);
        STNode constKeyword = parseConstantKeyword();
        
        return parseConstDecl(metadata, qualifier, constKeyword);
    }

    /**
     * Parse the components that follows after the const keyword of a constant declaration.
     *
     * @param metadata     Metadata
     * @param qualifier    Qualifier that precedes the constant decl
     * @param constKeyword Const keyword
     * @return Parsed node
     */
    private STNode parseConstDecl(STNode metadata, STNode qualifier, STNode constKeyword) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case ANNOTATION_KEYWORD:
                endContext();
                return parseAnnotationDeclaration(metadata, qualifier, constKeyword);
            case IDENTIFIER_TOKEN:
                STNode constantDecl =
                        parseConstantOrListenerDeclWithOptionalType(metadata, qualifier, constKeyword, false);
                endContext();
                return constantDecl;
            default:
                if (isTypeStartingToken(nextToken.kind)) {
                    break;
                }

                recover(peek(), ParserRuleContext.CONST_DECL_TYPE);
                return parseConstDecl(metadata, qualifier, constKeyword);
        }

        STNode typeDesc = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_BEFORE_IDENTIFIER);
        STNode variableName = parseVariableName();
        STNode equalsToken = parseAssignOp();
        STNode initializer = parseExpression();
        STNode semicolonToken = parseSemicolon();
        endContext();
        return STNodeFactory.createConstantDeclarationNode(metadata, qualifier, constKeyword, typeDesc, variableName,
                equalsToken, initializer, semicolonToken);
    }

    private STNode parseConstantOrListenerDeclWithOptionalType(STNode metadata, STNode qualifier, STNode constKeyword,
                                                               boolean isListener) {
        STNode varNameOrTypeName = parseStatementStartIdentifier();
        return parseConstantOrListenerDeclRhs(metadata, qualifier, constKeyword, varNameOrTypeName, isListener);
    }

    /**
     * Parse the component that follows the first identifier in a const decl. The identifier
     * can be either the type-name (a user defined type) or the var-name there the type-name
     * is not present.
     *
     * @param qualifier     Qualifier that precedes the constant decl
     * @param keyword       Keyword
     * @param typeOrVarName Identifier that follows the const-keywoord
     * @return Parsed node
     */
    private STNode parseConstantOrListenerDeclRhs(STNode metadata, STNode qualifier, STNode keyword,
                                                  STNode typeOrVarName, boolean isListener) {
        if (typeOrVarName.kind == SyntaxKind.QUALIFIED_NAME_REFERENCE) {
            STNode type = typeOrVarName;
            STNode variableName = parseVariableName();
            return parseListenerOrConstRhs(metadata, qualifier, keyword, isListener, type, variableName);
        }

        STNode type;
        STNode variableName;
        switch (peek().kind) {
            case IDENTIFIER_TOKEN:
                type = typeOrVarName;
                variableName = parseVariableName();
                break;
            case EQUAL_TOKEN:
                variableName = ((STSimpleNameReferenceNode) typeOrVarName).name; 
                type = STNodeFactory.createEmptyNode();
                break;
            default:
                recover(peek(), ParserRuleContext.CONST_DECL_RHS);
                return parseConstantOrListenerDeclRhs(metadata, qualifier, keyword, typeOrVarName, isListener);
        }

        return parseListenerOrConstRhs(metadata, qualifier, keyword, isListener, type, variableName);
    }

    private STNode parseListenerOrConstRhs(STNode metadata, STNode qualifier, STNode keyword, boolean isListener,
                                           STNode type, STNode variableName) {
        STNode equalsToken = parseAssignOp();
        STNode initializer = parseExpression();
        STNode semicolonToken = parseSemicolon();

        if (isListener) {
            return STNodeFactory.createListenerDeclarationNode(metadata, qualifier, keyword, type, variableName,
                    equalsToken, initializer, semicolonToken);
        }

        return STNodeFactory.createConstantDeclarationNode(metadata, qualifier, keyword, type, variableName,
                equalsToken, initializer, semicolonToken);
    }

    /**
     * Parse const keyword.
     *
     * @return Parsed node
     */
    private STNode parseConstantKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.CONST_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.CONST_KEYWORD);
            return parseConstantKeyword();
        }
    }

    /**
     * Parse typeof expression.
     * <p>
     * <code>
     * typeof-expr := typeof expression
     * </code>
     *
     * @param isRhsExpr
     * @return Typeof expression node
     */
    private STNode parseTypeofExpression(boolean isRhsExpr, boolean isInConditionalExpr) {
        STNode typeofKeyword = parseTypeofKeyword();

        
        
        STNode expr = parseExpression(OperatorPrecedence.UNARY, isRhsExpr, false, isInConditionalExpr);
        return STNodeFactory.createTypeofExpressionNode(typeofKeyword, expr);
    }

    /**
     * Parse typeof-keyword.
     *
     * @return Typeof-keyword node
     */
    private STNode parseTypeofKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.TYPEOF_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.TYPEOF_KEYWORD);
            return parseTypeofKeyword();
        }
    }

    /**
     * Parse optional type descriptor given the type.
     * <p>
     * <code>optional-type-descriptor := type-descriptor `?`</code>
     * </p>
     *
     * @param typeDescriptorNode Preceding type descriptor
     * @return Parsed node
     */
    private STNode parseOptionalTypeDescriptor(STNode typeDescriptorNode) {
        startContext(ParserRuleContext.OPTIONAL_TYPE_DESCRIPTOR);
        STNode questionMarkToken = parseQuestionMark();
        endContext();
        return createOptionalTypeDesc(typeDescriptorNode, questionMarkToken);
    }

    private STNode createOptionalTypeDesc(STNode typeDescNode, STNode questionMarkToken) {
        if (typeDescNode.kind == SyntaxKind.UNION_TYPE_DESC) {
            STUnionTypeDescriptorNode unionTypeDesc = (STUnionTypeDescriptorNode) typeDescNode;
            STNode middleTypeDesc = createOptionalTypeDesc(unionTypeDesc.rightTypeDesc, questionMarkToken);
            typeDescNode = mergeTypesWithUnion(unionTypeDesc.leftTypeDesc, unionTypeDesc.pipeToken, middleTypeDesc);
        } else if (typeDescNode.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
            STIntersectionTypeDescriptorNode intersectionTypeDesc = (STIntersectionTypeDescriptorNode) typeDescNode;
            STNode middleTypeDesc = createOptionalTypeDesc(intersectionTypeDesc.rightTypeDesc, questionMarkToken);
            typeDescNode = mergeTypesWithIntersection(intersectionTypeDesc.leftTypeDesc,
                    intersectionTypeDesc.bitwiseAndToken, middleTypeDesc);
        } else {
            typeDescNode = validateForUsageOfVar(typeDescNode);
            typeDescNode = STNodeFactory.createOptionalTypeDescriptorNode(typeDescNode, questionMarkToken);
        }

        return typeDescNode;
    }

    /**
     * Parse unary expression.
     * <p>
     * <code>
     * unary-expr := + expression | - expression | ~ expression | ! expression
     * </code>
     *
     * @param isRhsExpr
     * @return Unary expression node
     */
    private STNode parseUnaryExpression(boolean isRhsExpr, boolean isInConditionalExpr) {
        STNode unaryOperator = parseUnaryOperator();

        
        
        STNode expr = parseExpression(OperatorPrecedence.UNARY, isRhsExpr, false, isInConditionalExpr);
        return STNodeFactory.createUnaryExpressionNode(unaryOperator, expr);
    }

    /**
     * Parse unary operator.
     * <code>UnaryOperator := + | - | ~ | !</code>
     *
     * @return Parsed node
     */
    private STNode parseUnaryOperator() {
        STToken token = peek();
        if (isUnaryOperator(token.kind)) {
            return consume();
        } else {
            recover(token, ParserRuleContext.UNARY_OPERATOR);
            return parseUnaryOperator();
        }
    }

    /**
     * Check whether the given token kind is a unary operator.
     *
     * @param kind STToken kind
     * @return <code>true</code> if the token kind refers to a unary operator. <code>false</code> otherwise
     */
    private boolean isUnaryOperator(SyntaxKind kind) {
        switch (kind) {
            case PLUS_TOKEN:
            case MINUS_TOKEN:
            case NEGATION_TOKEN:
            case EXCLAMATION_MARK_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse array type descriptor.
     * <p>
     * <code>
     * array-type-descriptor := array-member-type-descriptor [ [ array-length ] ]
     * array-member-type-descriptor := type-descriptor
     * array-length :=
     * int-literal
     * | constant-reference-expr
     * | inferred-array-length
     * inferred-array-length := *
     * </code>
     * </p>
     *
     * @param memberTypeDesc
     * @return Parsed Node
     */
    private STNode parseArrayTypeDescriptor(STNode memberTypeDesc) {
        startContext(ParserRuleContext.ARRAY_TYPE_DESCRIPTOR);
        STNode openBracketToken = parseOpenBracket();
        STNode arrayLengthNode = parseArrayLength();
        STNode closeBracketToken = parseCloseBracket();
        endContext();
        return createArrayTypeDesc(memberTypeDesc, openBracketToken, arrayLengthNode, closeBracketToken);
    }

    private STNode createArrayTypeDesc(STNode memberTypeDesc, STNode openBracketToken, STNode arrayLengthNode,
                                       STNode closeBracketToken) {
        memberTypeDesc = validateForUsageOfVar(memberTypeDesc);
        if (arrayLengthNode != null) {
            switch (arrayLengthNode.kind) {
                case ASTERISK_LITERAL:
                case SIMPLE_NAME_REFERENCE:
                case QUALIFIED_NAME_REFERENCE:
                    break;
                case NUMERIC_LITERAL:
                    SyntaxKind numericLiteralKind = arrayLengthNode.childInBucket(0).kind;
                    if (numericLiteralKind == SyntaxKind.DECIMAL_INTEGER_LITERAL_TOKEN ||
                            numericLiteralKind == SyntaxKind.HEX_INTEGER_LITERAL_TOKEN) {
                        break;
                    }
                    
                default:
                    openBracketToken = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(openBracketToken,
                            arrayLengthNode, DiagnosticErrorCode.ERROR_INVALID_ARRAY_LENGTH);
                    arrayLengthNode = STNodeFactory.createEmptyNode();
            }
        }
        
        
        List<STNode> arrayDimensions = new ArrayList();
        if (memberTypeDesc.kind == SyntaxKind.ARRAY_TYPE_DESC) {
            STArrayTypeDescriptorNode innerArrayType = (STArrayTypeDescriptorNode) memberTypeDesc;
            STNode innerArrayDimensions = innerArrayType.dimensions;
            int dimensionCount = innerArrayDimensions.bucketCount();
            
            for (int i = 0; i < dimensionCount; i++) {
                arrayDimensions.add(innerArrayDimensions.childInBucket(i));
            }
            memberTypeDesc = innerArrayType.memberTypeDesc;
        }
        
        STNode arrayDimension = STNodeFactory.createArrayDimensionNode(openBracketToken, arrayLengthNode, 
                closeBracketToken);
        arrayDimensions.add(arrayDimension);
        STNode arrayDimensionNodeList = STNodeFactory.createNodeList(arrayDimensions);
        
        return STNodeFactory.createArrayTypeDescriptorNode(memberTypeDesc, arrayDimensionNodeList);
    }

    /**
     * Parse array length.
     * <p>
     * <code>
     * array-length :=
     * int-literal
     * | constant-reference-expr
     * | inferred-array-length
     * constant-reference-expr := variable-reference-expr
     * </code>
     * </p>
     *
     * @return Parsed array length
     */
    private STNode parseArrayLength() {
        STToken token = peek();
        switch (token.kind) {
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case ASTERISK_TOKEN:
                return parseBasicLiteral();
            case CLOSE_BRACKET_TOKEN:
                return STNodeFactory.createEmptyNode();
            
            case IDENTIFIER_TOKEN:
                return parseQualifiedIdentifier(ParserRuleContext.ARRAY_LENGTH);
            default:
                recover(token, ParserRuleContext.ARRAY_LENGTH);
                return parseArrayLength();
        }
    }

    /**
     * Parse annotations.
     * <p>
     * <i>Note: In the <a href="https:
     * annotations-list is specified as one-or-more annotations. And the usage is marked as
     * optional annotations-list. However, for the consistency of the tree, here we make the
     * annotation-list as zero-or-more annotations, and the usage is not-optional.</i>
     * <p>
     * <code>annots := annotation*</code>
     *
     * @return Parsed node
     */
    private STNode parseOptionalAnnotations() {
        startContext(ParserRuleContext.ANNOTATIONS);
        List<STNode> annotList = new ArrayList<>();
        STToken nextToken = peek();
        while (nextToken.kind == SyntaxKind.AT_TOKEN) {
            annotList.add(parseAnnotation());
            nextToken = peek();
        }

        endContext();
        return STNodeFactory.createNodeList(annotList);
    }

    /**
     * Parse annotation list with at least one annotation.
     *
     * @return Annotation list
     */
    private STNode parseAnnotations() {
        startContext(ParserRuleContext.ANNOTATIONS);
        List<STNode> annotList = new ArrayList<>();
        annotList.add(parseAnnotation());
        while (peek().kind == SyntaxKind.AT_TOKEN) {
            annotList.add(parseAnnotation());
        }

        endContext();
        return STNodeFactory.createNodeList(annotList);
    }

    /**
     * Parse annotation attachment.
     * <p>
     * <code>annotation := @ annot-tag-reference annot-value</code>
     *
     * @return Parsed node
     */
    private STNode parseAnnotation() {
        STNode atToken = parseAtToken();
        STNode annotReference;
        if (isPredeclaredIdentifier(peek().kind)) {
            annotReference = parseQualifiedIdentifier(ParserRuleContext.ANNOT_REFERENCE);
        } else {
            annotReference = STNodeFactory.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
            annotReference = STNodeFactory.createSimpleNameReferenceNode(annotReference);
        }

        STNode annotValue;
        if (peek().kind == SyntaxKind.OPEN_BRACE_TOKEN) {
            annotValue = parseMappingConstructorExpr();
        } else {
            annotValue = STNodeFactory.createEmptyNode();
        }
        return STNodeFactory.createAnnotationNode(atToken, annotReference, annotValue);
    }

    /**
     * Parse '@' token.
     *
     * @return Parsed node
     */
    private STNode parseAtToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.AT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.AT);
            return parseAtToken();
        }
    }

    /**
     * Parse metadata. Meta data consist of optional doc string and
     * an annotations list.
     * <p>
     * <code>metadata := [DocumentationString] annots</code>
     *
     * @return Parse node
     */
    private STNode parseMetaData() {
        STNode docString;
        STNode annotations;
        switch (peek().kind) {
            case DOCUMENTATION_STRING:
                docString = parseMarkdownDocumentation();
                annotations = parseOptionalAnnotations();
                break;
            case AT_TOKEN:
                docString = STNodeFactory.createEmptyNode();
                annotations = parseOptionalAnnotations();
                break;
            default:
                return STNodeFactory.createEmptyNode();
        }

        return createMetadata(docString, annotations);
    }

    /**
     * Create metadata node.
     *
     * @return A metadata node
     */
    private STNode createMetadata(STNode docString, STNode annotations) {
        if (annotations == null && docString == null) {
            return STNodeFactory.createEmptyNode();
        } else {
            return STNodeFactory.createMetadataNode(docString, annotations);
        }
    }

    /**
     * Parse type test expression.
     * <code>
     * type-test-expr := expression (is | !is) type-descriptor
     * </code>
     *
     * @param lhsExpr Preceding expression of the is expression
     * @return Is expression node
     */
    private STNode parseTypeTestExpression(STNode lhsExpr, boolean isInConditionalExpr) {
        STNode isOrNotIsKeyword = parseIsOrNotIsKeyword();
        STNode typeDescriptor = parseTypeDescriptorInExpression(isInConditionalExpr);
        return STNodeFactory.createTypeTestExpressionNode(lhsExpr, isOrNotIsKeyword, typeDescriptor);
    }

    /**
     * Parse `is` keyword or `!is` keyword.
     *
     * @return is-keyword or not-is-keyword node
     */
    private STNode parseIsOrNotIsKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.IS_KEYWORD ||
                token.kind == SyntaxKind.NOT_IS_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.IS_KEYWORD);
            return parseIsOrNotIsKeyword();
        }
    }

    /**
     * Parse local type definition statement statement.
     * <code>ocal-type-defn-stmt := [annots] type identifier type-descriptor ;</code>
     *
     * @return local type definition statement statement
     */
    private STNode parseLocalTypeDefinitionStatement(STNode annots) {
        startContext(ParserRuleContext.LOCAL_TYPE_DEFINITION_STMT);
        STNode typeKeyword = parseTypeKeyword();
        STNode typeName = parseTypeName();
        STNode typeDescriptor = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TYPE_DEF);
        STNode semicolon = parseSemicolon();
        endContext();
        return STNodeFactory.createLocalTypeDefinitionStatementNode(annots, typeKeyword, typeName, typeDescriptor,
                semicolon);
    }

    /**
     * Parse statement which is only consists of an action or expression.
     *
     * @param annots        Annotations
     * @return Statement node
     */
    private STNode parseExpressionStatement(STNode annots) {
        startContext(ParserRuleContext.EXPRESSION_STATEMENT);
        STNode expression = parseActionOrExpressionInLhs(annots);
        return getExpressionAsStatement(expression);
    }

    /**
     * Parse statements that starts with an expression.
     *
     * @return Statement node
     */
    private STNode parseStatementStartWithExpr(STNode annots) {
        startContext(ParserRuleContext.AMBIGUOUS_STMT);
        STNode expr = parseActionOrExpressionInLhs(annots);
        return parseStatementStartWithExprRhs(expr);
    }

    /**
     * Parse the component followed by the expression, at the beginning of a statement.
     *
     * @param expression Action or expression in LHS
     * @return Statement node
     */
    private STNode parseStatementStartWithExprRhs(STNode expression) {
        SyntaxKind nextTokenKind = peek().kind;
        if (isAction(expression) || nextTokenKind == SyntaxKind.SEMICOLON_TOKEN) {
            return getExpressionAsStatement(expression);
        }

        switch (nextTokenKind) {
            case EQUAL_TOKEN:
                switchContext(ParserRuleContext.ASSIGNMENT_STMT);
                return parseAssignmentStmtRhs(expression);
            case IDENTIFIER_TOKEN:
            default:
                
                if (isCompoundAssignment(nextTokenKind)) {
                    return parseCompoundAssignmentStmtRhs(expression);
                }

                ParserRuleContext context;
                if (isPossibleExpressionStatement(expression)) {
                    context = ParserRuleContext.EXPR_STMT_RHS;
                } else {
                    context = ParserRuleContext.STMT_START_WITH_EXPR_RHS;
                }

                recover(peek(), context);
                return parseStatementStartWithExprRhs(expression);
        }
    }

    private boolean isPossibleExpressionStatement(STNode expression) {
        switch (expression.kind) {
            case METHOD_CALL:
            case FUNCTION_CALL:
            case CHECK_EXPRESSION:
            case REMOTE_METHOD_CALL_ACTION:
            case CHECK_ACTION:
            case BRACED_ACTION:
            case START_ACTION:
            case TRAP_ACTION:
            case FLUSH_ACTION:
            case ASYNC_SEND_ACTION:
            case SYNC_SEND_ACTION:
            case RECEIVE_ACTION:
            case WAIT_ACTION:
            case QUERY_ACTION:
            case COMMIT_ACTION:
                return true;
            default:
                return false;
        }
    }

    private STNode getExpressionAsStatement(STNode expression) {
        switch (expression.kind) {
            case METHOD_CALL:
            case FUNCTION_CALL:
                return parseCallStatement(expression);
            case CHECK_EXPRESSION:
                return parseCheckStatement(expression);
            case REMOTE_METHOD_CALL_ACTION:
            case CHECK_ACTION:
            case BRACED_ACTION:
            case START_ACTION:
            case TRAP_ACTION:
            case FLUSH_ACTION:
            case ASYNC_SEND_ACTION:
            case SYNC_SEND_ACTION:
            case RECEIVE_ACTION:
            case WAIT_ACTION:
            case QUERY_ACTION:
            case COMMIT_ACTION:
            case CLIENT_RESOURCE_ACCESS_ACTION:
                return parseActionStatement(expression);
            default:
                
                STNode semicolon = parseSemicolon();
                endContext();
                expression = getExpression(expression);
                STNode exprStmt = STNodeFactory.createExpressionStatementNode(SyntaxKind.INVALID_EXPRESSION_STATEMENT,
                        expression, semicolon);
                exprStmt = SyntaxErrors.addDiagnostic(exprStmt, DiagnosticErrorCode.ERROR_INVALID_EXPRESSION_STATEMENT);
                return exprStmt;
        }
    }

    private STNode parseArrayTypeDescriptorNode(STIndexedExpressionNode indexedExpr) {
        STNode memberTypeDesc = getTypeDescFromExpr(indexedExpr.containerExpression);
        STNodeList lengthExprs = (STNodeList) indexedExpr.keyExpression;
        if (lengthExprs.isEmpty()) {
            return createArrayTypeDesc(memberTypeDesc, indexedExpr.openBracket, STNodeFactory.createEmptyNode(),
                    indexedExpr.closeBracket);
        }

        
        STNode lengthExpr = lengthExprs.get(0);
        switch (lengthExpr.kind) {
            case SIMPLE_NAME_REFERENCE:
                STSimpleNameReferenceNode nameRef = (STSimpleNameReferenceNode) lengthExpr;
                if (nameRef.name.isMissing()) {
                    return createArrayTypeDesc(memberTypeDesc, indexedExpr.openBracket, STNodeFactory.createEmptyNode(),
                            indexedExpr.closeBracket);
                }
                break;
            case ASTERISK_LITERAL:
            case QUALIFIED_NAME_REFERENCE:
                break;
            case NUMERIC_LITERAL:
                SyntaxKind innerChildKind = lengthExpr.childInBucket(0).kind;
                if (innerChildKind == SyntaxKind.DECIMAL_INTEGER_LITERAL_TOKEN ||
                        innerChildKind == SyntaxKind.HEX_INTEGER_LITERAL_TOKEN) {
                    break;
                }
                
            default:
                STNode newOpenBracketWithDiagnostics = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(
                        indexedExpr.openBracket, lengthExpr, DiagnosticErrorCode.ERROR_INVALID_ARRAY_LENGTH);
                indexedExpr = indexedExpr.replace(indexedExpr.openBracket, newOpenBracketWithDiagnostics);
                lengthExpr = STNodeFactory.createEmptyNode();
        }

        return createArrayTypeDesc(memberTypeDesc, indexedExpr.openBracket, lengthExpr, indexedExpr.closeBracket);
    }

    /**
     * <p>
     * Parse call statement, given the call expression.
     * </p>
     * <code>
     * call-stmt := call-expr ;
     * <br/>
     * call-expr := function-call-expr | method-call-expr | checking-keyword call-expr
     * </code>
     *
     * @param expression Call expression associated with the call statement
     * @return Call statement node
     */
    private STNode parseCallStatement(STNode expression) {
        return parseCallStatementOrCheckStatement(expression);
    }

    /**
     * <p>
     * Parse checking statement.
     * </p>
     * <code>
     * checking-stmt := checking-expr ;
     * <br/>
     * checking-expr := checking-keyword expr ;
     * </code>
     *
     * @param expression Checking expression associated with the checking statement
     * @return Checking statement node
     */
    private STNode parseCheckStatement(STNode expression) {
        return parseCallStatementOrCheckStatement(expression);
    }

    private STNode parseCallStatementOrCheckStatement(STNode expression) {
        STNode semicolon = parseSemicolon();
        endContext();
        return STNodeFactory.createExpressionStatementNode(SyntaxKind.CALL_STATEMENT, expression, semicolon);
    }

    private STNode parseActionStatement(STNode action) {
        STNode semicolon = parseSemicolon();
        endContext();
        return STNodeFactory.createExpressionStatementNode(SyntaxKind.ACTION_STATEMENT, action, semicolon);
    }

    /**
     * Parse client resource access action, given the starting expression.
     * <br/><br/>
     * <code>
     * client-resource-access-action := expression "->" "/" [resource-access-path] ["." method-name] ["(" arg-list ")"]
     * </code>
     * 
     * @param expression Expression
     * @param rightArrow Right arrow token
     * @param slashToken Slash token
     * @return Parsed node
     */
    private STNode parseClientResourceAccessAction(STNode expression, STNode rightArrow, STNode slashToken,
                                                   boolean isRhsExpr, boolean isInMatchGuard) {
        startContext(ParserRuleContext.CLIENT_RESOURCE_ACCESS_ACTION);
        
        STNode resourceAccessPath = parseOptionalResourceAccessPath(isRhsExpr, isInMatchGuard);
        STNode resourceAccessMethodDot = parseOptionalResourceAccessMethodDot(isRhsExpr, isInMatchGuard);
        STNode resourceAccessMethodName = STNodeFactory.createEmptyNode();
        if (resourceAccessMethodDot != null) {
            resourceAccessMethodName = STNodeFactory.createSimpleNameReferenceNode(parseFunctionName());
        }
        
        STNode resourceMethodCallArgList = parseOptionalResourceAccessActionArgList(isRhsExpr, isInMatchGuard);
        endContext();
        
        return STNodeFactory.createClientResourceAccessActionNode(expression, rightArrow, slashToken, 
                resourceAccessPath, resourceAccessMethodDot, resourceAccessMethodName, resourceMethodCallArgList);
    }
    
    private STNode parseOptionalResourceAccessPath(boolean isRhsExpr, boolean isInMatchGuard) {
        STNode resourceAccessPath = STNodeFactory.createEmptyNodeList();
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
            case OPEN_BRACKET_TOKEN:
                resourceAccessPath = parseResourceAccessPath(isRhsExpr, isInMatchGuard);
                break;
            case DOT_TOKEN:
            case OPEN_PAREN_TOKEN:
                break;
            default:
                if (isEndOfActionOrExpression(nextToken, isRhsExpr, isInMatchGuard)) {
                    break;
                }

                recover(nextToken, ParserRuleContext.OPTIONAL_RESOURCE_ACCESS_PATH);
                return parseOptionalResourceAccessPath(isRhsExpr, isInMatchGuard);
        }
        return  resourceAccessPath;
    }
    
    private STNode parseOptionalResourceAccessMethodDot(boolean isRhsExpr, boolean isInMatchGuard) {
        STNode dotToken = STNodeFactory.createEmptyNode();
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case DOT_TOKEN:
                dotToken = consume();
                break;
            case OPEN_PAREN_TOKEN:
                break;
            default:
                if (isEndOfActionOrExpression(nextToken, isRhsExpr, isInMatchGuard)) {
                    break;
                }

                recover(nextToken, ParserRuleContext.OPTIONAL_RESOURCE_ACCESS_METHOD);
                return parseOptionalResourceAccessMethodDot(isRhsExpr, isInMatchGuard);
        }
        
        return dotToken;
    }
    
    private STNode parseOptionalResourceAccessActionArgList(boolean isRhsExpr, boolean isInMatchGuard) {
        STNode argList = STNodeFactory.createEmptyNode();
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                argList = parseParenthesizedArgList();
                break;
            default:
                if (isEndOfActionOrExpression(nextToken, isRhsExpr, isInMatchGuard)) {
                    break;
                }

                recover(nextToken, ParserRuleContext.OPTIONAL_RESOURCE_ACCESS_ACTION_ARG_LIST);
                return parseOptionalResourceAccessActionArgList(isRhsExpr, isInMatchGuard);
        }
        
        return argList;
    }

    /**
     * Parse resource access path.
     * <br/><br/>
     * <code>
     * resource-access-path :=
     *    resource-access-segments ["/" resource-access-rest-segment]
     *    | resource-access-rest-segment
     * <br/><br/>
     * resource-access-segments := resource-access-segment ("/" resource-access-segment ")*
     * <br/><br/>
     * resource-access-segment := resource-path-segment-name | computed-resource-access-segment
     * <br/><br/>
     * resource-path-segment-name := identifier
     * </code>
     * @return
     */
    private STNode parseResourceAccessPath(boolean isRhsExpr, boolean isInMatchGuard) {
        List<STNode> pathSegmentList = new ArrayList<>();
        
        STNode pathSegment = parseResourceAccessSegment();
        pathSegmentList.add(pathSegment);

        STNode leadingSlash;
        STNode previousPathSegmentNode = pathSegment;
        while (!isEndOfResourceAccessPathSegments(peek(), isRhsExpr, isInMatchGuard)) {
            leadingSlash = parseResourceAccessSegmentRhs(isRhsExpr, isInMatchGuard);
            if (leadingSlash == null) {
                break;
            }
            
            pathSegment = parseResourceAccessSegment();
            
            if (previousPathSegmentNode.kind == SyntaxKind.RESOURCE_ACCESS_REST_SEGMENT) {
                updateLastNodeInListWithInvalidNode(pathSegmentList, leadingSlash, null);
                updateLastNodeInListWithInvalidNode(pathSegmentList, pathSegment, 
                        DiagnosticErrorCode.RESOURCE_ACCESS_SEGMENT_IS_NOT_ALLOWED_AFTER_REST_SEGMENT);
            } else {
                pathSegmentList.add(leadingSlash);
                pathSegmentList.add(pathSegment);
                previousPathSegmentNode = pathSegment;
            }
        }

        return STNodeFactory.createNodeList(pathSegmentList);
    }
    
    private STNode parseResourceAccessSegment() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
                return consume();
            case OPEN_BRACKET_TOKEN:
                return parseComputedOrResourceAccessRestSegment(consume());
            default:
                recover(nextToken, ParserRuleContext.RESOURCE_ACCESS_PATH_SEGMENT);
                return parseResourceAccessSegment();
        }
    }

    /**
     * Parse computed resource segment or resource access rest segment.
     * <code>
     * <br/>
     * computed-resource-access-segment := "[" expression "]"
     * <br/>
     * resource-access-rest-segment := "[" "..." expression "]"
     * </code>
     * @param openBracket Open bracket token
     * @return Parsed node
     */
    private STNode parseComputedOrResourceAccessRestSegment(STNode openBracket) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case ELLIPSIS_TOKEN:
                STNode ellipsisToken = consume();
                STNode expression = parseExpression();
                STNode closeBracketToken = parseCloseBracket();
                return STNodeFactory.createResourceAccessRestSegmentNode(openBracket, ellipsisToken,
                        expression, closeBracketToken);
            default:
                if (isValidExprStart(nextToken.kind)) {
                    expression = parseExpression();
                    closeBracketToken = parseCloseBracket();
                    return STNodeFactory.createComputedResourceAccessSegmentNode(openBracket, expression, 
                            closeBracketToken);
                }
                
                recover(nextToken, ParserRuleContext.COMPUTED_SEGMENT_OR_REST_SEGMENT);
                return parseComputedOrResourceAccessRestSegment(openBracket);
        }
    }

    /**
     * Parse resource access segment end.
     *
     * @return Parsed node
     */
    private STNode parseResourceAccessSegmentRhs(boolean isRhsExpr, boolean isInMatchGuard) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case SLASH_TOKEN:
                return consume();
            default:
                if (isEndOfResourceAccessPathSegments(nextToken, isRhsExpr, isInMatchGuard)) {
                    return null;
                }
                
                recover(nextToken, ParserRuleContext.RESOURCE_ACCESS_SEGMENT_RHS);
                return parseResourceAccessSegmentRhs(isRhsExpr, isInMatchGuard);
        }
    }
    
    private boolean isEndOfResourceAccessPathSegments(STToken nextToken, boolean isRhsExpr, boolean isInMatchGuard) {
        switch (nextToken.kind) {
            case DOT_TOKEN:
            case OPEN_PAREN_TOKEN:
                return true;
            default:
                return isEndOfActionOrExpression(nextToken, isRhsExpr, isInMatchGuard);
        }
    }
    
    private STNode parseRemoteMethodCallOrClientResourceAccessOrAsyncSendAction(STNode expression, boolean isRhsExpr,
                                                                                boolean isInMatchGuard) {
        STNode rightArrow = parseRightArrow();
        return parseClientResourceAccessOrAsyncSendActionRhs(expression, rightArrow, isRhsExpr, isInMatchGuard);
    }

    private STNode parseClientResourceAccessOrAsyncSendActionRhs(STNode expression, STNode rightArrow,
                                                                 boolean isRhsExpr, boolean isInMatchGuard) {
        STNode name;
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case FUNCTION_KEYWORD:
                STNode functionKeyword = consume();
                name = STNodeFactory.createSimpleNameReferenceNode(functionKeyword);
                return parseAsyncSendAction(expression, rightArrow, name);
            case CONTINUE_KEYWORD:
            case COMMIT_KEYWORD:
                name = getKeywordAsSimpleNameRef();
                break;
            case SLASH_TOKEN:
                STNode slashToken = consume();
                return parseClientResourceAccessAction(expression, rightArrow, slashToken, isRhsExpr, isInMatchGuard);
            default:
                if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN) {
                    
                    
                    
                    STToken nextNextToken = getNextNextToken();
                    if (nextNextToken.kind == SyntaxKind.OPEN_PAREN_TOKEN ||
                            isEndOfActionOrExpression(nextNextToken, isRhsExpr, isInMatchGuard) ||
                            nextToken.isMissing()) {
                        name = STNodeFactory.createSimpleNameReferenceNode(parseFunctionName());
                        break;
                    }
                }
                
                STToken token = peek();
                Solution solution = recover(token, ParserRuleContext.REMOTE_OR_RESOURCE_CALL_OR_ASYNC_SEND_RHS);
                if (solution.action == Action.KEEP) {
                    
                    name = STNodeFactory.createSimpleNameReferenceNode(parseFunctionName());
                    break;
                }
                return parseClientResourceAccessOrAsyncSendActionRhs(expression, rightArrow, isRhsExpr, isInMatchGuard);
        }

        return parseRemoteCallOrAsyncSendEnd(expression, rightArrow, name);
    }

    private STNode parseRemoteCallOrAsyncSendEnd(STNode expression, STNode rightArrow, STNode name) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                return parseRemoteMethodCallAction(expression, rightArrow, name);
            case SEMICOLON_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case OPEN_BRACE_TOKEN:
            case COMMA_TOKEN:
            case FROM_KEYWORD:
            case JOIN_KEYWORD:
            case ON_KEYWORD:
            case LET_KEYWORD:
            case WHERE_KEYWORD:
            case ORDER_KEYWORD:
            case LIMIT_KEYWORD:
            case SELECT_KEYWORD:
                return parseAsyncSendAction(expression, rightArrow, name);
            default:
                if (isGroupOrCollectKeyword(nextToken)) {
                    return parseAsyncSendAction(expression, rightArrow, name);
                }

                recover(peek(), ParserRuleContext.REMOTE_CALL_OR_ASYNC_SEND_END);
                return parseRemoteCallOrAsyncSendEnd(expression, rightArrow, name);
        }
    }

    private STNode parseAsyncSendAction(STNode expression, STNode rightArrow, STNode peerWorker) {
        return STNodeFactory.createAsyncSendActionNode(expression, rightArrow, peerWorker);
    }


    /**
     * Parse remote method call action.
     * <p>
     * <code>
     * remote-method-call-action := expression -> method-name ( arg-list )
     * <br/>
     * async-send-action := expression -> peer-worker ;
     * </code>
     *
     * @param expression LHS expression
     * @param rightArrow  right arrow token
     * @param name remote method name
     * @return
     */
    private STNode parseRemoteMethodCallAction(STNode expression, STNode rightArrow, STNode name) {
        STNode openParenToken = parseArgListOpenParenthesis();
        STNode arguments = parseArgsList();
        STNode closeParenToken = parseArgListCloseParenthesis();
        return STNodeFactory.createRemoteMethodCallActionNode(expression, rightArrow, name, openParenToken, arguments,
                closeParenToken);
    }

    /**
     * Parse right arrow (<code>-></code>) token.
     *
     * @return Parsed node
     */
    private STNode parseRightArrow() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.RIGHT_ARROW_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.RIGHT_ARROW);
            return parseRightArrow();
        }
    }

    /**
     * Parse map type descriptor.
     * map-type-descriptor := `map` type-parameter
     *
     * @return Parsed node
     */
    private STNode parseMapTypeDescriptor(STNode mapKeyword) {
        STNode typeParameter = parseTypeParameter();
        return STNodeFactory.createMapTypeDescriptorNode(mapKeyword, typeParameter);
    }

    /**
     * Parse parameterized type descriptor.
     * parameterized-type-descriptor := `typedesc` [type-parameter]
     * <br/>&nbsp;| `future` [type-parameter]
     * <br/>&nbsp;| `xml` [type-parameter]
     * <br/>&nbsp;| `error` [type-parameter]
     *
     * @return Parsed node
     */
    private STNode parseParameterizedTypeDescriptor(STNode keywordToken) {
        STNode typeParamNode;
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.LT_TOKEN) {
            typeParamNode = parseTypeParameter();
        } else {
            typeParamNode = STNodeFactory.createEmptyNode();
        }

        SyntaxKind parameterizedTypeDescKind = getParameterizedTypeDescKind(keywordToken);
        return STNodeFactory.createParameterizedTypeDescriptorNode(parameterizedTypeDescKind, keywordToken,
                typeParamNode);
    }

    private SyntaxKind getParameterizedTypeDescKind(STNode keywordToken) {
        switch (keywordToken.kind) {
            case TYPEDESC_KEYWORD:
                return SyntaxKind.TYPEDESC_TYPE_DESC;
            case FUTURE_KEYWORD:
                return SyntaxKind.FUTURE_TYPE_DESC;
            case XML_KEYWORD:
                return SyntaxKind.XML_TYPE_DESC;
            case ERROR_KEYWORD:
            default:
                return SyntaxKind.ERROR_TYPE_DESC;
        }
    }
    
    /**
     * Parse <code> < </code> token.
     *
     * @return Parsed node
     */
    private STNode parseGTToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.GT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.GT);
            return parseGTToken();
        }
    }

    /**
     * Parse <code> > </code> token.
     *
     * @return Parsed node
     */
    private STNode parseLTToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.LT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.LT);
            return parseLTToken();
        }
    }

    /**
     * Parse nil literal. Here nil literal is only referred to ( ).
     *
     * @return Parsed node
     */
    private STNode parseNilLiteral() {
        startContext(ParserRuleContext.NIL_LITERAL);
        STNode openParenthesisToken = parseOpenParenthesis();
        STNode closeParenthesisToken = parseCloseParenthesis();
        endContext();
        return STNodeFactory.createNilLiteralNode(openParenthesisToken, closeParenthesisToken);
    }

    /**
     * Parse annotation declaration, given the qualifier.
     *
     * @param metadata     Metadata
     * @param qualifier    Qualifier that precedes the listener declaration
     * @param constKeyword Const keyword
     * @return Parsed node
     */
    private STNode parseAnnotationDeclaration(STNode metadata, STNode qualifier, STNode constKeyword) {
        startContext(ParserRuleContext.ANNOTATION_DECL);
        STNode annotationKeyword = parseAnnotationKeyword();
        STNode annotDecl = parseAnnotationDeclFromType(metadata, qualifier, constKeyword, annotationKeyword);
        endContext();
        return annotDecl;
    }

    /**
     * Parse annotation keyword.
     *
     * @return Parsed node
     */
    private STNode parseAnnotationKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ANNOTATION_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ANNOTATION_KEYWORD);
            return parseAnnotationKeyword();
        }
    }

    /**
     * Parse the components that follows after the annotation keyword of a annotation declaration.
     *
     * @param metadata          Metadata
     * @param qualifier         Qualifier that precedes the constant decl
     * @param constKeyword      Const keyword
     * @param annotationKeyword
     * @return Parsed node
     */
    private STNode parseAnnotationDeclFromType(STNode metadata, STNode qualifier, STNode constKeyword,
                                               STNode annotationKeyword) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
                return parseAnnotationDeclWithOptionalType(metadata, qualifier, constKeyword, annotationKeyword);
            default:
                if (isTypeStartingToken(nextToken.kind)) {
                    break;
                }

                recover(peek(), ParserRuleContext.ANNOT_DECL_OPTIONAL_TYPE);
                return parseAnnotationDeclFromType(metadata, qualifier, constKeyword, annotationKeyword);
        }

        STNode typeDesc = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_ANNOTATION_DECL);
        STNode annotTag = parseAnnotationTag();
        return parseAnnotationDeclAttachPoints(metadata, qualifier, constKeyword, annotationKeyword, typeDesc,
                annotTag);
    }

    /**
     * Parse annotation tag.
     * <p>
     * <code>annot-tag := identifier</code>
     *
     * @return
     */
    private STNode parseAnnotationTag() {
        STToken token = peek();
        if (token.kind == SyntaxKind.IDENTIFIER_TOKEN) {
            return consume();
        } else {
            recover(peek(), ParserRuleContext.ANNOTATION_TAG);
            return parseAnnotationTag();
        }
    }

    private STNode parseAnnotationDeclWithOptionalType(STNode metadata, STNode qualifier, STNode constKeyword,
                                                       STNode annotationKeyword) {
        
        
        STNode typeDescOrAnnotTag = parseQualifiedIdentifier(ParserRuleContext.ANNOT_DECL_OPTIONAL_TYPE);
        if (typeDescOrAnnotTag.kind == SyntaxKind.QUALIFIED_NAME_REFERENCE) {
            STNode annotTag = parseAnnotationTag();
            return parseAnnotationDeclAttachPoints(metadata, qualifier, constKeyword, annotationKeyword,
                    typeDescOrAnnotTag, annotTag);
        }

        
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN || isValidTypeContinuationToken(nextToken)) {
            STNode typeDesc = parseComplexTypeDescriptor(typeDescOrAnnotTag,
                    ParserRuleContext.TYPE_DESC_IN_ANNOTATION_DECL, false);
            STNode annotTag = parseAnnotationTag();
            return parseAnnotationDeclAttachPoints(metadata, qualifier, constKeyword, annotationKeyword, typeDesc,
                    annotTag);
        }

        STNode annotTag = ((STSimpleNameReferenceNode) typeDescOrAnnotTag).name;
        return parseAnnotationDeclRhs(metadata, qualifier, constKeyword, annotationKeyword, annotTag);
    }

    /**
     * Parse the component that follows the first identifier in an annotation decl. The identifier
     * can be either the type-name (a user defined type) or the annot-tag, where the type-name
     * is not present.
     *
     * @param metadata           Metadata
     * @param qualifier          Qualifier that precedes the annotation decl
     * @param constKeyword       Const keyword
     * @param annotationKeyword  Annotation keyword
     * @param typeDescOrAnnotTag Identifier that follows the annotation-keyword
     * @return Parsed node
     */
    private STNode parseAnnotationDeclRhs(STNode metadata, STNode qualifier, STNode constKeyword,
                                          STNode annotationKeyword, STNode typeDescOrAnnotTag) {
        STToken nextToken = peek();
        STNode typeDesc;
        STNode annotTag;
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
                typeDesc = typeDescOrAnnotTag;
                annotTag = parseAnnotationTag();
                break;
            case SEMICOLON_TOKEN:
            case ON_KEYWORD:
                typeDesc = STNodeFactory.createEmptyNode();
                annotTag = typeDescOrAnnotTag;
                break;
            default:
                recover(peek(), ParserRuleContext.ANNOT_DECL_RHS);
                return parseAnnotationDeclRhs(metadata, qualifier, constKeyword, annotationKeyword, typeDescOrAnnotTag);
        }

        return parseAnnotationDeclAttachPoints(metadata, qualifier, constKeyword, annotationKeyword, typeDesc,
                annotTag);
    }

    private STNode parseAnnotationDeclAttachPoints(STNode metadata, STNode qualifier, STNode constKeyword,
                                                   STNode annotationKeyword, STNode typeDesc, STNode annotTag) {
        STNode onKeyword;
        STNode attachPoints;
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case SEMICOLON_TOKEN:
                onKeyword = STNodeFactory.createEmptyNode();
                attachPoints = STNodeFactory.createEmptyNodeList();
                break;
            case ON_KEYWORD:
                onKeyword = parseOnKeyword();
                attachPoints = parseAnnotationAttachPoints();
                onKeyword = cloneWithDiagnosticIfListEmpty(attachPoints, onKeyword,
                        DiagnosticErrorCode.ERROR_MISSING_ANNOTATION_ATTACH_POINT);
                break;
            default:
                recover(peek(), ParserRuleContext.ANNOT_OPTIONAL_ATTACH_POINTS);
                return parseAnnotationDeclAttachPoints(metadata, qualifier, constKeyword, annotationKeyword, typeDesc,
                        annotTag);
        }

        STNode semicolonToken = parseSemicolon();
        return STNodeFactory.createAnnotationDeclarationNode(metadata, qualifier, constKeyword, annotationKeyword,
                typeDesc, annotTag, onKeyword, attachPoints, semicolonToken);
    }

    /**
     * Parse annotation attach points.
     * <p>
     * <code>
     * annot-attach-points := annot-attach-point (, annot-attach-point)*
     * <br/><br/>
     * annot-attach-point := dual-attach-point | source-only-attach-point
     * <br/><br/>
     * dual-attach-point := [source] dual-attach-point-ident
     * <br/><br/>
     * dual-attach-point-ident :=
     * type
     * | class
     * | [object|service remote] function
     * | parameter
     * | return
     * | service
     * | [object|record] field
     * <br/><br/>
     * source-only-attach-point := source source-only-attach-point-ident
     * <br/><br/>
     * source-only-attach-point-ident :=
     * annotation
     * | external
     * | var
     * | const
     * | listener
     * | worker
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseAnnotationAttachPoints() {
        startContext(ParserRuleContext.ANNOT_ATTACH_POINTS_LIST);
        List<STNode> attachPoints = new ArrayList<>();

        STToken nextToken = peek();
        if (isEndAnnotAttachPointList(nextToken.kind)) {
            endContext();
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode attachPoint = parseAnnotationAttachPoint();
        attachPoints.add(attachPoint);

        
        nextToken = peek();
        STNode leadingComma;
        while (!isEndAnnotAttachPointList(nextToken.kind)) {
            leadingComma = parseAttachPointEnd();
            if (leadingComma == null) {
                break;
            }
            attachPoints.add(leadingComma);

            
            attachPoint = parseAnnotationAttachPoint();
            if (attachPoint == null) {
                STToken missingAttachPointIdent = SyntaxErrors.createMissingToken(SyntaxKind.TYPE_KEYWORD);
                STNode identList = STNodeFactory.createNodeList(missingAttachPointIdent);
                attachPoint = STNodeFactory.createAnnotationAttachPointNode(STNodeFactory.createEmptyNode(), identList);
                attachPoint = SyntaxErrors.addDiagnostic(attachPoint,
                        DiagnosticErrorCode.ERROR_MISSING_ANNOTATION_ATTACH_POINT);
                attachPoints.add(attachPoint);
                break;
            }

            attachPoints.add(attachPoint);
            nextToken = peek();
        }
        
        if (attachPoint.lastToken().isMissing() && this.tokenReader.peek().kind == SyntaxKind.IDENTIFIER_TOKEN &&
                !this.tokenReader.head().hasTrailingNewline()) {
            
            
            STToken nextNonVirtualToken = this.tokenReader.read();
            updateLastNodeInListWithInvalidNode(attachPoints, nextNonVirtualToken,
                    DiagnosticErrorCode.ERROR_INVALID_TOKEN, nextNonVirtualToken.text());
        }

        endContext();
        return STNodeFactory.createNodeList(attachPoints);
    }

    /**
     * Parse annotation attach point end.
     *
     * @return Parsed node
     */
    private STNode parseAttachPointEnd() {
        switch (peek().kind) {
            case SEMICOLON_TOKEN:
                
                return null;
            case COMMA_TOKEN:
                return consume();
            default:
                recover(peek(), ParserRuleContext.ATTACH_POINT_END);
                return parseAttachPointEnd();
        }
    }

    private boolean isEndAnnotAttachPointList(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case EOF_TOKEN:
            case SEMICOLON_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse annotation attach point.
     *
     * @return Parsed node
     */
    private STNode parseAnnotationAttachPoint() {
        switch (peek().kind) {
            case EOF_TOKEN:
                return null;

            
            case ANNOTATION_KEYWORD:
            case EXTERNAL_KEYWORD:
            case VAR_KEYWORD:
            case CONST_KEYWORD:
            case LISTENER_KEYWORD:
            case WORKER_KEYWORD:
                

            case SOURCE_KEYWORD:
                STNode sourceKeyword = parseSourceKeyword();
                return parseAttachPointIdent(sourceKeyword);

            
            case OBJECT_KEYWORD:
            case TYPE_KEYWORD:
            case FUNCTION_KEYWORD:
            case PARAMETER_KEYWORD:
            case RETURN_KEYWORD:
            case SERVICE_KEYWORD:
            case FIELD_KEYWORD:
            case RECORD_KEYWORD:
            case CLASS_KEYWORD:
                sourceKeyword = STNodeFactory.createEmptyNode();
                STNode firstIdent = consume();
                return parseDualAttachPointIdent(sourceKeyword, firstIdent);
            default:
                recover(peek(), ParserRuleContext.ATTACH_POINT);
                return parseAnnotationAttachPoint();
        }
    }

    /**
     * Parse source keyword.
     *
     * @return Parsed node
     */
    private STNode parseSourceKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.SOURCE_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.SOURCE_KEYWORD);
            return parseSourceKeyword();
        }
    }

    /**
     * Parse attach point ident gievn.
     * <p>
     * <code>
     * source-only-attach-point-ident := annotation | external | var | const | listener | worker
     * <br/><br/>
     * dual-attach-point-ident := type | class | [object|service remote] function | parameter
     * | return | service | [object|record] field
     * </code>
     *
     * @param sourceKeyword Source keyword
     * @return Parsed node
     */
    private STNode parseAttachPointIdent(STNode sourceKeyword) {
        switch (peek().kind) {
            case ANNOTATION_KEYWORD:
            case EXTERNAL_KEYWORD:
            case VAR_KEYWORD:
            case CONST_KEYWORD:
            case LISTENER_KEYWORD:
            case WORKER_KEYWORD:
                STNode firstIdent = consume();
                STNode identList = STNodeFactory.createNodeList(firstIdent);
                return STNodeFactory.createAnnotationAttachPointNode(sourceKeyword, identList);
            case OBJECT_KEYWORD:
            case RESOURCE_KEYWORD:
            case RECORD_KEYWORD:
            case TYPE_KEYWORD:
            case FUNCTION_KEYWORD:
            case PARAMETER_KEYWORD:
            case RETURN_KEYWORD:
            case SERVICE_KEYWORD:
            case FIELD_KEYWORD:
            case CLASS_KEYWORD:
                firstIdent = consume();
                return parseDualAttachPointIdent(sourceKeyword, firstIdent);
            default:
                recover(peek(), ParserRuleContext.ATTACH_POINT_IDENT);
                return parseAttachPointIdent(sourceKeyword);
        }
    }

    /**
     * Parse dual-attach-point ident.
     *
     * @param sourceKeyword Source keyword
     * @param firstIdent    first part of the dual attach-point
     * @return Parsed node
     */
    private STNode parseDualAttachPointIdent(STNode sourceKeyword, STNode firstIdent) {
        STNode secondIdent;
        switch (firstIdent.kind) {
            case OBJECT_KEYWORD:
                secondIdent = parseIdentAfterObjectIdent();
                break;
            case RESOURCE_KEYWORD:
                secondIdent = parseFunctionIdent();
                break;
            case RECORD_KEYWORD:
                secondIdent = parseFieldIdent();
                break;
            case SERVICE_KEYWORD:
                return parseServiceAttachPoint(sourceKeyword, firstIdent);
            case TYPE_KEYWORD:
            case FUNCTION_KEYWORD:
            case PARAMETER_KEYWORD:
            case RETURN_KEYWORD:
            case FIELD_KEYWORD:
            case CLASS_KEYWORD:
            default: 
                STNode identList = STNodeFactory.createNodeList(firstIdent);
                return STNodeFactory.createAnnotationAttachPointNode(sourceKeyword, identList);
        }

        STNode identList = STNodeFactory.createNodeList(firstIdent, secondIdent);
        return STNodeFactory.createAnnotationAttachPointNode(sourceKeyword, identList);
    }

    /**
     * Parse remote ident.
     *
     * @return Parsed node
     */
    private STNode parseRemoteIdent() {
        STToken token = peek();
        if (token.kind == SyntaxKind.REMOTE_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.REMOTE_IDENT);
            return parseRemoteIdent();
        }
    }

    /**
     * Parse service attach point.
     * <code>service-attach-point := service | service remote function</code>
     *
     * @return Parsed node
     */
    private STNode parseServiceAttachPoint(STNode sourceKeyword, STNode firstIdent) {
        STNode identList;
        STToken token = peek();
        switch (token.kind) {
            case REMOTE_KEYWORD:
                STNode secondIdent = parseRemoteIdent();
                STNode thirdIdent = parseFunctionIdent();
                identList = STNodeFactory.createNodeList(firstIdent, secondIdent, thirdIdent);
                return STNodeFactory.createAnnotationAttachPointNode(sourceKeyword, identList);
            case COMMA_TOKEN:
            case SEMICOLON_TOKEN:
                identList = STNodeFactory.createNodeList(firstIdent);
                return STNodeFactory.createAnnotationAttachPointNode(sourceKeyword, identList);
            default:
                recover(token, ParserRuleContext.SERVICE_IDENT_RHS);
                return parseServiceAttachPoint(sourceKeyword, firstIdent);
        }
    }

    /**
     * Parse the idents that are supported after object-ident.
     *
     * @return Parsed node
     */
    private STNode parseIdentAfterObjectIdent() {
        STToken token = peek();
        switch (token.kind) {
            case FUNCTION_KEYWORD:
            case FIELD_KEYWORD:
                return consume();
            default:
                recover(token, ParserRuleContext.IDENT_AFTER_OBJECT_IDENT);
                return parseIdentAfterObjectIdent();
        }
    }

    /**
     * Parse function ident.
     *
     * @return Parsed node
     */
    private STNode parseFunctionIdent() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FUNCTION_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FUNCTION_IDENT);
            return parseFunctionIdent();
        }
    }

    /**
     * Parse field ident.
     *
     * @return Parsed node
     */
    private STNode parseFieldIdent() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FIELD_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FIELD_IDENT);
            return parseFieldIdent();
        }
    }

    /**
     * Parse XML namespace declaration.
     * <p>
     * <code>xmlns-decl := xmlns xml-namespace-uri [ as xml-namespace-prefix ] ;
     * <br/>
     * xml-namespace-uri := simple-const-expr
     * <br/>
     * xml-namespace-prefix := identifier
     * </code>
     *
     * @return
     */
    private STNode parseXMLNamespaceDeclaration(boolean isModuleVar) {
        startContext(ParserRuleContext.XML_NAMESPACE_DECLARATION);
        STNode xmlnsKeyword = parseXMLNSKeyword();

        STNode namespaceUri = parseSimpleConstExpr();
        while (!isValidXMLNameSpaceURI(namespaceUri)) {
            xmlnsKeyword = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(xmlnsKeyword, namespaceUri,
                    DiagnosticErrorCode.ERROR_INVALID_XML_NAMESPACE_URI);
            namespaceUri = parseSimpleConstExpr();
        }

        STNode xmlnsDecl = parseXMLDeclRhs(xmlnsKeyword, namespaceUri, isModuleVar);
        endContext();
        return xmlnsDecl;
    }

    /**
     * Parse xmlns keyword.
     *
     * @return Parsed node
     */
    private STNode parseXMLNSKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.XMLNS_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.XMLNS_KEYWORD);
            return parseXMLNSKeyword();
        }
    }

    private boolean isValidXMLNameSpaceURI(STNode expr) {
        switch (expr.kind) {
            case STRING_LITERAL:
            case QUALIFIED_NAME_REFERENCE:
            case SIMPLE_NAME_REFERENCE:
                return true;
            case IDENTIFIER_TOKEN:
            default:
                return false;
        }
    }

    private STNode parseSimpleConstExpr() {
        startContext(ParserRuleContext.CONSTANT_EXPRESSION);
        STNode expr = parseSimpleConstExprInternal();
        endContext();
        return expr;
    }

    /**
     * Parse simple constants expr.
     *
     * @return Parsed node
     */
    private STNode parseSimpleConstExprInternal() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case STRING_LITERAL_TOKEN:
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case NULL_KEYWORD:
                return parseBasicLiteral();
            case PLUS_TOKEN:
            case MINUS_TOKEN:
                return parseSignedIntOrFloat();
            case OPEN_PAREN_TOKEN:
                return parseNilLiteral();
            default:
                if (isPredeclaredIdentifier(nextToken.kind)) {
                    return parseQualifiedIdentifier(ParserRuleContext.VARIABLE_REF);
                }
                
                recover(nextToken, ParserRuleContext.CONSTANT_EXPRESSION_START);
                return parseSimpleConstExprInternal();
        }
    }

    /**
     * Parse the portion after the namsepsace-uri of an XML declaration.
     *
     * @param xmlnsKeyword XMLNS keyword
     * @param namespaceUri Namespace URI
     * @return Parsed node
     */
    private STNode parseXMLDeclRhs(STNode xmlnsKeyword, STNode namespaceUri, boolean isModuleVar) {
        STNode asKeyword = STNodeFactory.createEmptyNode();
        STNode namespacePrefix = STNodeFactory.createEmptyNode();

        switch (peek().kind) {
            case AS_KEYWORD:
                asKeyword = parseAsKeyword();
                namespacePrefix = parseNamespacePrefix();
                break;
            case SEMICOLON_TOKEN:
                break;
            default:
                recover(peek(), ParserRuleContext.XML_NAMESPACE_PREFIX_DECL);
                return parseXMLDeclRhs(xmlnsKeyword, namespaceUri, isModuleVar);
        }

        STNode semicolon = parseSemicolon();
        if (isModuleVar) {
            return STNodeFactory.createModuleXMLNamespaceDeclarationNode(xmlnsKeyword, namespaceUri, asKeyword,
                    namespacePrefix, semicolon);
        }
        return STNodeFactory.createXMLNamespaceDeclarationNode(xmlnsKeyword, namespaceUri, asKeyword, namespacePrefix,
                semicolon);
    }

    /**
     * Parse import prefix.
     *
     * @return Parsed node
     */
    private STNode parseNamespacePrefix() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN) {
            return consume();
        } else {
            recover(peek(), ParserRuleContext.NAMESPACE_PREFIX);
            return parseNamespacePrefix();
        }
    }

    /**
     * Parse named worker declaration.
     * <p>
     * <code>named-worker-decl := [annots] [transactional] worker worker-name return-type-descriptor { sequence-stmt }
     * </code>
     *
     * @param annots Annotations attached to the worker decl
     * @param qualifiers Preceding transactional keyword in a list
     * @return Parsed node
     */
    private STNode parseNamedWorkerDeclaration(STNode annots, List<STNode> qualifiers) {
        startContext(ParserRuleContext.NAMED_WORKER_DECL);
        STNode transactionalKeyword = getTransactionalKeyword(qualifiers);
        STNode workerKeyword = parseWorkerKeyword();
        STNode workerName = parseWorkerName();
        STNode returnTypeDesc = parseReturnTypeDescriptor();
        STNode workerBody = parseBlockNode();
        endContext();
        return STNodeFactory.createNamedWorkerDeclarationNode(annots, transactionalKeyword, workerKeyword, workerName,
                returnTypeDesc, workerBody);
    }

    private STNode getTransactionalKeyword(List<STNode> qualifierList) {
        
        List<STNode> validatedList = new ArrayList<>();

        for (int i = 0; i < qualifierList.size(); i++) {
            STNode qualifier = qualifierList.get(i);
            int nextIndex = i + 1;

            if (isSyntaxKindInList(validatedList, qualifier.kind)) {
                updateLastNodeInListWithInvalidNode(validatedList, qualifier,
                        DiagnosticErrorCode.ERROR_DUPLICATE_QUALIFIER, ((STToken) qualifier).text());
            } else if (qualifier.kind == SyntaxKind.TRANSACTIONAL_KEYWORD) {
                validatedList.add(qualifier);
            } else if (qualifierList.size() == nextIndex) {
                addInvalidNodeToNextToken(qualifier, DiagnosticErrorCode.ERROR_QUALIFIER_NOT_ALLOWED,
                        ((STToken) qualifier).text());
            } else {
                updateANodeInListWithLeadingInvalidNode(qualifierList, nextIndex, qualifier,
                        DiagnosticErrorCode.ERROR_QUALIFIER_NOT_ALLOWED, ((STToken) qualifier).text());
            }
        }

        STNode transactionalKeyword;
        if (validatedList.isEmpty()) {
            transactionalKeyword = STNodeFactory.createEmptyNode();
        } else {
            transactionalKeyword = validatedList.get(0);
        }
        return transactionalKeyword;
    }

    private STNode parseReturnTypeDescriptor() {
        
        STToken token = peek();
        if (token.kind != SyntaxKind.RETURNS_KEYWORD) {
            return STNodeFactory.createEmptyNode();
        }

        STNode returnsKeyword = consume();
        STNode annot = parseOptionalAnnotations();
        STNode type = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_RETURN_TYPE_DESC);
        return STNodeFactory.createReturnTypeDescriptorNode(returnsKeyword, annot, type);
    }

    /**
     * Parse worker keyword.
     *
     * @return Parsed node
     */
    private STNode parseWorkerKeyword() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.WORKER_KEYWORD) {
            return consume();
        } else {
            recover(peek(), ParserRuleContext.WORKER_KEYWORD);
            return parseWorkerKeyword();
        }
    }

    /**
     * Parse worker name.
     * <p>
     * <code>worker-name := identifier</code>
     *
     * @return Parsed node
     */
    private STNode parseWorkerName() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN) {
            return consume();
        } else {
            recover(peek(), ParserRuleContext.WORKER_NAME);
            return parseWorkerName();
        }
    }

    /**
     * Parse lock statement.
     * <code>lock-stmt := lock block-stmt [on-fail-clause]</code>
     *
     * @return Lock statement
     */
    private STNode parseLockStatement() {
        startContext(ParserRuleContext.LOCK_STMT);
        STNode lockKeyword = parseLockKeyword();
        STNode blockStatement = parseBlockNode();
        endContext();
        STNode onFailClause = parseOptionalOnFailClause();
        return STNodeFactory.createLockStatementNode(lockKeyword, blockStatement, onFailClause);
    }

    /**
     * Parse lock-keyword.
     *
     * @return lock-keyword node
     */
    private STNode parseLockKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.LOCK_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.LOCK_KEYWORD);
            return parseLockKeyword();
        }
    }

    /**
     * Parse union type descriptor.
     * union-type-descriptor := type-descriptor | type-descriptor
     *
     * @param leftTypeDesc Type desc in the LHS os the union type desc.
     * @param context      Current context.
     * @return parsed union type desc node
     */
    private STNode parseUnionTypeDescriptor(STNode leftTypeDesc, ParserRuleContext context,
                                            boolean isTypedBindingPattern) {
        
        STNode pipeToken = consume();
        STNode rightTypeDesc = parseTypeDescriptorInternal(new ArrayList<>(), context, isTypedBindingPattern, false,
                TypePrecedence.UNION);
        return mergeTypesWithUnion(leftTypeDesc, pipeToken, rightTypeDesc);
    }

    /**
     * Creates a union type descriptor after validating lhs and rhs types.
     * <p>
     * <i>Note: Since type precedence and associativity are not taken into account here,
     * this method should not be called directly when types are unknown.
     * <br/>
     * Call {@link 
     *
     * @param leftTypeDesc  lhs type
     * @param pipeToken     pipe token
     * @param rightTypeDesc rhs type
     * @return a UnionTypeDescriptorNode
     */
    private STNode createUnionTypeDesc(STNode leftTypeDesc, STNode pipeToken, STNode rightTypeDesc) {
        leftTypeDesc = validateForUsageOfVar(leftTypeDesc);
        rightTypeDesc = validateForUsageOfVar(rightTypeDesc);
        return STNodeFactory.createUnionTypeDescriptorNode(leftTypeDesc, pipeToken, rightTypeDesc);
    }

    /**
     * Parse pipe token.
     *
     * @return parsed pipe token node
     */
    private STNode parsePipeToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.PIPE_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.PIPE);
            return parsePipeToken();
        }
    }

    private boolean isTypeStartingToken(SyntaxKind nodeKind) {
        return isTypeStartingToken(nodeKind, getNextNextToken());
    }
    
    private static boolean isTypeStartingToken(SyntaxKind nextTokenKind, STToken nextNextToken) {
        switch (nextTokenKind) {
            case IDENTIFIER_TOKEN:
            case SERVICE_KEYWORD:
            case RECORD_KEYWORD:
            case OBJECT_KEYWORD:
            case ABSTRACT_KEYWORD:
            case CLIENT_KEYWORD:
            case OPEN_PAREN_TOKEN: 
            case MAP_KEYWORD: 
            case STREAM_KEYWORD: 
            case TABLE_KEYWORD: 
            case FUNCTION_KEYWORD:
            case OPEN_BRACKET_TOKEN:
            case DISTINCT_KEYWORD:
            case ISOLATED_KEYWORD:
            case TRANSACTIONAL_KEYWORD:
            case TRANSACTION_KEYWORD:
                return true;
            default:
                if (isParameterizedTypeToken(nextTokenKind)) {
                    return true;
                }
                
                if (isSingletonTypeDescStart(nextTokenKind, nextNextToken)) {
                    return true;
                }
                return isSimpleType(nextTokenKind);
        }
    }

    /**
     * Check if the token kind is a type descriptor in terminal expression.
     * <p>
     * simple-type-in-expr :=
     * boolean | int | byte | float | decimal | string | handle | json | anydata | any | never
     *
     * @param nodeKind token kind to check
     * @return <code>true</code> for simple type token in expression. <code>false</code> otherwise.
     */
    private boolean isSimpleTypeInExpression(SyntaxKind nodeKind) {
        switch (nodeKind) {
            case VAR_KEYWORD:
            case READONLY_KEYWORD:
                return false;
            default:
                return isSimpleType(nodeKind);
        }
    }

    static boolean isSimpleType(SyntaxKind nodeKind) {
        switch (nodeKind) {
            case INT_KEYWORD:
            case FLOAT_KEYWORD:
            case DECIMAL_KEYWORD:
            case BOOLEAN_KEYWORD:
            case STRING_KEYWORD:
            case BYTE_KEYWORD:
            case JSON_KEYWORD:
            case HANDLE_KEYWORD:
            case ANY_KEYWORD:
            case ANYDATA_KEYWORD:
            case NEVER_KEYWORD:
            case VAR_KEYWORD:
            case READONLY_KEYWORD:
                return true;
            default:
                return false;
        }
    }

    static boolean isPredeclaredPrefix(SyntaxKind nodeKind) {
        switch (nodeKind) {
            case BOOLEAN_KEYWORD:
            case DECIMAL_KEYWORD:
            case ERROR_KEYWORD:
            case FLOAT_KEYWORD:
            case FUNCTION_KEYWORD:
            case FUTURE_KEYWORD:
            case INT_KEYWORD:
            case MAP_KEYWORD:
            case OBJECT_KEYWORD:
            case STREAM_KEYWORD:
            case STRING_KEYWORD:
            case TABLE_KEYWORD:
            case TRANSACTION_KEYWORD:
            case TYPEDESC_KEYWORD:
            case XML_KEYWORD:
                return true;
            default:
                return false;
        }
    }

    private boolean isQualifiedIdentifierPredeclaredPrefix(SyntaxKind nodeKind) {
       return isPredeclaredPrefix(nodeKind) && getNextNextToken().kind == SyntaxKind.COLON_TOKEN;
    }

    private static SyntaxKind getBuiltinTypeSyntaxKind(SyntaxKind typeKeyword) {
        switch (typeKeyword) {
            case INT_KEYWORD:
                return SyntaxKind.INT_TYPE_DESC;
            case FLOAT_KEYWORD:
                return SyntaxKind.FLOAT_TYPE_DESC;
            case DECIMAL_KEYWORD:
                return SyntaxKind.DECIMAL_TYPE_DESC;
            case BOOLEAN_KEYWORD:
                return SyntaxKind.BOOLEAN_TYPE_DESC;
            case STRING_KEYWORD:
                return SyntaxKind.STRING_TYPE_DESC;
            case BYTE_KEYWORD:
                return SyntaxKind.BYTE_TYPE_DESC;
            case JSON_KEYWORD:
                return SyntaxKind.JSON_TYPE_DESC;
            case HANDLE_KEYWORD:
                return SyntaxKind.HANDLE_TYPE_DESC;
            case ANY_KEYWORD:
                return SyntaxKind.ANY_TYPE_DESC;
            case ANYDATA_KEYWORD:
                return SyntaxKind.ANYDATA_TYPE_DESC;
            case NEVER_KEYWORD:
                return SyntaxKind.NEVER_TYPE_DESC;
            case VAR_KEYWORD:
                return SyntaxKind.VAR_TYPE_DESC;
            case READONLY_KEYWORD:
                return SyntaxKind.READONLY_TYPE_DESC;
            default:
                assert false : typeKeyword + " is not a built-in type";
                return SyntaxKind.TYPE_REFERENCE;
        }
    }

    /**
     * Parse fork-keyword.
     *
     * @return Fork-keyword node
     */
    private STNode parseForkKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FORK_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FORK_KEYWORD);
            return parseForkKeyword();
        }
    }

    /**
     * Parse fork statement.
     * <code>fork-stmt := fork { named-worker-decl+ }</code>
     *
     * @return Fork statement
     */
    private STNode parseForkStatement() {
        startContext(ParserRuleContext.FORK_STMT);
        STNode forkKeyword = parseForkKeyword();
        STNode openBrace = parseOpenBrace();

        
        ArrayList<STNode> workers = new ArrayList<>();
        while (!isEndOfStatements()) {
            STNode stmt = parseStatement();
            if (stmt == null) {
                break;
            }

            if (validateStatement(stmt)) {
                continue;
            }

            switch (stmt.kind) {
                case NAMED_WORKER_DECLARATION:
                    workers.add(stmt);
                    break;
                default:
                    
                    
                    
                    if (workers.isEmpty()) {
                        openBrace = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(openBrace, stmt,
                                DiagnosticErrorCode.ERROR_ONLY_NAMED_WORKERS_ALLOWED_HERE);
                    } else {
                        updateLastNodeInListWithInvalidNode(workers, stmt,
                                DiagnosticErrorCode.ERROR_ONLY_NAMED_WORKERS_ALLOWED_HERE);
                    }
            }
        }

        STNode namedWorkerDeclarations = STNodeFactory.createNodeList(workers);
        STNode closeBrace = parseCloseBrace();
        endContext();

        STNode forkStmt =
                STNodeFactory.createForkStatementNode(forkKeyword, openBrace, namedWorkerDeclarations, closeBrace);
        if (isNodeListEmpty(namedWorkerDeclarations)) {
            return SyntaxErrors.addDiagnostic(forkStmt,
                    DiagnosticErrorCode.ERROR_MISSING_NAMED_WORKER_DECLARATION_IN_FORK_STMT);
        }

        return forkStmt;
    }

    /**
     * Parse trap expression.
     * <p>
     * <code>
     * trap-expr := trap expression
     * </code>
     *
     * @param allowActions Allow actions
     * @param isRhsExpr    Whether this is a RHS expression or not
     * @return Trap expression node
     */
    private STNode parseTrapExpression(boolean isRhsExpr, boolean allowActions, boolean isInConditionalExpr) {
        STNode trapKeyword = parseTrapKeyword();
        STNode expr = parseExpression(OperatorPrecedence.TRAP, isRhsExpr, allowActions, isInConditionalExpr);
        if (isAction(expr)) {
            return STNodeFactory.createTrapExpressionNode(SyntaxKind.TRAP_ACTION, trapKeyword, expr);
        }

        return STNodeFactory.createTrapExpressionNode(SyntaxKind.TRAP_EXPRESSION, trapKeyword, expr);
    }

    /**
     * Parse trap-keyword.
     *
     * @return Trap-keyword node
     */
    private STNode parseTrapKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.TRAP_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.TRAP_KEYWORD);
            return parseTrapKeyword();
        }
    }

    /**
     * Parse list constructor expression.
     * <p>
     * <code>
     * list-constructor-expr := [ [ list-members  ] ]
     * <br/>
     * list-members  := list-member  (, list-member)*
     * <br/>
     * list-member := expression | spread-member
     * <br/>
     * spread-member := ... expression
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseListConstructorExpr() {
        startContext(ParserRuleContext.LIST_CONSTRUCTOR);
        STNode openBracket = parseOpenBracket();
        STNode listMembers = parseListMembers();
        STNode closeBracket = parseCloseBracket();
        endContext();
        return STNodeFactory.createListConstructorExpressionNode(openBracket, listMembers, closeBracket);
    }

    /**
     * Parse optional list member list.
     *
     * @return Parsed node
     */
    private STNode parseListMembers() {
        List<STNode> listMembers = new ArrayList<>();
        if (isEndOfListConstructor(peek().kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        STNode listMember = parseListMember();
        listMembers.add(listMember);
        return parseListMembers(listMembers);
    }

    private STNode parseListMembers(List<STNode> listMembers) {
        
        STNode listConstructorMemberEnd;
        while (!isEndOfListConstructor(peek().kind)) {
            listConstructorMemberEnd = parseListConstructorMemberEnd();
            if (listConstructorMemberEnd == null) {
                break;
            }
            listMembers.add(listConstructorMemberEnd);

            STNode listMember = parseListMember();
            listMembers.add(listMember);
        }

        return STNodeFactory.createNodeList(listMembers);
    }

    /**
     * Parse list member.
     * <p>
     * <code>
     * list-member := expression | spread-member
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseListMember() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.ELLIPSIS_TOKEN) {
            return parseSpreadMember();
        } else {
            return parseExpression();
        }
    }

    /**
     * Parse spread member.
     * <p>
     * <code>
     * spread-member := ... expression
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseSpreadMember() {
        STNode ellipsis = parseEllipsis();
        STNode expr = parseExpression();
        return STNodeFactory.createSpreadMemberNode(ellipsis, expr);
    }

    private boolean isEndOfListConstructor(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACKET_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseListConstructorMemberEnd() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return consume();
            case CLOSE_BRACKET_TOKEN:
                return null;
            default:
                recover(nextToken, ParserRuleContext.LIST_CONSTRUCTOR_MEMBER_END);
                return parseListConstructorMemberEnd();
        }
    }

    /**
     * Parse foreach statement.
     * <code>foreach-stmt := foreach typed-binding-pattern in action-or-expr block-stmt [on-fail-clause]</code>
     *
     * @return foreach statement
     */
    private STNode parseForEachStatement() {
        startContext(ParserRuleContext.FOREACH_STMT);
        STNode forEachKeyword = parseForEachKeyword();
        STNode typedBindingPattern = parseTypedBindingPattern(ParserRuleContext.FOREACH_STMT);
        STNode inKeyword = parseInKeyword();
        STNode actionOrExpr = parseActionOrExpression();
        STNode blockStatement = parseBlockNode();
        endContext();
        STNode onFailClause = parseOptionalOnFailClause();
        return STNodeFactory.createForEachStatementNode(forEachKeyword, typedBindingPattern, inKeyword, actionOrExpr,
                blockStatement, onFailClause);
    }

    /**
     * Parse foreach-keyword.
     *
     * @return ForEach-keyword node
     */
    private STNode parseForEachKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FOREACH_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FOREACH_KEYWORD);
            return parseForEachKeyword();
        }
    }

    /**
     * Parse in-keyword.
     *
     * @return In-keyword node
     */
    private STNode parseInKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.IN_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.IN_KEYWORD);
            return parseInKeyword();
        }
    }

    /**
     * Parse type cast expression.
     * <p>
     * <code>
     * type-cast-expr := < type-cast-param > expression
     * <br/>
     * type-cast-param := [annots] type-descriptor | annots
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseTypeCastExpr(boolean isRhsExpr, boolean allowActions, boolean isInConditionalExpr) {
        startContext(ParserRuleContext.TYPE_CAST);
        STNode ltToken = parseLTToken();
        return parseTypeCastExpr(ltToken, isRhsExpr, allowActions, isInConditionalExpr);
    }

    private STNode parseTypeCastExpr(STNode ltToken, boolean isRhsExpr, boolean allowActions,
                                     boolean isInConditionalExpr) {
        STNode typeCastParam = parseTypeCastParam();
        STNode gtToken = parseGTToken();
        endContext();

        
        
        STNode expression =
                parseExpression(OperatorPrecedence.EXPRESSION_ACTION, isRhsExpr, allowActions, isInConditionalExpr);
        return STNodeFactory.createTypeCastExpressionNode(ltToken, typeCastParam, gtToken, expression);
    }

    private STNode parseTypeCastParam() {
        STNode annot;
        STNode type;
        STToken token = peek();

        switch (token.kind) {
            case AT_TOKEN:
                annot = parseOptionalAnnotations();
                token = peek();
                if (isTypeStartingToken(token.kind)) {
                    type = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_ANGLE_BRACKETS);
                } else {
                    type = STNodeFactory.createEmptyNode();
                }
                break;
            default:
                annot = STNodeFactory.createEmptyNode();
                type = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_ANGLE_BRACKETS);
                break;
        }

        return STNodeFactory.createTypeCastParamNode(getAnnotations(annot), type);
    }

    /**
     * Parse table constructor expression.
     * <p>
     * <code>
     * table-constructor-expr-rhs := [ [row-list] ]
     * </code>
     *
     * @param tableKeyword tableKeyword that precedes this rhs
     * @param keySpecifier keySpecifier that precedes this rhs
     * @return Parsed node
     */
    private STNode parseTableConstructorExprRhs(STNode tableKeyword, STNode keySpecifier) {
        switchContext(ParserRuleContext.TABLE_CONSTRUCTOR);
        STNode openBracket = parseOpenBracket();
        STNode rowList = parseRowList();
        STNode closeBracket = parseCloseBracket();
        return STNodeFactory.createTableConstructorExpressionNode(tableKeyword, keySpecifier, openBracket, rowList,
                closeBracket);
    }

    /**
     * Parse table-keyword.
     *
     * @return Table-keyword node
     */
    private STNode parseTableKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.TABLE_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.TABLE_KEYWORD);
            return parseTableKeyword();
        }
    }

    /**
     * Parse table rows.
     * <p>
     * <code>row-list := [ mapping-constructor-expr (, mapping-constructor-expr)* ]</code>
     *
     * @return Parsed node
     */
    private STNode parseRowList() {
        STToken nextToken = peek();
        
        if (isEndOfTableRowList(nextToken.kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        List<STNode> mappings = new ArrayList<>();
        STNode mapExpr = parseMappingConstructorExpr();
        mappings.add(mapExpr);

        
        nextToken = peek();
        STNode rowEnd;
        while (!isEndOfTableRowList(nextToken.kind)) {
            rowEnd = parseTableRowEnd();
            if (rowEnd == null) {
                break;
            }

            mappings.add(rowEnd);
            mapExpr = parseMappingConstructorExpr();
            mappings.add(mapExpr);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(mappings);
    }

    private boolean isEndOfTableRowList(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACKET_TOKEN:
                return true;
            case COMMA_TOKEN:
            case OPEN_BRACE_TOKEN:
                return false;
            default:
                return isEndOfMappingConstructor(tokenKind);
        }
    }

    private STNode parseTableRowEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACKET_TOKEN:
            case EOF_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.TABLE_ROW_END);
                return parseTableRowEnd();
        }
    }

    /**
     * Parse key specifier.
     * <p>
     * <code>key-specifier := key ( [ field-name (, field-name)* ] )</code>
     *
     * @return Parsed node
     */
    private STNode parseKeySpecifier() {
        startContext(ParserRuleContext.KEY_SPECIFIER);
        STNode keyKeyword = parseKeyKeyword();
        STNode openParen = parseOpenParenthesis();
        STNode fieldNames = parseFieldNames();
        STNode closeParen = parseCloseParenthesis();
        endContext();
        return STNodeFactory.createKeySpecifierNode(keyKeyword, openParen, fieldNames, closeParen);
    }

    /**
     * Parse key-keyword.
     *
     * @return Key-keyword node
     */
    private STNode parseKeyKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.KEY_KEYWORD) {
            return consume();
        }

        if (isKeyKeyword(token)) {
            
            return getKeyKeyword(consume());
        }

        recover(token, ParserRuleContext.KEY_KEYWORD);
        return parseKeyKeyword();
    }

    static boolean isKeyKeyword(STToken token) {
        return token.kind == SyntaxKind.IDENTIFIER_TOKEN && LexerTerminals.KEY.equals(token.text());
    }

    private STNode getKeyKeyword(STToken token) {
        return STNodeFactory.createToken(SyntaxKind.KEY_KEYWORD, token.leadingMinutiae(), token.trailingMinutiae(),
                token.diagnostics());
    }

    private STToken getUnderscoreKeyword(STToken token) {
        return STNodeFactory.createToken(SyntaxKind.UNDERSCORE_KEYWORD, token.leadingMinutiae(),
                token.trailingMinutiae(), token.diagnostics());
    }

    /**
     * Parse field names.
     * <p>
     * <code>field-name-list := [ field-name (, field-name)* ]</code>
     *
     * @return Parsed node
     */
    private STNode parseFieldNames() {
        STToken nextToken = peek();
        
        if (isEndOfFieldNamesList(nextToken.kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        List<STNode> fieldNames = new ArrayList<>();
        STNode fieldName = parseVariableName();
        fieldNames.add(fieldName);

        
        nextToken = peek();
        STNode leadingComma;
        while (!isEndOfFieldNamesList(nextToken.kind)) {
            leadingComma = parseComma();
            fieldNames.add(leadingComma);
            fieldName = parseVariableName();
            fieldNames.add(fieldName);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(fieldNames);
    }

    private boolean isEndOfFieldNamesList(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case COMMA_TOKEN:
            case IDENTIFIER_TOKEN:
                return false;
            default:
                return true;
        }
    }

    /**
     * Parse error-keyword.
     *
     * @return Parsed error-keyword node
     */
    private STNode parseErrorKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ERROR_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ERROR_KEYWORD);
            return parseErrorKeyword();
        }
    }

    /**
     * Parse stream type descriptor.
     * <p>
     * stream-type-descriptor := stream [stream-type-parameters]
     * <br/>
     * stream-type-parameters := < type-descriptor [, type-descriptor]>
     * </p>
     *
     * @return Parsed stream type descriptor node
     */
    private STNode parseStreamTypeDescriptor(STNode streamKeywordToken) {
        STNode streamTypeParamsNode;
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.LT_TOKEN) {
            streamTypeParamsNode = parseStreamTypeParamsNode();
        } else {
            streamTypeParamsNode = STNodeFactory.createEmptyNode();
        }
        return STNodeFactory.createStreamTypeDescriptorNode(streamKeywordToken, streamTypeParamsNode);
    }

    /**
     * Parse stream type params node.
     * <p>
     * stream-type-parameters := < type-descriptor [, type-descriptor]>
     * </p>
     *
     * @return Parsed stream type params node
     */
    private STNode parseStreamTypeParamsNode() {
        STNode ltToken = parseLTToken();
        startContext(ParserRuleContext.TYPE_DESC_IN_STREAM_TYPE_DESC);
        STNode leftTypeDescNode = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_STREAM_TYPE_DESC);
        STNode streamTypedesc = parseStreamTypeParamsNode(ltToken, leftTypeDescNode);
        endContext();
        return streamTypedesc;
    }

    private STNode parseStreamTypeParamsNode(STNode ltToken, STNode leftTypeDescNode) {
        STNode commaToken, rightTypeDescNode, gtToken;
        switch (peek().kind) {
            case COMMA_TOKEN:
                commaToken = parseComma();
                rightTypeDescNode = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_STREAM_TYPE_DESC);
                break;
            case GT_TOKEN:
                commaToken = STNodeFactory.createEmptyNode();
                rightTypeDescNode = STNodeFactory.createEmptyNode();
                break;
            default:
                recover(peek(), ParserRuleContext.STREAM_TYPE_FIRST_PARAM_RHS);
                return parseStreamTypeParamsNode(ltToken, leftTypeDescNode);
        }

        gtToken = parseGTToken();
        return STNodeFactory.createStreamTypeParamsNode(ltToken, leftTypeDescNode, commaToken, rightTypeDescNode,
                gtToken);
    }

    /**
     * Parse let expression.
     * <p>
     * <code>
     * let-expr := let let-var-decl [, let-var-decl]* in expression
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseLetExpression(boolean isRhsExpr, boolean isInConditionalExpr) {
        STNode letKeyword = parseLetKeyword();
        STNode letVarDeclarations = parseLetVarDeclarations(ParserRuleContext.LET_EXPR_LET_VAR_DECL, isRhsExpr, false);
        STNode inKeyword = parseInKeyword();

        
        letKeyword = cloneWithDiagnosticIfListEmpty(letVarDeclarations, letKeyword,
                DiagnosticErrorCode.ERROR_MISSING_LET_VARIABLE_DECLARATION);

        
        
        STNode expression = parseExpression(OperatorPrecedence.REMOTE_CALL_ACTION, isRhsExpr, false,
                isInConditionalExpr);
        return STNodeFactory.createLetExpressionNode(letKeyword, letVarDeclarations, inKeyword, expression);
    }

    /**
     * Parse let-keyword.
     *
     * @return Let-keyword node
     */
    private STNode parseLetKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.LET_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.LET_KEYWORD);
            return parseLetKeyword();
        }
    }

    /**
     * Parse let variable declarations.
     * <p>
     * <code>let-var-decl-list := let-var-decl [, let-var-decl]*</code>
     *
     * @return Parsed node
     */
    private STNode parseLetVarDeclarations(ParserRuleContext context, boolean isRhsExpr, boolean allowActions) {
        startContext(context);
        List<STNode> varDecls = new ArrayList<>();
        STToken nextToken = peek();

        if (isEndOfLetVarDeclarations(nextToken, getNextNextToken())) {
            endContext();
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode varDec = parseLetVarDecl(context, isRhsExpr, allowActions);
        varDecls.add(varDec);

        
        nextToken = peek();
        STNode leadingComma;
        while (!isEndOfLetVarDeclarations(nextToken, getNextNextToken())) {
            leadingComma = parseComma();
            varDecls.add(leadingComma);
            varDec = parseLetVarDecl(context, isRhsExpr, allowActions);
            varDecls.add(varDec);
            nextToken = peek();
        }

        endContext();
        return STNodeFactory.createNodeList(varDecls);
    }

    static boolean isEndOfLetVarDeclarations(STToken nextToken, STToken nextNextToken) {
        SyntaxKind tokenKind = nextToken.kind;
        switch (tokenKind) {
            case COMMA_TOKEN:
            case AT_TOKEN:
                return false;
            case IN_KEYWORD:
                return true;
            default:
                return isGroupOrCollectKeyword(nextToken) || !isTypeStartingToken(tokenKind, nextNextToken);
        }
    }

    /**
     * Parse let variable declaration.
     * <p>
     * <code>let-var-decl := [annots] typed-binding-pattern = expression</code>
     *
     * @return Parsed node
     */
    private STNode parseLetVarDecl(ParserRuleContext context, boolean isRhsExpr, boolean allowActions) {
        STNode annot = parseOptionalAnnotations();
        STNode typedBindingPattern = parseTypedBindingPattern(ParserRuleContext.LET_EXPR_LET_VAR_DECL);
        STNode assign = parseAssignOp();
        STNode expression = context ==  ParserRuleContext.LET_CLAUSE_LET_VAR_DECL ?
                parseExpression(OperatorPrecedence.QUERY, isRhsExpr, allowActions) :
                parseExpression(OperatorPrecedence.ANON_FUNC_OR_LET, isRhsExpr, false);

        return STNodeFactory.createLetVariableDeclarationNode(annot, typedBindingPattern, assign, expression);
    }

    /**
     * Parse raw backtick string template expression.
     * <p>
     * <code>BacktickString := `expression`</code>
     *
     * @return Template expression node
     */
    private STNode parseTemplateExpression() {
        STNode type = STNodeFactory.createEmptyNode();
        STNode startingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);
        STNode content = parseTemplateContent();
        STNode endingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);
        return STNodeFactory.createTemplateExpressionNode(SyntaxKind.RAW_TEMPLATE_EXPRESSION, type, startingBackTick,
                content, endingBackTick);
    }

    private STNode parseTemplateContent() {
        List<STNode> items = new ArrayList<>();
        STToken nextToken = peek();
        while (!isEndOfBacktickContent(nextToken.kind)) {
            STNode contentItem = parseTemplateItem();
            items.add(contentItem);
            nextToken = peek();
        }
        return STNodeFactory.createNodeList(items);
    }

    private boolean isEndOfBacktickContent(SyntaxKind kind) {
        switch (kind) {
            case EOF_TOKEN:
            case BACKTICK_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseTemplateItem() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.INTERPOLATION_START_TOKEN) {
            return parseInterpolation();
        }

        
        return consume();
    }

    /**
     * Parse string template expression.
     * <p>
     * <code>string-template-expr := string ` expression `</code>
     *
     * @return String template expression node
     */
    private STNode parseStringTemplateExpression() {
        STNode type = parseStringKeyword();
        STNode startingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);
        STNode content = parseTemplateContent();
        STNode endingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_END);
        return STNodeFactory.createTemplateExpressionNode(SyntaxKind.STRING_TEMPLATE_EXPRESSION, type, startingBackTick,
                content, endingBackTick);
    }

    /**
     * Parse <code>string</code> keyword.
     *
     * @return string keyword node
     */
    private STNode parseStringKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.STRING_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.STRING_KEYWORD);
            return parseStringKeyword();
        }
    }

    /**
     * Parse XML template expression.
     * <p>
     * <code>xml-template-expr := xml BacktickString</code>
     *
     * @return XML template expression
     */
    private STNode parseXMLTemplateExpression() {
        STNode xmlKeyword = parseXMLKeyword();
        STNode startingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);

        if (startingBackTick.isMissing()) {
            return createMissingTemplateExpressionNode(xmlKeyword, SyntaxKind.XML_TEMPLATE_EXPRESSION);
        }

        STNode content = parseTemplateContentAsXML();
        STNode endingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_END);
        return STNodeFactory.createTemplateExpressionNode(SyntaxKind.XML_TEMPLATE_EXPRESSION, xmlKeyword,
                startingBackTick, content, endingBackTick);
    }

    /**
     * Parse <code>xml</code> keyword.
     *
     * @return xml keyword node
     */
    private STNode parseXMLKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.XML_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.XML_KEYWORD);
            return parseXMLKeyword();
        }
    }

    /**
     * Parse the content of the template string as XML. This method first read the
     * input in the same way as the raw-backtick-template (BacktickString). Then
     * it parses the content as XML.
     *
     * @return XML node
     */
    private STNode parseTemplateContentAsXML() {
        
        
        
        ArrayDeque<STNode> expressions = new ArrayDeque<>();
        StringBuilder xmlStringBuilder = new StringBuilder();
        STToken nextToken = peek();
        while (!isEndOfBacktickContent(nextToken.kind)) {
            STNode contentItem = parseTemplateItem();
            if (contentItem.kind == SyntaxKind.TEMPLATE_STRING) {
                xmlStringBuilder.append(((STToken) contentItem).text());
            } else {
                xmlStringBuilder.append("${}");
                expressions.add(contentItem);
            }
            nextToken = peek();
        }

        CharReader charReader = CharReader.from(xmlStringBuilder.toString());
        AbstractTokenReader tokenReader = new TokenReader(new XMLLexer(charReader));
        XMLParser xmlParser = new XMLParser(tokenReader, expressions);
        return xmlParser.parse();
    }

    /**
     * Parse regular expression constructor.
     * <p>
     * <code>regexp-constructor-expr := re BacktickString</code>
     *
     * @return Regular expression template expression
     */
    private STNode parseRegExpTemplateExpression() {
        STNode reKeyword = consume();
        STNode startingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);

        if (startingBackTick.isMissing()) {
            return createMissingTemplateExpressionNode(reKeyword, SyntaxKind.REGEX_TEMPLATE_EXPRESSION);
        }

        STNode content = parseTemplateContentAsRegExp();
        STNode endingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_END);
        return STNodeFactory.createTemplateExpressionNode(SyntaxKind.REGEX_TEMPLATE_EXPRESSION, reKeyword,
                startingBackTick, content, endingBackTick);
    }

    private STNode createMissingTemplateExpressionNode(STNode reKeyword, SyntaxKind kind) {
        
        STNode startingBackTick = SyntaxErrors.createMissingToken(SyntaxKind.BACKTICK_TOKEN);
        STNode endingBackTick = SyntaxErrors.createMissingToken(SyntaxKind.BACKTICK_TOKEN);
        STNode content = STAbstractNodeFactory.createEmptyNodeList();
        STNode templateExpr =
                STNodeFactory.createTemplateExpressionNode(kind, reKeyword, startingBackTick, content, endingBackTick);
        templateExpr = SyntaxErrors.addDiagnostic(templateExpr, DiagnosticErrorCode.ERROR_MISSING_BACKTICK_STRING);
        return templateExpr;
    }

    /**
     * Parse the content of the template string as regular expression. This method first read the
     * input in the same way as the raw-backtick-template (BacktickString). Then
     * it parses the content as regular expression.
     *
     * @return Template expression node
     */
    private STNode parseTemplateContentAsRegExp() {
        
        
        
        this.tokenReader.startMode(ParserMode.REGEXP);
        ArrayDeque<STNode> expressions = new ArrayDeque<>();
        StringBuilder regExpStringBuilder = new StringBuilder();
        STToken nextToken = peek();
        while (!isEndOfBacktickContent(nextToken.kind)) {
            STNode contentItem = parseTemplateItem();
            if (contentItem.kind == SyntaxKind.TEMPLATE_STRING) {
                regExpStringBuilder.append(((STToken) contentItem).text());
            } else {
                regExpStringBuilder.append("${}");
                expressions.add(contentItem);
            }
            nextToken = peek();
        }
        this.tokenReader.endMode();
        CharReader charReader = CharReader.from(regExpStringBuilder.toString());
        AbstractTokenReader tokenReader = new TokenReader(new RegExpLexer(charReader));
        RegExpParser regExpParser = new RegExpParser(tokenReader, expressions);
        return regExpParser.parse();
    }

    /**
     * Parse interpolation of a back-tick string.
     * <p>
     * <code>
     * interpolation := ${ expression }
     * </code>
     *
     * @return Interpolation node
     */
    private STNode parseInterpolation() {
        startContext(ParserRuleContext.INTERPOLATION);
        STNode interpolStart = parseInterpolationStart();
        STNode expr = parseExpression();

        
        while (!isEndOfInterpolation()) {
            STToken nextToken = consume();
            expr = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(expr, nextToken,
                    DiagnosticErrorCode.ERROR_INVALID_TOKEN, nextToken.text());
        }

        STNode closeBrace = parseCloseBrace();
        endContext();
        return STNodeFactory.createInterpolationNode(interpolStart, expr, closeBrace);
    }

    private boolean isEndOfInterpolation() {
        SyntaxKind nextTokenKind = peek().kind;
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case BACKTICK_TOKEN:
                return true;
            default:
                
                ParserMode currentLexerMode = this.tokenReader.getCurrentMode();
                return nextTokenKind == SyntaxKind.CLOSE_BRACE_TOKEN && currentLexerMode != ParserMode.INTERPOLATION &&
                        currentLexerMode != ParserMode.INTERPOLATION_BRACED_CONTENT;
        }
    }

    /**
     * Parse interpolation start token.
     * <p>
     * <code>interpolation-start := ${</code>
     *
     * @return Interpolation start token
     */
    private STNode parseInterpolationStart() {
        STToken token = peek();
        if (token.kind == SyntaxKind.INTERPOLATION_START_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.INTERPOLATION_START_TOKEN);
            return parseInterpolationStart();
        }
    }

    /**
     * Parse back-tick token.
     *
     * @return Back-tick token
     */
    private STNode parseBacktickToken(ParserRuleContext ctx) {
        STToken token = peek();
        if (token.kind == SyntaxKind.BACKTICK_TOKEN) {
            return consume();
        } else {
            recover(token, ctx);
            return parseBacktickToken(ctx);
        }
    }

    /**
     * Parse table type descriptor.
     * <p>
     * table-type-descriptor := table row-type-parameter [key-constraint]
     * row-type-parameter := type-parameter
     * key-constraint := key-specifier | key-type-constraint
     * key-specifier := key ( [ field-name (, field-name)* ] )
     * key-type-constraint := key type-parameter
     * </p>
     *
     * @return Parsed table type desc node.
     */
    private STNode parseTableTypeDescriptor(STNode tableKeywordToken) {
        STNode rowTypeParameterNode = parseRowTypeParameter();
        STNode keyConstraintNode;
        STToken nextToken = peek();
        if (isKeyKeyword(nextToken)) {
            STNode keyKeywordToken = getKeyKeyword(consume());
            keyConstraintNode = parseKeyConstraint(keyKeywordToken);
        } else {
            keyConstraintNode = STNodeFactory.createEmptyNode();
        }
        return STNodeFactory.createTableTypeDescriptorNode(tableKeywordToken, rowTypeParameterNode, keyConstraintNode);
    }

    /**
     * Parse row type parameter node.
     * <p>
     * row-type-parameter := type-parameter
     * </p>
     *
     * @return Parsed node.
     */
    private STNode parseRowTypeParameter() {
        startContext(ParserRuleContext.ROW_TYPE_PARAM);
        STNode rowTypeParameterNode = parseTypeParameter();
        endContext();
        return rowTypeParameterNode;
    }

    /**
     * Parse type parameter node.
     * <p>
     * type-parameter := < type-descriptor >
     * </p>
     *
     * @return Parsed node
     */
    private STNode parseTypeParameter() {
        STNode ltToken = parseLTToken();
        STNode typeNode = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_ANGLE_BRACKETS);
        STNode gtToken = parseGTToken();
        return STNodeFactory.createTypeParameterNode(ltToken, typeNode, gtToken);
    }

    /**
     * Parse key constraint.
     * <p>
     * key-constraint := key-specifier | key-type-constraint
     * </p>
     *
     * @return Parsed node.
     */
    private STNode parseKeyConstraint(STNode keyKeywordToken) {
        switch (peek().kind) {
            case OPEN_PAREN_TOKEN:
                return parseKeySpecifier(keyKeywordToken);
            case LT_TOKEN:
                return parseKeyTypeConstraint(keyKeywordToken);
            default:
                recover(peek(), ParserRuleContext.KEY_CONSTRAINTS_RHS);
                return parseKeyConstraint(keyKeywordToken);
        }
    }

    /**
     * Parse key specifier given parsed key keyword token.
     * <p>
     * <code>key-specifier := key ( [ field-name (, field-name)* ] )</code>
     *
     * @return Parsed node
     */
    private STNode parseKeySpecifier(STNode keyKeywordToken) {
        startContext(ParserRuleContext.KEY_SPECIFIER);
        STNode openParenToken = parseOpenParenthesis();
        STNode fieldNamesNode = parseFieldNames();
        STNode closeParenToken = parseCloseParenthesis();
        endContext();
        return STNodeFactory.createKeySpecifierNode(keyKeywordToken, openParenToken, fieldNamesNode, closeParenToken);
    }

    /**
     * Parse key type constraint.
     * <p>
     * key-type-constraint := key type-parameter
     * </p>
     *
     * @return Parsed node
     */
    private STNode parseKeyTypeConstraint(STNode keyKeywordToken) {
        STNode typeParameterNode = parseTypeParameter();
        return STNodeFactory.createKeyTypeConstraintNode(keyKeywordToken, typeParameterNode);
    }

    /**
     * Parse function type descriptor.
     * <p>
     * <code>
     * function-type-descriptor := function-quals function function-signature 
     * <br/>&nbsp;| [isolated] function
     * <br/>
     * function-quals := (transactional | isolated)*
     * </code>
     *
     * @param qualifiers Preceding type descriptor qualifiers
     * @return Function type descriptor node
     */
    private STNode parseFunctionTypeDesc(List<STNode> qualifiers) {
        startContext(ParserRuleContext.FUNC_TYPE_DESC);
        STNode functionKeyword = parseFunctionKeyword();

        boolean hasFuncSignature = false;
        STNode signature = STNodeFactory.createEmptyNode();
        if (peek().kind == SyntaxKind.OPEN_PAREN_TOKEN ||
                isSyntaxKindInList(qualifiers, SyntaxKind.TRANSACTIONAL_KEYWORD)) {
            signature = parseFuncSignature(true);
            hasFuncSignature = true;
        }

        
        STNode[] nodes = createFuncTypeQualNodeList(qualifiers, functionKeyword, hasFuncSignature);
        STNode qualifierList = nodes[0];
        functionKeyword = nodes[1];

        endContext();
        return STNodeFactory.createFunctionTypeDescriptorNode(qualifierList, functionKeyword, signature);
    }
    
    private STNode getLastNodeInList(List<STNode> nodeList) {
        return nodeList.get(nodeList.size() - 1);
    }

    private STNode[] createFuncTypeQualNodeList(List<STNode> qualifierList, STNode functionKeyword,
                                                boolean hasFuncSignature) {
        
        List<STNode> validatedList = new ArrayList<>();

        for (int i = 0; i < qualifierList.size(); i++) {
            STNode qualifier = qualifierList.get(i);
            int nextIndex = i + 1;

            if (isSyntaxKindInList(validatedList, qualifier.kind)) {
                updateLastNodeInListWithInvalidNode(validatedList, qualifier,
                        DiagnosticErrorCode.ERROR_DUPLICATE_QUALIFIER, ((STToken) qualifier).text());
            } else if (hasFuncSignature && isRegularFuncQual(qualifier.kind)) {
                validatedList.add(qualifier);
            } else if (qualifier.kind == SyntaxKind.ISOLATED_KEYWORD) {
                validatedList.add(qualifier);
            } else if (qualifierList.size() == nextIndex) {
                functionKeyword = SyntaxErrors.cloneWithLeadingInvalidNodeMinutiae(functionKeyword, qualifier,
                        DiagnosticErrorCode.ERROR_QUALIFIER_NOT_ALLOWED, ((STToken) qualifier).text());
            } else {
                updateANodeInListWithLeadingInvalidNode(qualifierList, nextIndex, qualifier,
                        DiagnosticErrorCode.ERROR_QUALIFIER_NOT_ALLOWED, ((STToken) qualifier).text());
            }
        }

        STNode nodeList = STNodeFactory.createNodeList(validatedList);
        return new STNode[]{ nodeList, functionKeyword };
    }

    private boolean isRegularFuncQual(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case ISOLATED_KEYWORD:
            case TRANSACTIONAL_KEYWORD:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse explicit anonymous function expression.
     * <p>
     * <code>explicit-anonymous-function-expr :=
     * [annots] (isolated| transactional) function function-signature anon-func-body</code>
     *
     * @param annots     Annotations.
     * @param qualifiers Function qualifiers
     * @param isRhsExpr  Is expression in rhs context
     * @return Anonymous function expression node
     */
    private STNode parseExplicitFunctionExpression(STNode annots, List<STNode> qualifiers, boolean isRhsExpr) {
        startContext(ParserRuleContext.ANON_FUNC_EXPRESSION);
        STNode funcKeyword = parseFunctionKeyword();

        
        STNode[] nodes = createFuncTypeQualNodeList(qualifiers, funcKeyword, true);
        STNode qualifierList = nodes[0];
        funcKeyword = nodes[1];

        STNode funcSignature = parseFuncSignature(false);
        
        STNode funcBody = parseAnonFuncBody(isRhsExpr);
        return STNodeFactory.createExplicitAnonymousFunctionExpressionNode(annots, qualifierList, funcKeyword,
                funcSignature, funcBody);
    }

    /**
     * Parse anonymous function body.
     * <p>
     * <code>anon-func-body := block-function-body | expr-function-body</code>
     *
     * @param isRhsExpr Is expression in rhs context
     * @return Anon function body node
     */
    private STNode parseAnonFuncBody(boolean isRhsExpr) {
        switch (peek().kind) {
            case OPEN_BRACE_TOKEN:
            case EOF_TOKEN:
                STNode body = parseFunctionBodyBlock(true);
                endContext();
                return body;
            case RIGHT_DOUBLE_ARROW_TOKEN:
                
                
                endContext();
                return parseExpressionFuncBody(true, isRhsExpr);
            default:
                recover(peek(), ParserRuleContext.ANON_FUNC_BODY);
                return parseAnonFuncBody(isRhsExpr);
        }
    }

    /**
     * Parse expression function body.
     * <p>
     * <code>expr-function-body := => expression</code>
     *
     * @param isAnon    Is anonymous function.
     * @param isRhsExpr Is expression in rhs context
     * @return Expression function body node
     */
    private STNode parseExpressionFuncBody(boolean isAnon, boolean isRhsExpr) {
        STNode rightDoubleArrow = parseDoubleRightArrow();

        
        
        STNode expression = parseExpression(OperatorPrecedence.REMOTE_CALL_ACTION, isRhsExpr, false);

        STNode semiColon;
        if (isAnon) {
            semiColon = STNodeFactory.createEmptyNode();
        } else {
            semiColon = parseSemicolon();
        }
        return STNodeFactory.createExpressionFunctionBodyNode(rightDoubleArrow, expression, semiColon);
    }

    /**
     * Parse '=>' token.
     *
     * @return Double right arrow token
     */
    private STNode parseDoubleRightArrow() {
        STToken token = peek();
        if (token.kind == SyntaxKind.RIGHT_DOUBLE_ARROW_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.EXPR_FUNC_BODY_START);
            return parseDoubleRightArrow();
        }
    }

    private STNode parseImplicitAnonFunc(STNode params, boolean isRhsExpr) {
        switch (params.kind) {
            case SIMPLE_NAME_REFERENCE:
            case INFER_PARAM_LIST:
                break;
            case BRACED_EXPRESSION:
                params = getAnonFuncParam((STBracedExpressionNode) params);
                break;
            case NIL_LITERAL:
                STNilLiteralNode nilLiteralNode = (STNilLiteralNode) params;
                params = STNodeFactory.createImplicitAnonymousFunctionParameters(nilLiteralNode.openParenToken,
                        STNodeFactory.createNodeList(new ArrayList<>()), nilLiteralNode.closeParenToken);
                break;
            default:
                STToken syntheticParam = STNodeFactory.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
                syntheticParam = SyntaxErrors.cloneWithLeadingInvalidNodeMinutiae(syntheticParam, params,
                        DiagnosticErrorCode.ERROR_INVALID_PARAM_LIST_IN_INFER_ANONYMOUS_FUNCTION_EXPR);
                params = STNodeFactory.createSimpleNameReferenceNode(syntheticParam);
        }

        STNode rightDoubleArrow = parseDoubleRightArrow();
        
        
        STNode expression = parseExpression(OperatorPrecedence.REMOTE_CALL_ACTION, isRhsExpr, false);
        return STNodeFactory.createImplicitAnonymousFunctionExpressionNode(params, rightDoubleArrow, expression);
    }

    /**
     * Create a new anon-func-param node from a braced expression.
     *
     * @param bracedExpression Braced expression
     * @return Anon-func param node
     */
    private STNode getAnonFuncParam(STBracedExpressionNode bracedExpression) {
        List<STNode> paramList = new ArrayList<>();
        STNode innerExpression = bracedExpression.expression;
        STNode openParen = bracedExpression.openParen;
        if (innerExpression.kind == SyntaxKind.SIMPLE_NAME_REFERENCE) {
            paramList.add(innerExpression);
        } else {
            openParen = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(openParen, innerExpression,
                    DiagnosticErrorCode.ERROR_INVALID_PARAM_LIST_IN_INFER_ANONYMOUS_FUNCTION_EXPR);
        }
        return STNodeFactory.createImplicitAnonymousFunctionParameters(openParen,
                STNodeFactory.createNodeList(paramList), bracedExpression.closeParen);
    }

    /**
     * Parse implicit anon function expression.
     *
     * @param openParen  Open parenthesis token
     * @param firstParam First parameter
     * @param isRhsExpr  Is expression in rhs context
     * @return Implicit anon function expression node
     */
    private STNode parseImplicitAnonFunc(STNode openParen, STNode firstParam, boolean isRhsExpr) {
        List<STNode> paramList = new ArrayList<>();
        paramList.add(firstParam);

        
        STToken nextToken = peek();
        STNode paramEnd;
        STNode param;
        while (!isEndOfAnonFuncParametersList(nextToken.kind)) {
            paramEnd = parseImplicitAnonFuncParamEnd();
            if (paramEnd == null) {
                break;
            }

            paramList.add(paramEnd);
            param = parseIdentifier(ParserRuleContext.IMPLICIT_ANON_FUNC_PARAM);
            param = STNodeFactory.createSimpleNameReferenceNode(param);
            paramList.add(param);
            nextToken = peek();
        }

        STNode params = STNodeFactory.createNodeList(paramList);
        STNode closeParen = parseCloseParenthesis();
        endContext(); 

        STNode inferedParams = STNodeFactory.createImplicitAnonymousFunctionParameters(openParen, params, closeParen);
        return parseImplicitAnonFunc(inferedParams, isRhsExpr);
    }

    private STNode parseImplicitAnonFuncParamEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_PAREN_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.ANON_FUNC_PARAM_RHS);
                return parseImplicitAnonFuncParamEnd();
        }
    }

    private boolean isEndOfAnonFuncParametersList(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case CLOSE_BRACKET_TOKEN:
            case SEMICOLON_TOKEN:
            case RETURNS_KEYWORD:
            case TYPE_KEYWORD:
            case LISTENER_KEYWORD:
            case IF_KEYWORD:
            case WHILE_KEYWORD:
            case DO_KEYWORD:
            case OPEN_BRACE_TOKEN:
            case RIGHT_DOUBLE_ARROW_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse tuple type descriptor.
     * <p>
     * <code>tuple-type-descriptor := [ tuple-member-type-descriptors ]
     * <br/><br/>
     * tuple-member-type-descriptors := member-type-descriptor (, member-type-descriptor)* [, tuple-rest-descriptor]
     * | [ tuple-rest-descriptor ]
     * <br/><br/>
     * member-type-descriptor := [annots] type-descriptor
     * tuple-rest-descriptor := type-descriptor ...
     * </code>
     *
     * @return
     */
    private STNode parseTupleTypeDesc() {
        STNode openBracket = parseOpenBracket();
        startContext(ParserRuleContext.TUPLE_MEMBERS);
        STNode memberTypeDesc = parseTupleMemberTypeDescList();
        STNode closeBracket = parseCloseBracket();
        endContext();

        
        openBracket = cloneWithDiagnosticIfListEmpty(memberTypeDesc, openBracket,
                DiagnosticErrorCode.ERROR_MISSING_TYPE_DESC);

        return STNodeFactory.createTupleTypeDescriptorNode(openBracket, memberTypeDesc, closeBracket);
    }

    /**
     * Parse tuple member type descriptors.
     *
     * @return Parsed node
     */
    private STNode parseTupleMemberTypeDescList() {
        List<STNode> typeDescList = new ArrayList<>();
        STToken nextToken = peek();

        
        if (isEndOfTypeList(nextToken.kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode typeDesc = parseTupleMember();
        return parseTupleTypeMembers(typeDesc, typeDescList);
    }

    private STNode parseTupleTypeMembers(STNode firstMember, List<STNode> memberList) {
        STNode tupleMemberRhs;
        
        while (!isEndOfTypeList(peek().kind)) {
            if (firstMember.kind == SyntaxKind.REST_TYPE) {
                firstMember = invalidateTypeDescAfterRestDesc(firstMember);
                break;
            }

            tupleMemberRhs = parseTupleMemberRhs();
            if (tupleMemberRhs == null) {
                break;
            }
            memberList.add(firstMember);
            memberList.add(tupleMemberRhs);
            firstMember = parseTupleMember();
        }

        memberList.add(firstMember);
        return STNodeFactory.createNodeList(memberList);
    }

    private STNode parseTupleMember() {
        STNode annot = parseOptionalAnnotations();
        STNode typeDesc = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
        return createMemberOrRestNode(annot, typeDesc);
    }

    private STNode createMemberOrRestNode(STNode annot, STNode typeDesc) {
        STNode tupleMemberRhs = parseTypeDescInTupleRhs();
        if (tupleMemberRhs != null) {
            if (!((STNodeList) annot).isEmpty()) {
                typeDesc = SyntaxErrors.cloneWithLeadingInvalidNodeMinutiae(typeDesc, annot,
                        DiagnosticErrorCode.ERROR_ANNOTATIONS_NOT_ALLOWED_FOR_TUPLE_REST_DESCRIPTOR);
            }
            return STNodeFactory.createRestDescriptorNode(typeDesc, tupleMemberRhs);
        }

        return STNodeFactory.createMemberTypeDescriptorNode(annot, typeDesc);
    }

    private STNode invalidateTypeDescAfterRestDesc(STNode restDescriptor) {
        while (!isEndOfTypeList(peek().kind)) {
            STNode tupleMemberRhs = parseTupleMemberRhs();
            if (tupleMemberRhs == null) {
                break;
            }

            restDescriptor = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(restDescriptor, tupleMemberRhs, null);
            restDescriptor = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(restDescriptor, parseTupleMember(),
                    DiagnosticErrorCode.ERROR_TYPE_DESC_AFTER_REST_DESCRIPTOR);
        }

        return restDescriptor;
    }

    private STNode parseTupleMemberRhs() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACKET_TOKEN:
                return null;
            default:
                recover(nextToken, ParserRuleContext.TUPLE_TYPE_MEMBER_RHS);
                return parseTupleMemberRhs();
        }
    }

    private STNode parseTypeDescInTupleRhs() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
            case CLOSE_BRACKET_TOKEN:
                return null;
            case ELLIPSIS_TOKEN:
                return parseEllipsis();
            default:
                recover(nextToken, ParserRuleContext.TYPE_DESC_IN_TUPLE_RHS);
                return parseTypeDescInTupleRhs();
        }
    }

    private boolean isEndOfTypeList(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case CLOSE_BRACKET_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case EOF_TOKEN:
            case EQUAL_TOKEN:
            case SEMICOLON_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse table constructor or query expression.
     * <p>
     * <code>
     * table-constructor-or-query-expr := table-constructor-expr | query-expr
     * <br/>
     * table-constructor-expr := table [key-specifier] [ [row-list] ]
     * <br/>
     * query-expr := [query-construct-type] query-pipeline select-clause
     * [query-construct-type] query-pipeline select-clause on-conflict-clause?
     * <br/>
     * query-construct-type := table key-specifier | stream | map
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseTableConstructorOrQuery(boolean isRhsExpr, boolean allowActions) {
        startContext(ParserRuleContext.TABLE_CONSTRUCTOR_OR_QUERY_EXPRESSION);
        STNode tableOrQueryExpr = parseTableConstructorOrQueryInternal(isRhsExpr, allowActions);
        endContext();
        return tableOrQueryExpr;
    }

    private STNode parseTableConstructorOrQueryInternal(boolean isRhsExpr, boolean allowActions) {
        STNode queryConstructType;
        switch (peek().kind) {
            case FROM_KEYWORD:
                queryConstructType = STNodeFactory.createEmptyNode();
                return parseQueryExprRhs(queryConstructType, isRhsExpr, allowActions);
            case TABLE_KEYWORD:
                STNode tableKeyword = parseTableKeyword();
                return parseTableConstructorOrQuery(tableKeyword, isRhsExpr, allowActions);
            case STREAM_KEYWORD:
            case MAP_KEYWORD:
                STNode streamOrMapKeyword = consume();
                STNode keySpecifier = STNodeFactory.createEmptyNode();
                queryConstructType = parseQueryConstructType(streamOrMapKeyword, keySpecifier);
                return parseQueryExprRhs(queryConstructType, isRhsExpr, allowActions);
            default:
                recover(peek(), ParserRuleContext.TABLE_CONSTRUCTOR_OR_QUERY_START);
                return parseTableConstructorOrQueryInternal(isRhsExpr, allowActions);
        }

    }

    private STNode parseTableConstructorOrQuery(STNode tableKeyword, boolean isRhsExpr, boolean allowActions) {
        STNode keySpecifier;
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_BRACKET_TOKEN:
                keySpecifier = STNodeFactory.createEmptyNode();
                return parseTableConstructorExprRhs(tableKeyword, keySpecifier);
            case KEY_KEYWORD:
                keySpecifier = parseKeySpecifier();
                return parseTableConstructorOrQueryRhs(tableKeyword, keySpecifier, isRhsExpr, allowActions);
            case IDENTIFIER_TOKEN:
                if (isKeyKeyword(nextToken)) {
                    keySpecifier = parseKeySpecifier();
                    return parseTableConstructorOrQueryRhs(tableKeyword, keySpecifier, isRhsExpr, allowActions);
                }
                break;
            default:
                break;
        }

        recover(peek(), ParserRuleContext.TABLE_KEYWORD_RHS);
        return parseTableConstructorOrQuery(tableKeyword, isRhsExpr, allowActions);
    }

    private STNode parseTableConstructorOrQueryRhs(STNode tableKeyword, STNode keySpecifier, boolean isRhsExpr,
                                                   boolean allowActions) {
        switch (peek().kind) {
            case FROM_KEYWORD:
                return parseQueryExprRhs(parseQueryConstructType(tableKeyword, keySpecifier), isRhsExpr, allowActions);
            case OPEN_BRACKET_TOKEN:
                return parseTableConstructorExprRhs(tableKeyword, keySpecifier);
            default:
                recover(peek(), ParserRuleContext.TABLE_CONSTRUCTOR_OR_QUERY_RHS);
                return parseTableConstructorOrQueryRhs(tableKeyword, keySpecifier, isRhsExpr, allowActions);
        }
    }

    /**
     * Parse query construct type.
     * <p>
     * <code>query-construct-type := table key-specifier | stream | map</code>
     *
     * @return Parsed node
     */
    private STNode parseQueryConstructType(STNode keyword, STNode keySpecifier) {
        return STNodeFactory.createQueryConstructTypeNode(keyword, keySpecifier);
    }

    /**
     * Parse query action or expression.
     * <p>
     * <code>
     * query-expr-rhs := query-pipeline select-clause
     * query-pipeline select-clause on-conflict-clause?
     * <br/>
     * query-pipeline := from-clause intermediate-clause*
     * </code>
     *
     * @param queryConstructType queryConstructType that precedes this rhs
     * @return Parsed node
     */
    private STNode parseQueryExprRhs(STNode queryConstructType, boolean isRhsExpr, boolean allowActions) {

        switchContext(ParserRuleContext.QUERY_EXPRESSION);
        STNode fromClause = parseFromClause(isRhsExpr, allowActions);

        List<STNode> clauses = new ArrayList<>();
        STNode intermediateClause;
        STNode selectClause = null;
        STNode collectClause = null;
        while (!isEndOfIntermediateClause(peek().kind)) {
            intermediateClause = parseIntermediateClause(isRhsExpr, allowActions);
            if (intermediateClause == null) {
                break;
            }

            
            if (selectClause != null) {
                selectClause = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(selectClause, intermediateClause,
                        DiagnosticErrorCode.ERROR_MORE_CLAUSES_AFTER_SELECT_CLAUSE);
                continue;
            } else if (collectClause != null) {
                collectClause = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(collectClause, intermediateClause,
                        DiagnosticErrorCode.ERROR_MORE_CLAUSES_AFTER_COLLECT_CLAUSE);
                continue;
            }

            if (intermediateClause.kind == SyntaxKind.SELECT_CLAUSE) {
                selectClause = intermediateClause;
            } else if (intermediateClause.kind == SyntaxKind.COLLECT_CLAUSE) {
                collectClause = intermediateClause;
            } else {
                clauses.add(intermediateClause);
                continue;
            }

            if (isNestedQueryExpr() || !isValidIntermediateQueryStart(peek())) {
                
                
                
                break;
            }
        }

        if (peek().kind == SyntaxKind.DO_KEYWORD) {
            STNode intermediateClauses = STNodeFactory.createNodeList(clauses);
            STNode queryPipeline = STNodeFactory.createQueryPipelineNode(fromClause, intermediateClauses);
            return parseQueryAction(queryConstructType, queryPipeline, selectClause);
        }

        if (selectClause == null && collectClause == null) {
            STNode selectKeyword = SyntaxErrors.createMissingToken(SyntaxKind.SELECT_KEYWORD);
            STNode expr = STNodeFactory
                    .createSimpleNameReferenceNode(SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN));
            selectClause = STNodeFactory.createSelectClauseNode(selectKeyword, expr);

            
            
            if (clauses.isEmpty()) {
                fromClause = SyntaxErrors.addDiagnostic(fromClause, DiagnosticErrorCode.ERROR_MISSING_SELECT_CLAUSE);
            } else {
                int lastIndex = clauses.size() - 1;
                STNode intClauseWithDiagnostic = SyntaxErrors.addDiagnostic(clauses.get(lastIndex),
                        DiagnosticErrorCode.ERROR_MISSING_SELECT_CLAUSE);
                clauses.set(lastIndex, intClauseWithDiagnostic);
            }
        }

        STNode intermediateClauses = STNodeFactory.createNodeList(clauses);
        STNode queryPipeline = STNodeFactory.createQueryPipelineNode(fromClause, intermediateClauses);
        STNode onConflictClause = parseOnConflictClause(isRhsExpr);


        return STNodeFactory.createQueryExpressionNode(queryConstructType, queryPipeline,
                selectClause == null ? collectClause : selectClause, onConflictClause);
    }

    /**
     * Check whether currently parsing query expr is a nested query expression.
     *
     * @return <code>true</code> if currently parsing query-expr is a nested query-expr. <code>false</code> otherwise.
     */
    private boolean isNestedQueryExpr() {
        return Collections.frequency(this.errorHandler.getContextStack(), ParserRuleContext.QUERY_EXPRESSION) > 1;
    }

    private boolean isValidIntermediateQueryStart(STToken token) {
        switch (token.kind) {
            case FROM_KEYWORD:
            case WHERE_KEYWORD:
            case LET_KEYWORD:
            case SELECT_KEYWORD:
            case JOIN_KEYWORD:
            case OUTER_KEYWORD:
            case ORDER_KEYWORD:
            case BY_KEYWORD:
            case ASCENDING_KEYWORD:
            case DESCENDING_KEYWORD:
            case LIMIT_KEYWORD:
                return true;
            case IDENTIFIER_TOKEN:
                return isGroupOrCollectKeyword(token);
            default:
                return false;
        }
    }

    private static boolean isGroupOrCollectKeyword(STToken nextToken) {
        return isKeywordMatch(SyntaxKind.COLLECT_KEYWORD, nextToken)
                || isKeywordMatch(SyntaxKind.GROUP_KEYWORD, nextToken);
    }

    private static boolean isKeywordMatch(SyntaxKind syntaxKind, STToken token) {
        return token.kind == SyntaxKind.IDENTIFIER_TOKEN && syntaxKind.stringValue().equals(token.text());
    }

    /**
     * Parse an intermediate clause.
     * <p>
     * <code>
     * intermediate-clause := from-clause | where-clause | let-clause | join-clause | limit-clause | order-by-clause
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseIntermediateClause(boolean isRhsExpr, boolean allowActions) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case FROM_KEYWORD:
                return parseFromClause(isRhsExpr, allowActions);
            case WHERE_KEYWORD:
                return parseWhereClause(isRhsExpr);
            case LET_KEYWORD:
                return parseLetClause(isRhsExpr, allowActions);
            case SELECT_KEYWORD:
                return parseSelectClause(isRhsExpr, allowActions);
            case JOIN_KEYWORD:
            case OUTER_KEYWORD:
                return parseJoinClause(isRhsExpr);
            case ORDER_KEYWORD:
            case ASCENDING_KEYWORD:
            case DESCENDING_KEYWORD:
                return parseOrderByClause(isRhsExpr);
            case LIMIT_KEYWORD:
                return parseLimitClause(isRhsExpr);
            case DO_KEYWORD:
            case SEMICOLON_TOKEN:
            case ON_KEYWORD:
            case CONFLICT_KEYWORD:
                return null;
            default:
                if (isKeywordMatch(SyntaxKind.COLLECT_KEYWORD, nextToken)) {
                    return parseCollectClause(isRhsExpr);
                }

                if (isKeywordMatch(SyntaxKind.GROUP_KEYWORD, nextToken)) {
                    return parseGroupByClause(isRhsExpr);
                }

                recover(peek(), ParserRuleContext.QUERY_PIPELINE_RHS);
                return parseIntermediateClause(isRhsExpr, allowActions);
        }
    }

    private STNode parseCollectClause(boolean isRhsExpr) {
        startContext(ParserRuleContext.COLLECT_CLAUSE);
        STNode collectKeyword = parseCollectKeyword();
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        endContext();
        return STNodeFactory.createCollectClauseNode(collectKeyword, expression);
    }

    /**
     * Parse collect-keyword.
     *
     * @return collect-keyword node
     */
    private STNode parseCollectKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.COLLECT_KEYWORD) {
            return consume();
        }

        if (isKeywordMatch(SyntaxKind.COLLECT_KEYWORD, token)) {
            
            return getCollectKeyword(consume());
        }

        recover(token, ParserRuleContext.COLLECT_KEYWORD);
        return parseCollectKeyword();
    }

    private STNode getCollectKeyword(STToken token) {
        return STNodeFactory.createToken(SyntaxKind.COLLECT_KEYWORD, token.leadingMinutiae(), token.trailingMinutiae(),
                token.diagnostics());
    }

    /**
     * Parse join-keyword.
     *
     * @return Join-keyword node
     */
    private STNode parseJoinKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.JOIN_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.JOIN_KEYWORD);
            return parseJoinKeyword();
        }
    }

    /**
     * Parse equals keyword.
     *
     * @return Parsed node
     */
    private STNode parseEqualsKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.EQUALS_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.EQUALS_KEYWORD);
            return parseEqualsKeyword();
        }
    }

    private boolean isEndOfIntermediateClause(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case CLOSE_BRACE_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case CLOSE_BRACKET_TOKEN:
            case OPEN_BRACE_TOKEN:
            case SEMICOLON_TOKEN:
            case PUBLIC_KEYWORD:
            case FUNCTION_KEYWORD:
            case EOF_TOKEN:
            case RESOURCE_KEYWORD:
            case LISTENER_KEYWORD:
            case DOCUMENTATION_STRING:
            case PRIVATE_KEYWORD:
            case RETURNS_KEYWORD:
            case SERVICE_KEYWORD:
            case TYPE_KEYWORD:
            case CONST_KEYWORD:
            case FINAL_KEYWORD:
            case DO_KEYWORD:
            case ON_KEYWORD:
            case CONFLICT_KEYWORD:
                return true;
            default:
                return isValidExprRhsStart(tokenKind, SyntaxKind.NONE);
        }
    }

    /**
     * Parse from clause.
     * <p>
     * <code>from-clause := from typed-binding-pattern in expression</code>
     *
     * @return Parsed node
     */
    private STNode parseFromClause(boolean isRhsExpr, boolean allowActions) {
        STNode fromKeyword = parseFromKeyword();
        STNode typedBindingPattern = parseTypedBindingPattern(ParserRuleContext.FROM_CLAUSE);
        STNode inKeyword = parseInKeyword();
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, allowActions);
        return STNodeFactory.createFromClauseNode(fromKeyword, typedBindingPattern, inKeyword, expression);
    }

    /**
     * Parse from-keyword.
     *
     * @return From-keyword node
     */
    private STNode parseFromKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FROM_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FROM_KEYWORD);
            return parseFromKeyword();
        }
    }

    /**
     * Parse where clause.
     * <p>
     * <code>where-clause := where expression</code>
     *
     * @return Parsed node
     */
    private STNode parseWhereClause(boolean isRhsExpr) {
        STNode whereKeyword = parseWhereKeyword();

        
        
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        return STNodeFactory.createWhereClauseNode(whereKeyword, expression);
    }

    /**
     * Parse where-keyword.
     *
     * @return Where-keyword node
     */
    private STNode parseWhereKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.WHERE_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.WHERE_KEYWORD);
            return parseWhereKeyword();
        }
    }

    /**
     * Parse limit-keyword.
     *
     * @return limit-keyword node
     */
    private STNode parseLimitKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.LIMIT_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.LIMIT_KEYWORD);
            return parseLimitKeyword();
        }
    }

    /**
     * Parse let clause.
     * <p>
     * <code>let-clause := let let-var-decl [, let-var-decl]* </code>
     *
     * @return Parsed node
     */
    private STNode parseLetClause(boolean isRhsExpr, boolean allowActions) {
        STNode letKeyword = parseLetKeyword();
        STNode letVarDeclarations = parseLetVarDeclarations(ParserRuleContext.LET_CLAUSE_LET_VAR_DECL, isRhsExpr,
                allowActions);

        
        letKeyword = cloneWithDiagnosticIfListEmpty(letVarDeclarations, letKeyword,
                DiagnosticErrorCode.ERROR_MISSING_LET_VARIABLE_DECLARATION);

        return STNodeFactory.createLetClauseNode(letKeyword, letVarDeclarations);
    }

    /**
     * Parse group by clause.
     * <code>group-by-clause := group by grouping-key-list</code>
     *
     * @return Parsed node
     */
    private STNode parseGroupByClause(boolean isRhsExpr) {
        startContext(ParserRuleContext.GROUP_BY_CLAUSE);
        STNode groupKeyword = parseGroupKeyword();
        STNode byKeyword = parseByKeyword();
        STNode groupingKeys = parseGroupingKeyList(isRhsExpr);
        byKeyword = cloneWithDiagnosticIfListEmpty(groupingKeys, byKeyword,
                    DiagnosticErrorCode.ERROR_MISSING_GROUPING_KEY);
        endContext();
        return STNodeFactory.createGroupByClauseNode(groupKeyword, byKeyword, groupingKeys);
    }

    /**
     * Parse group-keyword.
     *
     * @return group-keyword node
     */
    private STNode parseGroupKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.GROUP_KEYWORD) {
            return consume();
        }

        if (isKeywordMatch(SyntaxKind.GROUP_KEYWORD, token)) {
            
            return getGroupKeyword(consume());
        }

        recover(token, ParserRuleContext.GROUP_KEYWORD);
        return parseGroupKeyword();
    }

    private STNode getGroupKeyword(STToken token) {
        return STNodeFactory.createToken(SyntaxKind.GROUP_KEYWORD, token.leadingMinutiae(), token.trailingMinutiae(),
                token.diagnostics());
    }

    /**
     * Parse order-keyword.
     *
     * @return Order-keyword node
     */
    private STNode parseOrderKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ORDER_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ORDER_KEYWORD);
            return parseOrderKeyword();
        }
    }

    /**
     * Parse by-keyword.
     *
     * @return By-keyword node
     */
    private STNode parseByKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.BY_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.BY_KEYWORD);
            return parseByKeyword();
        }
    }

    /**
     * Parse order by clause.
     * <p>
     * <code>order-by-clause := order by order-key-list
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseOrderByClause(boolean isRhsExpr) {
        STNode orderKeyword = parseOrderKeyword();
        STNode byKeyword = parseByKeyword();
        STNode orderKeys = parseOrderKeyList(isRhsExpr);
        byKeyword = cloneWithDiagnosticIfListEmpty(orderKeys, byKeyword, DiagnosticErrorCode.ERROR_MISSING_ORDER_KEY);
        return STNodeFactory.createOrderByClauseNode(orderKeyword, byKeyword, orderKeys);
    }

    /**
     * Parse grouping key.
     * <code>grouping-key-list := grouping-key ["," grouping-key]*</code>
     *
     * @return Parsed node
     */
    private STNode parseGroupingKeyList(boolean isRhsExpr) {
        List<STNode> groupingKeys = new ArrayList<>();
        STToken nextToken = peek();

        if (isEndOfGroupByKeyListElement(nextToken)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode groupingKey = parseGroupingKey(isRhsExpr);
        groupingKeys.add(groupingKey);

        
        nextToken = peek();
        STNode groupingKeyListMemberEnd;
        while (!isEndOfGroupByKeyListElement(nextToken)) {
            groupingKeyListMemberEnd = parseGroupingKeyListMemberEnd();
            if (groupingKeyListMemberEnd == null) {
                break;
            }

            groupingKeys.add(groupingKeyListMemberEnd);
            groupingKey = parseGroupingKey(isRhsExpr);
            groupingKeys.add(groupingKey);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(groupingKeys);
    }

    /**
     * Parse order key.
     * <p>
     * <code>order-key-list := order-key [, order-key]*</code>
     *
     * @return Parsed node
     */
    private STNode parseOrderKeyList(boolean isRhsExpr) {
        startContext(ParserRuleContext.ORDER_KEY_LIST);
        List<STNode> orderKeys = new ArrayList<>();
        STToken nextToken = peek();

        if (isEndOfOrderKeys(nextToken)) {
            endContext();
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode orderKey = parseOrderKey(isRhsExpr);
        orderKeys.add(orderKey);

        
        nextToken = peek();
        STNode orderKeyListMemberEnd;
        while (!isEndOfOrderKeys(nextToken)) {
            orderKeyListMemberEnd = parseOrderKeyListMemberEnd();
            if (orderKeyListMemberEnd == null) {
                break;
            }
            orderKeys.add(orderKeyListMemberEnd);
            orderKey = parseOrderKey(isRhsExpr);
            orderKeys.add(orderKey);
            nextToken = peek();
        }

        endContext();
        return STNodeFactory.createNodeList(orderKeys);
    }

    private boolean isEndOfGroupByKeyListElement(STToken nextToken) {
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return false;
            case EOF_TOKEN:
                return true;
            default:
                return isQueryClauseStartToken(nextToken);
        }
    }

    private boolean isEndOfOrderKeys(STToken nextToken) {
        switch (nextToken.kind) {
            case COMMA_TOKEN:
            case ASCENDING_KEYWORD:
            case DESCENDING_KEYWORD:
                return false;
            case SEMICOLON_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return isQueryClauseStartToken(nextToken);
        }
    }

    private boolean isQueryClauseStartToken(STToken nextToken) {
        switch (nextToken.kind) {
            case SELECT_KEYWORD:
            case LET_KEYWORD:
            case WHERE_KEYWORD:
            case OUTER_KEYWORD:
            case JOIN_KEYWORD:
            case ORDER_KEYWORD:
            case DO_KEYWORD:
            case FROM_KEYWORD:
            case LIMIT_KEYWORD:
                return true;
            case IDENTIFIER_TOKEN:
                return isGroupOrCollectKeyword(nextToken);
            default:
                return false;
        }
    }

    private STNode parseGroupingKeyListMemberEnd() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return consume();
            case EOF_TOKEN:
                return null;
            default:
                if (isQueryClauseStartToken(nextToken)) {
                    
                    return null;
                }
                recover(peek(), ParserRuleContext.GROUPING_KEY_LIST_ELEMENT_END);
                return parseGroupingKeyListMemberEnd();
        }
    }

    private STNode parseOrderKeyListMemberEnd() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return parseComma();
            case EOF_TOKEN:
                return null;
            default:
                if (isQueryClauseStartToken(nextToken)) {
                    
                    return null;
                }

                recover(peek(), ParserRuleContext.ORDER_KEY_LIST_END);
                return parseOrderKeyListMemberEnd();
        }
    }

    private STNode parseGroupingKeyVariableDeclaration(boolean isRhsExpr) {
        STNode groupingKeyElementTypeDesc =
                parseTypeDescriptor(ParserRuleContext.TYPE_DESC_BEFORE_IDENTIFIER_IN_GROUPING_KEY);
        startContext(ParserRuleContext.BINDING_PATTERN_STARTING_IDENTIFIER);
        STNode groupingKeySimpleBP = createCaptureOrWildcardBP(parseVariableName());
        endContext();
        STNode equalsToken = parseAssignOp();
        STNode groupingKeyExpression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        return STNodeFactory.createGroupingKeyVarDeclarationNode(groupingKeyElementTypeDesc, groupingKeySimpleBP,
                equalsToken, groupingKeyExpression);
    }

    /**
     * Parse grouping key.
     * <code>grouping-key := variable-name | inferable-type-descriptor variable-name "=" expression</code>
     *
     * @return Parsed node
     */
    private STNode parseGroupingKey(boolean isRhsExpr) {
        STToken nextToken = peek();
        SyntaxKind nextTokenKind = nextToken.kind;
        if (nextTokenKind == SyntaxKind.IDENTIFIER_TOKEN && !isPossibleGroupingKeyVarDeclaration()) {
            return STNodeFactory.createSimpleNameReferenceNode(parseVariableName());
        } else if (isTypeStartingToken(nextTokenKind, nextToken)) {
            return parseGroupingKeyVariableDeclaration(isRhsExpr);
        }

        recover(nextToken, ParserRuleContext.GROUPING_KEY_LIST_ELEMENT);
        return parseGroupingKey(isRhsExpr);
    }

    private boolean isPossibleGroupingKeyVarDeclaration() {
        SyntaxKind nextNextTokenKind = getNextNextToken().kind;
        return nextNextTokenKind == SyntaxKind.EQUAL_TOKEN ||
                nextNextTokenKind == SyntaxKind.IDENTIFIER_TOKEN && peek(3).kind == SyntaxKind.EQUAL_TOKEN;
    }

    /**
     * Parse order key.
     * <p>
     * <code>order-key := expression (ascending | descending)?</code>
     *
     * @return Parsed node
     */
    private STNode parseOrderKey(boolean isRhsExpr) {
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);

        STNode orderDirection;
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case ASCENDING_KEYWORD:
            case DESCENDING_KEYWORD:
                orderDirection = consume();
                break;
            default:
                orderDirection = STNodeFactory.createEmptyNode();
        }

        return STNodeFactory.createOrderKeyNode(expression, orderDirection);
    }

    /**
     * Parse select clause.
     * <p>
     * <code>select-clause := select expression</code>
     *
     * @return Parsed node
     */
    private STNode parseSelectClause(boolean isRhsExpr, boolean allowActions) {
        startContext(ParserRuleContext.SELECT_CLAUSE);
        STNode selectKeyword = parseSelectKeyword();
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, allowActions);
        endContext();
        return STNodeFactory.createSelectClauseNode(selectKeyword, expression);
    }

    /**
     * Parse select-keyword.
     *
     * @return Select-keyword node
     */
    private STNode parseSelectKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.SELECT_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.SELECT_KEYWORD);
            return parseSelectKeyword();
        }
    }

    /**
     * Parse on-conflict clause.
     * <p>
     * <code>
     * onConflictClause := on conflict expression
     * </code>
     *
     * @return On conflict clause node
     */
    private STNode parseOnConflictClause(boolean isRhsExpr) {
        STToken nextToken = peek();
        if (nextToken.kind != SyntaxKind.ON_KEYWORD && nextToken.kind != SyntaxKind.CONFLICT_KEYWORD) {
            return STNodeFactory.createEmptyNode();
        }

        startContext(ParserRuleContext.ON_CONFLICT_CLAUSE);
        STNode onKeyword = parseOnKeyword();
        STNode conflictKeyword = parseConflictKeyword();
        endContext();
        STNode expr = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        return STNodeFactory.createOnConflictClauseNode(onKeyword, conflictKeyword, expr);
    }

    /**
     * Parse conflict keyword.
     *
     * @return Conflict keyword node
     */
    private STNode parseConflictKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.CONFLICT_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.CONFLICT_KEYWORD);
            return parseConflictKeyword();
        }
    }

    /**
     * Parse limit clause.
     * <p>
     * <code>limitClause := limit expression</code>
     *
     * @return Limit expression node
     */
    private STNode parseLimitClause(boolean isRhsExpr) {
        STNode limitKeyword = parseLimitKeyword();
        
        
        STNode expr = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        return STNodeFactory.createLimitClauseNode(limitKeyword, expr);
    }

    /**
     * Parse join clause.
     * <p>
     * <code>
     * join-clause := (join-var-decl | outer-join-var-decl) in expression on-clause
     * <br/>
     * join-var-decl := join (typeName | var) bindingPattern
     * <br/>
     * outer-join-var-decl := outer join var binding-pattern
     * </code>
     *
     * @return Join clause
     */
    private STNode parseJoinClause(boolean isRhsExpr) {
        startContext(ParserRuleContext.JOIN_CLAUSE);
        STNode outerKeyword;
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.OUTER_KEYWORD) {
            outerKeyword = consume();
        } else {
            outerKeyword = STNodeFactory.createEmptyNode();
        }

        STNode joinKeyword = parseJoinKeyword();
        STNode typedBindingPattern = parseTypedBindingPattern(ParserRuleContext.JOIN_CLAUSE);
        STNode inKeyword = parseInKeyword();
        
        
        STNode expression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        endContext();
        STNode onCondition = parseOnClause(isRhsExpr);
        return STNodeFactory.createJoinClauseNode(outerKeyword, joinKeyword, typedBindingPattern, inKeyword, expression,
                onCondition);
    }

    /**
     * Parse on clause.
     * <p>
     * <code>on clause := `on` expression `equals` expression</code>
     *
     * @return On clause node
     */
    private STNode parseOnClause(boolean isRhsExpr) {
        STToken nextToken = peek();
        if (isQueryClauseStartToken(nextToken)) {
            return createMissingOnClauseNode();
        }

        startContext(ParserRuleContext.ON_CLAUSE);
        STNode onKeyword = parseOnKeyword();
        STNode lhsExpression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        STNode equalsKeyword = parseEqualsKeyword();
        endContext();
        STNode rhsExpression = parseExpression(OperatorPrecedence.QUERY, isRhsExpr, false);
        return STNodeFactory.createOnClauseNode(onKeyword, lhsExpression, equalsKeyword, rhsExpression);
    }

    private STNode createMissingOnClauseNode() {
        STNode onKeyword = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.ON_KEYWORD,
                DiagnosticErrorCode.ERROR_MISSING_ON_KEYWORD);
        STNode identifier = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                DiagnosticErrorCode.ERROR_MISSING_IDENTIFIER);
        STNode equalsKeyword = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.EQUALS_KEYWORD,
                DiagnosticErrorCode.ERROR_MISSING_EQUALS_KEYWORD);

        STNode lhsExpression = STNodeFactory.createSimpleNameReferenceNode(identifier);
        STNode rhsExpression = STNodeFactory.createSimpleNameReferenceNode(identifier);
        return STNodeFactory.createOnClauseNode(onKeyword, lhsExpression, equalsKeyword, rhsExpression);
    }

    /**
     * Parse start action.
     * <p>
     * <code>start-action := [annots] start (function-call-expr|method-call-expr|remote-method-call-action)</code>
     *
     * @return Start action node
     */
    private STNode parseStartAction(STNode annots) {
        STNode startKeyword = parseStartKeyword();
        STNode expr = parseActionOrExpression();

        
        switch (expr.kind) {
            case FUNCTION_CALL:
            case METHOD_CALL:
            case REMOTE_METHOD_CALL_ACTION:
                break;
            case SIMPLE_NAME_REFERENCE:
            case QUALIFIED_NAME_REFERENCE:
            case FIELD_ACCESS:
            case ASYNC_SEND_ACTION:
                expr = generateValidExprForStartAction(expr);
                break;
            default:
                startKeyword = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(startKeyword, expr,
                        DiagnosticErrorCode.ERROR_INVALID_EXPRESSION_IN_START_ACTION);
                STNode funcName = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
                funcName = STNodeFactory.createSimpleNameReferenceNode(funcName);
                STNode openParenToken = SyntaxErrors.createMissingToken(SyntaxKind.OPEN_PAREN_TOKEN);
                STNode closeParenToken = SyntaxErrors.createMissingToken(SyntaxKind.CLOSE_PAREN_TOKEN);
                expr = STNodeFactory.createFunctionCallExpressionNode(funcName, openParenToken,
                        STNodeFactory.createEmptyNodeList(), closeParenToken);
                break;
        }

        return STNodeFactory.createStartActionNode(getAnnotations(annots), startKeyword, expr);
    }

    private STNode generateValidExprForStartAction(STNode expr) {
        STNode openParenToken = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.OPEN_PAREN_TOKEN,
                DiagnosticErrorCode.ERROR_MISSING_OPEN_PAREN_TOKEN);
        STNode arguments = STNodeFactory.createEmptyNodeList();
        STNode closeParenToken = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.CLOSE_PAREN_TOKEN,
                DiagnosticErrorCode.ERROR_MISSING_CLOSE_PAREN_TOKEN);

        switch (expr.kind) {
            case FIELD_ACCESS:
                STFieldAccessExpressionNode fieldAccessExpr = (STFieldAccessExpressionNode) expr;
                return STNodeFactory.createMethodCallExpressionNode(fieldAccessExpr.expression,
                        fieldAccessExpr.dotToken, fieldAccessExpr.fieldName, openParenToken, arguments,
                        closeParenToken);
            case ASYNC_SEND_ACTION:
                STAsyncSendActionNode asyncSendAction = (STAsyncSendActionNode) expr;
                return STNodeFactory.createRemoteMethodCallActionNode(asyncSendAction.expression,
                        asyncSendAction.rightArrowToken, asyncSendAction.peerWorker, openParenToken, arguments,
                        closeParenToken);
            default: 
                return STNodeFactory.createFunctionCallExpressionNode(expr, openParenToken, arguments, closeParenToken);
        }
    }

    /**
     * Parse start keyword.
     *
     * @return Start keyword node
     */
    private STNode parseStartKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.START_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.START_KEYWORD);
            return parseStartKeyword();
        }
    }

    /**
     * Parse flush action.
     * <p>
     * <code>flush-action := flush [peer-worker]</code>
     *
     * @return flush action node
     */
    private STNode parseFlushAction() {
        STNode flushKeyword = parseFlushKeyword();
        STNode peerWorker = parseOptionalPeerWorkerName();
        return STNodeFactory.createFlushActionNode(flushKeyword, peerWorker);
    }

    /**
     * Parse flush keyword.
     *
     * @return flush keyword node
     */
    private STNode parseFlushKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.FLUSH_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.FLUSH_KEYWORD);
            return parseFlushKeyword();
        }
    }

    /**
     * Parse peer worker.
     * <p>
     * <code>peer-worker := worker-name | function</code>
     *
     * @return peer worker name node
     */
    private STNode parseOptionalPeerWorkerName() {
        STToken token = peek();
        switch (token.kind) {
            case IDENTIFIER_TOKEN:
            case FUNCTION_KEYWORD:
                return STNodeFactory.createSimpleNameReferenceNode(consume());
            default:
                return STNodeFactory.createEmptyNode();
        }
    }

    /**
     * Parse intersection type descriptor.
     * <p>
     * intersection-type-descriptor := type-descriptor & type-descriptor
     * </p>
     *
     * @return Parsed node
     */
    private STNode parseIntersectionTypeDescriptor(STNode leftTypeDesc, ParserRuleContext context,
                                                   boolean isTypedBindingPattern) {
        
        STNode bitwiseAndToken = consume();
        STNode rightTypeDesc = parseTypeDescriptorInternal(new ArrayList<>(), context, isTypedBindingPattern, false,
                TypePrecedence.INTERSECTION);
        return mergeTypesWithIntersection(leftTypeDesc, bitwiseAndToken, rightTypeDesc);
    }

    /**
     * Creates an intersection type descriptor after validating lhs and rhs types.
     * <p>
     * <i>Note: Since type precedence and associativity are not taken into account here,
     * this method should not be called directly when types are unknown.
     * <br/>
     * Call {@link 
     *
     * @param leftTypeDesc    lhs type
     * @param bitwiseAndToken bitwise-and token
     * @param rightTypeDesc   rhs type
     * @return an IntersectionTypeDescriptorNode
     */
    private STNode createIntersectionTypeDesc(STNode leftTypeDesc, STNode bitwiseAndToken, STNode rightTypeDesc) {
        leftTypeDesc = validateForUsageOfVar(leftTypeDesc);
        rightTypeDesc = validateForUsageOfVar(rightTypeDesc);
        return STNodeFactory.createIntersectionTypeDescriptorNode(leftTypeDesc, bitwiseAndToken, rightTypeDesc);
    }

    /**
     * Parse singleton type descriptor.
     * <p>
     * singleton-type-descriptor := simple-const-expr
     * simple-const-expr :=
     * nil-literal
     * | boolean-literal
     * | [Sign] int-literal
     * | [Sign] floating-point-literal
     * | string-literal
     * | constant-reference-expr
     * </p>
     */
    private STNode parseSingletonTypeDesc() {
        STNode simpleContExpr = parseSimpleConstExpr();
        return STNodeFactory.createSingletonTypeDescriptorNode(simpleContExpr);
    }

    
    private STNode parseSignedIntOrFloat() {
        STNode operator = parseUnaryOperator();
        STNode literal;
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
                literal = parseBasicLiteral();
                break;
            default: 
                literal = parseDecimalIntLiteral(ParserRuleContext.DECIMAL_INTEGER_LITERAL_TOKEN);
                literal = STNodeFactory.createBasicLiteralNode(SyntaxKind.NUMERIC_LITERAL, literal);
        }
        return STNodeFactory.createUnaryExpressionNode(operator, literal);
    }

    private static boolean isSingletonTypeDescStart(SyntaxKind tokenKind, STToken nextNextToken) {
        switch (tokenKind) {
            case STRING_LITERAL_TOKEN:
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case NULL_KEYWORD:
                return true;
            case PLUS_TOKEN:
            case MINUS_TOKEN:
                return isIntOrFloat(nextNextToken);
            default:
                return false;
        }
    }

    static boolean isIntOrFloat(STToken token) {
        switch (token.kind) {
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Check whether the parser reached to a valid expression start.
     *
     * @param nextTokenKind  Kind of the next immediate token.
     * @param nextTokenIndex Index to the next token.
     * @return <code>true</code> if this is a start of a valid expression. <code>false</code> otherwise
     */
    private boolean isValidExpressionStart(SyntaxKind nextTokenKind, int nextTokenIndex) {
        nextTokenIndex++;
        switch (nextTokenKind) {
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case STRING_LITERAL_TOKEN:
            case NULL_KEYWORD:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
                SyntaxKind nextNextTokenKind = peek(nextTokenIndex).kind;
                if (nextNextTokenKind == SyntaxKind.PIPE_TOKEN || nextNextTokenKind == SyntaxKind.BITWISE_AND_TOKEN) {
                    
                    nextTokenIndex++;
                    return isValidExpressionStart(peek(nextTokenIndex).kind, nextTokenIndex);
                }

                return nextNextTokenKind == SyntaxKind.SEMICOLON_TOKEN ||
                        nextNextTokenKind == SyntaxKind.COMMA_TOKEN ||
                        nextNextTokenKind == SyntaxKind.CLOSE_BRACKET_TOKEN ||
                        isValidExprRhsStart(nextNextTokenKind, SyntaxKind.SIMPLE_NAME_REFERENCE);
            case IDENTIFIER_TOKEN:
                return isValidExprRhsStart(peek(nextTokenIndex).kind, SyntaxKind.SIMPLE_NAME_REFERENCE);
            case OPEN_PAREN_TOKEN:
            case CHECK_KEYWORD:
            case CHECKPANIC_KEYWORD:
            case OPEN_BRACE_TOKEN:
            case TYPEOF_KEYWORD:
            case NEGATION_TOKEN:
            case EXCLAMATION_MARK_TOKEN:
            case TRAP_KEYWORD:
            case OPEN_BRACKET_TOKEN:
            case LT_TOKEN:
            case FROM_KEYWORD:
            case LET_KEYWORD:
            case BACKTICK_TOKEN:
            case NEW_KEYWORD:
            case LEFT_ARROW_TOKEN:
            case FUNCTION_KEYWORD:
            case TRANSACTIONAL_KEYWORD:
            case ISOLATED_KEYWORD:
            case BASE16_KEYWORD:
            case BASE64_KEYWORD:
                return true;
            case PLUS_TOKEN:
            case MINUS_TOKEN:
                return isValidExpressionStart(peek(nextTokenIndex).kind, nextTokenIndex);
            case TABLE_KEYWORD:
            case MAP_KEYWORD:
                return peek(nextTokenIndex).kind == SyntaxKind.FROM_KEYWORD;
            case STREAM_KEYWORD:
                STToken nextNextToken = peek(nextTokenIndex);
                return nextNextToken.kind == SyntaxKind.KEY_KEYWORD ||
                        nextNextToken.kind == SyntaxKind.OPEN_BRACKET_TOKEN ||
                        nextNextToken.kind == SyntaxKind.FROM_KEYWORD;
            case ERROR_KEYWORD:
                return peek(nextTokenIndex).kind == SyntaxKind.OPEN_PAREN_TOKEN;
            case XML_KEYWORD:
            case STRING_KEYWORD:
            case RE_KEYWORD:
                return peek(nextTokenIndex).kind == SyntaxKind.BACKTICK_TOKEN;

            
            case START_KEYWORD:
            case FLUSH_KEYWORD:
            case WAIT_KEYWORD:
            default:
                return false;
        }
    }

    /**
     * Parse sync send action.
     * <p>
     * <code>sync-send-action := expression ->> peer-worker</code>
     *
     * @param expression LHS expression of the sync send action
     * @return Sync send action node
     */
    private STNode parseSyncSendAction(STNode expression) {
        STNode syncSendToken = parseSyncSendToken();
        STNode peerWorker = parsePeerWorkerName();
        return STNodeFactory.createSyncSendActionNode(expression, syncSendToken, peerWorker);
    }

    /**
     * Parse peer worker.
     * <p>
     * <code>peer-worker := worker-name | function</code>
     *
     * @return peer worker name node
     */
    private STNode parsePeerWorkerName() {
        STToken token = peek();
        switch (token.kind) {
            case IDENTIFIER_TOKEN:
            case FUNCTION_KEYWORD:
                return STNodeFactory.createSimpleNameReferenceNode(consume());
            default:
                recover(token, ParserRuleContext.PEER_WORKER_NAME);
                return parsePeerWorkerName();
        }
    }

    /**
     * Parse sync send token.
     * <p>
     * <code>sync-send-token :=  ->> </code>
     *
     * @return sync send token
     */
    private STNode parseSyncSendToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.SYNC_SEND_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.SYNC_SEND_TOKEN);
            return parseSyncSendToken();
        }
    }

    /**
     * Parse receive action.
     * <p>
     * <code>receive-action := single-receive-action | multiple-receive-action</code>
     *
     * @return Receive action
     */
    private STNode parseReceiveAction() {
        STNode leftArrow = parseLeftArrowToken();
        STNode receiveWorkers = parseReceiveWorkers();
        return STNodeFactory.createReceiveActionNode(leftArrow, receiveWorkers);
    }

    private STNode parseReceiveWorkers() {
        switch (peek().kind) {
            case FUNCTION_KEYWORD:
            case IDENTIFIER_TOKEN:
                return parsePeerWorkerName();
            case OPEN_BRACE_TOKEN:
                return parseMultipleReceiveWorkers();
            default:
                recover(peek(), ParserRuleContext.RECEIVE_WORKERS);
                return parseReceiveWorkers();
        }
    }

    /**
     * Parse multiple worker receivers.
     * <p>
     * <code>{ receive-field (, receive-field)* }</code>
     *
     * @return Multiple worker receiver node
     */
    private STNode parseMultipleReceiveWorkers() {
        startContext(ParserRuleContext.MULTI_RECEIVE_WORKERS);
        STNode openBrace = parseOpenBrace();
        STNode receiveFields = parseReceiveFields();
        STNode closeBrace = parseCloseBrace();
        endContext();

        openBrace = cloneWithDiagnosticIfListEmpty(receiveFields, openBrace,
                DiagnosticErrorCode.ERROR_MISSING_RECEIVE_FIELD_IN_RECEIVE_ACTION);
        return STNodeFactory.createReceiveFieldsNode(openBrace, receiveFields, closeBrace);
    }

    private STNode parseReceiveFields() {
        List<STNode> receiveFields = new ArrayList<>();
        STToken nextToken = peek();

        
        if (isEndOfReceiveFields(nextToken.kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode receiveField = parseReceiveField();
        receiveFields.add(receiveField);

        
        nextToken = peek();
        STNode recieveFieldEnd;
        while (!isEndOfReceiveFields(nextToken.kind)) {
            recieveFieldEnd = parseReceiveFieldEnd();
            if (recieveFieldEnd == null) {
                break;
            }

            receiveFields.add(recieveFieldEnd);
            receiveField = parseReceiveField();
            receiveFields.add(receiveField);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(receiveFields);
    }

    private boolean isEndOfReceiveFields(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACE_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseReceiveFieldEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACE_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.RECEIVE_FIELD_END);
                return parseReceiveFieldEnd();
        }
    }

    /**
     * Parse receive field.
     * <p>
     * <code>receive-field := peer-worker | field-name : peer-worker</code>
     *
     * @return Receiver field node
     */
    private STNode parseReceiveField() {
        switch (peek().kind) {
            case FUNCTION_KEYWORD:
                STNode functionKeyword = consume();
                return STNodeFactory.createSimpleNameReferenceNode(functionKeyword);
            case IDENTIFIER_TOKEN:
                STNode identifier = parseIdentifier(ParserRuleContext.RECEIVE_FIELD_NAME);
                return createQualifiedReceiveField(identifier);
            default:
                recover(peek(), ParserRuleContext.RECEIVE_FIELD);
                return parseReceiveField();
        }
    }

    private STNode createQualifiedReceiveField(STNode identifier) {
        if (peek().kind != SyntaxKind.COLON_TOKEN) {
            return identifier;
        }

        STNode colon = parseColon();
        STNode peerWorker = parsePeerWorkerName();
        return createQualifiedNameReferenceNode(identifier, colon, peerWorker);
    }

    /**
     * Parse left arrow (<-) token.
     *
     * @return left arrow token
     */
    private STNode parseLeftArrowToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.LEFT_ARROW_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.LEFT_ARROW_TOKEN);
            return parseLeftArrowToken();
        }
    }

    /**
     * Parse signed right shift token (>>).
     * This method should only be called by seeing a `DOUBLE_GT_TOKEN` or
     * by seeing a `GT_TOKEN` followed by a `GT_TOKEN`
     *
     * @return Parsed node
     */
    private STNode parseSignedRightShiftToken() {
        STNode firstToken = consume();
        if (firstToken.kind == SyntaxKind.DOUBLE_GT_TOKEN) {
            return firstToken;
        }

        STToken endLGToken = consume();
        STNode doubleGTToken = STNodeFactory.createToken(SyntaxKind.DOUBLE_GT_TOKEN, firstToken.leadingMinutiae(),
                endLGToken.trailingMinutiae());

        if (hasTrailingMinutiae(firstToken)) {
            doubleGTToken = SyntaxErrors.addDiagnostic(doubleGTToken,
                    DiagnosticErrorCode.ERROR_NO_WHITESPACES_ALLOWED_IN_RIGHT_SHIFT_OP);
        }
        return doubleGTToken;
    }

    /**
     * Parse unsigned right shift token (>>>).
     * This method should only be called by seeing a `TRIPPLE_GT_TOKEN` or
     * by seeing a `GT_TOKEN` followed by two `GT_TOKEN`s
     *
     * @return Parsed node
     */
    private STNode parseUnsignedRightShiftToken() {
        STNode firstToken = consume();
        if (firstToken.kind == SyntaxKind.TRIPPLE_GT_TOKEN) {
            return firstToken;
        }

        STNode middleGTToken = consume();
        STNode endLGToken = consume();
        STNode unsignedRightShiftToken = STNodeFactory.createToken(SyntaxKind.TRIPPLE_GT_TOKEN,
                firstToken.leadingMinutiae(), endLGToken.trailingMinutiae());

        boolean validOpenGTToken = !hasTrailingMinutiae(firstToken);
        boolean validMiddleGTToken = !hasTrailingMinutiae(middleGTToken);
        if (validOpenGTToken && validMiddleGTToken) {
            return unsignedRightShiftToken;
        }

        unsignedRightShiftToken = SyntaxErrors.addDiagnostic(unsignedRightShiftToken,
                DiagnosticErrorCode.ERROR_NO_WHITESPACES_ALLOWED_IN_UNSIGNED_RIGHT_SHIFT_OP);
        return unsignedRightShiftToken;
    }

    /**
     * Parse wait action.
     * <p>
     * <code>wait-action := single-wait-action | multiple-wait-action | alternate-wait-action </code>
     *
     * @return Wait action node
     */
    private STNode parseWaitAction() {
        STNode waitKeyword = parseWaitKeyword();
        if (peek().kind == SyntaxKind.OPEN_BRACE_TOKEN) {
            return parseMultiWaitAction(waitKeyword);
        }

        return parseSingleOrAlternateWaitAction(waitKeyword);
    }

    /**
     * Parse wait keyword.
     *
     * @return wait keyword
     */
    private STNode parseWaitKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.WAIT_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.WAIT_KEYWORD);
            return parseWaitKeyword();
        }
    }

    /**
     * Parse single or alternate wait actions.
     * <p>
     * <code>
     * alternate-or-single-wait-action := wait wait-future-expr (| wait-future-expr)+
     * <br/>
     * wait-future-expr := expression but not mapping-constructor-expr
     * </code>
     *
     * @param waitKeyword wait keyword
     * @return Single or alternate wait action node
     */
    private STNode parseSingleOrAlternateWaitAction(STNode waitKeyword) {
        startContext(ParserRuleContext.ALTERNATE_WAIT_EXPRS);
        STToken nextToken = peek();

        
        if (isEndOfWaitFutureExprList(nextToken.kind)) {
            endContext();
            STNode waitFutureExprs = STNodeFactory
                    .createSimpleNameReferenceNode(STNodeFactory.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN));
            waitFutureExprs = SyntaxErrors.addDiagnostic(waitFutureExprs,
                    DiagnosticErrorCode.ERROR_MISSING_WAIT_FUTURE_EXPRESSION);
            return STNodeFactory.createWaitActionNode(waitKeyword, waitFutureExprs);
        }

        
        List<STNode> waitFutureExprList = new ArrayList<>();
        STNode waitField = parseWaitFutureExpr();
        waitFutureExprList.add(waitField);

        
        nextToken = peek();
        STNode waitFutureExprEnd;
        while (!isEndOfWaitFutureExprList(nextToken.kind)) {
            waitFutureExprEnd = parseWaitFutureExprEnd();
            if (waitFutureExprEnd == null) {
                break;
            }

            waitFutureExprList.add(waitFutureExprEnd);
            waitField = parseWaitFutureExpr();
            waitFutureExprList.add(waitField);
            nextToken = peek();
        }

        
        
        endContext();
        return STNodeFactory.createWaitActionNode(waitKeyword, waitFutureExprList.get(0));
    }

    private boolean isEndOfWaitFutureExprList(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case SEMICOLON_TOKEN:
            case OPEN_BRACE_TOKEN:
                return true;
            case PIPE_TOKEN:
            default:
                return false;
        }
    }

    private STNode parseWaitFutureExpr() {
        STNode waitFutureExpr = parseActionOrExpression();
        if (waitFutureExpr.kind == SyntaxKind.MAPPING_CONSTRUCTOR) {
            waitFutureExpr = SyntaxErrors.addDiagnostic(waitFutureExpr,
                    DiagnosticErrorCode.ERROR_MAPPING_CONSTRUCTOR_EXPR_AS_A_WAIT_EXPR);
        } else if (isAction(waitFutureExpr)) {
            waitFutureExpr =
                    SyntaxErrors.addDiagnostic(waitFutureExpr, DiagnosticErrorCode.ERROR_ACTION_AS_A_WAIT_EXPR);
        }
        return waitFutureExpr;
    }

    private STNode parseWaitFutureExprEnd() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case PIPE_TOKEN:
                return parsePipeToken();
            default:
                if (isEndOfWaitFutureExprList(nextToken.kind) || !isValidExpressionStart(nextToken.kind, 1)) {
                    return null;
                }

                recover(peek(), ParserRuleContext.WAIT_FUTURE_EXPR_END);
                return parseWaitFutureExprEnd();
        }
    }

    /**
     * Parse multiple wait action.
     * <p>
     * <code>multiple-wait-action := wait { wait-field (, wait-field)* }</code>
     *
     * @param waitKeyword Wait keyword
     * @return Multiple wait action node
     */
    private STNode parseMultiWaitAction(STNode waitKeyword) {
        startContext(ParserRuleContext.MULTI_WAIT_FIELDS);
        STNode openBrace = parseOpenBrace();
        STNode waitFields = parseWaitFields();
        STNode closeBrace = parseCloseBrace();
        endContext();

        openBrace = cloneWithDiagnosticIfListEmpty(waitFields, openBrace,
                DiagnosticErrorCode.ERROR_MISSING_WAIT_FIELD_IN_WAIT_ACTION);
        STNode waitFieldsNode = STNodeFactory.createWaitFieldsListNode(openBrace, waitFields, closeBrace);
        return STNodeFactory.createWaitActionNode(waitKeyword, waitFieldsNode);
    }

    private STNode parseWaitFields() {
        List<STNode> waitFields = new ArrayList<>();
        STToken nextToken = peek();

        
        if (isEndOfWaitFields(nextToken.kind)) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        STNode waitField = parseWaitField();
        waitFields.add(waitField);

        
        nextToken = peek();
        STNode waitFieldEnd;
        while (!isEndOfWaitFields(nextToken.kind)) {
            waitFieldEnd = parseWaitFieldEnd();
            if (waitFieldEnd == null) {
                break;
            }

            waitFields.add(waitFieldEnd);
            waitField = parseWaitField();
            waitFields.add(waitField);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(waitFields);
    }

    private boolean isEndOfWaitFields(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACE_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseWaitFieldEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACE_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.WAIT_FIELD_END);
                return parseWaitFieldEnd();
        }
    }

    /**
     * Parse wait field.
     * <p>
     * <code>wait-field := variable-name | field-name : wait-future-expr</code>
     *
     * @return Receiver field node
     */
    private STNode parseWaitField() {
        switch (peek().kind) {
            case IDENTIFIER_TOKEN:
                STNode identifier = parseIdentifier(ParserRuleContext.WAIT_FIELD_NAME);
                identifier = STNodeFactory.createSimpleNameReferenceNode(identifier);
                return createQualifiedWaitField(identifier);
            default:
                recover(peek(), ParserRuleContext.WAIT_FIELD_NAME);
                return parseWaitField();
        }
    }

    private STNode createQualifiedWaitField(STNode identifier) {
        if (peek().kind != SyntaxKind.COLON_TOKEN) {
            return identifier;
        }

        STNode colon = parseColon();
        STNode waitFutureExpr = parseWaitFutureExpr();
        return STNodeFactory.createWaitFieldNode(identifier, colon, waitFutureExpr);
    }

    /**
     * Parse annot access expression.
     * <p>
     * <code>
     * annot-access-expr := expression .@ annot-tag-reference
     * <br/>
     * annot-tag-reference := qualified-identifier | identifier
     * </code>
     *
     * @param lhsExpr Preceding expression of the annot access access
     * @return Parsed node
     */
    private STNode parseAnnotAccessExpression(STNode lhsExpr, boolean isInConditionalExpr) {
        STNode annotAccessToken = parseAnnotChainingToken();
        STNode annotTagReference = parseFieldAccessIdentifier(isInConditionalExpr);
        return STNodeFactory.createAnnotAccessExpressionNode(lhsExpr, annotAccessToken, annotTagReference);
    }

    /**
     * Parse annot-chaining-token.
     *
     * @return Parsed node
     */
    private STNode parseAnnotChainingToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ANNOT_CHAINING_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ANNOT_CHAINING_TOKEN);
            return parseAnnotChainingToken();
        }
    }

    /**
     * Parse field access identifier.
     * <p>
     * <code>field-access-identifier := qualified-identifier | identifier</code>
     *
     * @return Parsed node
     */
    private STNode parseFieldAccessIdentifier(boolean isInConditionalExpr) {
        STToken nextToken = peek();
        if (!isPredeclaredIdentifier(nextToken.kind)) {
            
            STNode identifier = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                    DiagnosticErrorCode.ERROR_MISSING_IDENTIFIER);
            return parseQualifiedIdentifier(identifier, isInConditionalExpr);
        }

        return parseQualifiedIdentifier(ParserRuleContext.FIELD_ACCESS_IDENTIFIER, isInConditionalExpr);
    }

    /**
     * Parse query action.
     * <p>
     * <code>query-action := query-pipeline do-clause
     * <br/>
     * do-clause := do block-stmt
     * </code>
     *
     * @param queryConstructType Query construct type. This is only for validation
     * @param queryPipeline      Query pipeline
     * @param selectClause       Select clause if any This is only for validation.
     * @return Query action node
     */
    private STNode parseQueryAction(STNode queryConstructType, STNode queryPipeline, STNode selectClause) {
        if (queryConstructType != null) {
            queryPipeline = SyntaxErrors.cloneWithLeadingInvalidNodeMinutiae(queryPipeline, queryConstructType,
                    DiagnosticErrorCode.ERROR_QUERY_CONSTRUCT_TYPE_IN_QUERY_ACTION);
        }
        if (selectClause != null) {
            queryPipeline = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(queryPipeline, selectClause,
                    DiagnosticErrorCode.ERROR_SELECT_CLAUSE_IN_QUERY_ACTION);
        }

        startContext(ParserRuleContext.DO_CLAUSE);
        STNode doKeyword = parseDoKeyword();
        STNode blockStmt = parseBlockNode();
        endContext();

        return STNodeFactory.createQueryActionNode(queryPipeline, doKeyword, blockStmt);
    }

    /**
     * Parse 'do' keyword.
     *
     * @return do keyword node
     */
    private STNode parseDoKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.DO_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.DO_KEYWORD);
            return parseDoKeyword();
        }
    }

    /**
     * Parse optional field access or xml optional attribute access expression.
     * <p>
     * <code>
     * optional-field-access-expr := expression ?. field-name
     * <br/>
     * xml-optional-attribute-access-expr := expression ?. xml-attribute-name
     * <br/>
     * xml-attribute-name := xml-qualified-name | qualified-identifier | identifier
     * <br/>
     * xml-qualified-name := xml-namespace-prefix : identifier
     * <br/>
     * xml-namespace-prefix := identifier
     * </code>
     *
     * @param lhsExpr Preceding expression of the optional access
     * @return Parsed node
     */
    private STNode parseOptionalFieldAccessExpression(STNode lhsExpr, boolean isInConditionalExpr) {
        STNode optionalFieldAccessToken = parseOptionalChainingToken();
        STNode fieldName = parseFieldAccessIdentifier(isInConditionalExpr);
        return STNodeFactory.createOptionalFieldAccessExpressionNode(lhsExpr, optionalFieldAccessToken, fieldName);
    }

    /**
     * Parse optional chaining token.
     *
     * @return parsed node
     */
    private STNode parseOptionalChainingToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.OPTIONAL_CHAINING_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.OPTIONAL_CHAINING_TOKEN);
            return parseOptionalChainingToken();
        }
    }

    /**
     * Parse conditional expression.
     * <p>
     * <code>conditional-expr := expression ? expression : expression</code>
     *
     * @param lhsExpr             Preceding expression of the question mark
     * @param isInConditionalExpr whether calling from a conditional-expr
     * @return Parsed node
     */
    private STNode parseConditionalExpression(STNode lhsExpr, boolean isInConditionalExpr) {
        startContext(ParserRuleContext.CONDITIONAL_EXPRESSION);
        STNode questionMark = parseQuestionMark();
        
        
        STNode middleExpr = parseExpression(OperatorPrecedence.ANON_FUNC_OR_LET, true, false, true);

        
        
        if (peek().kind != SyntaxKind.COLON_TOKEN) {
            if (middleExpr.kind == SyntaxKind.CONDITIONAL_EXPRESSION) {
                STConditionalExpressionNode innerConditionalExpr = (STConditionalExpressionNode) middleExpr;
                STNode innerMiddleExpr = innerConditionalExpr.middleExpression;

                
                
                
                STNode rightMostQNameRef = ConditionalExprResolver.getQualifiedNameRefNode(innerMiddleExpr, false);
                if (rightMostQNameRef != null) {
                    middleExpr = generateConditionalExprForRightMost(innerConditionalExpr.lhsExpression,
                            innerConditionalExpr.questionMarkToken, innerMiddleExpr, rightMostQNameRef);
                    endContext();
                    return STNodeFactory.createConditionalExpressionNode(lhsExpr, questionMark, middleExpr,
                            innerConditionalExpr.colonToken, innerConditionalExpr.endExpression);
                }

                STNode leftMostQNameRef = ConditionalExprResolver.getQualifiedNameRefNode(innerMiddleExpr, true);
                if (leftMostQNameRef != null) {
                    middleExpr = generateConditionalExprForLeftMost(innerConditionalExpr.lhsExpression,
                            innerConditionalExpr.questionMarkToken, innerMiddleExpr, leftMostQNameRef);
                    endContext();
                    return STNodeFactory.createConditionalExpressionNode(lhsExpr, questionMark, middleExpr,
                            innerConditionalExpr.colonToken, innerConditionalExpr.endExpression);
                }
            }

            
            
            
            STNode rightMostQNameRef = ConditionalExprResolver.getQualifiedNameRefNode(middleExpr, false);
            if (rightMostQNameRef != null) {
                endContext();
                return generateConditionalExprForRightMost(lhsExpr, questionMark, middleExpr, rightMostQNameRef);
            }

            STNode leftMostQNameRef = ConditionalExprResolver.getQualifiedNameRefNode(middleExpr, true);
            if (leftMostQNameRef != null) {
                endContext();
                return generateConditionalExprForLeftMost(lhsExpr, questionMark, middleExpr, leftMostQNameRef);
            }
        }

        return parseConditionalExprRhs(lhsExpr, questionMark, middleExpr, isInConditionalExpr);
    }

    private STNode generateConditionalExprForRightMost(STNode lhsExpr, STNode questionMark, STNode middleExpr,
                                                       STNode rightMostQualifiedNameRef) {
        STQualifiedNameReferenceNode qualifiedNameRef =
                (STQualifiedNameReferenceNode) rightMostQualifiedNameRef;
        STNode endExpr = STNodeFactory.createSimpleNameReferenceNode(qualifiedNameRef.identifier);

        STNode simpleNameRef =
                ConditionalExprResolver.getSimpleNameRefNode(qualifiedNameRef.modulePrefix);
        middleExpr = middleExpr.replace(rightMostQualifiedNameRef, simpleNameRef);
        return STNodeFactory.createConditionalExpressionNode(lhsExpr, questionMark, middleExpr, qualifiedNameRef.colon,
                endExpr);
    }

    private STNode generateConditionalExprForLeftMost(STNode lhsExpr, STNode questionMark, STNode middleExpr,
                                                      STNode leftMostQualifiedNameRef) {
        STQualifiedNameReferenceNode qualifiedNameRef = (STQualifiedNameReferenceNode) leftMostQualifiedNameRef;
        STNode simpleNameRef = STNodeFactory.createSimpleNameReferenceNode(qualifiedNameRef.identifier);
        STNode endExpr = middleExpr.replace(leftMostQualifiedNameRef, simpleNameRef);
        middleExpr = ConditionalExprResolver.getSimpleNameRefNode(qualifiedNameRef.modulePrefix);
        return STNodeFactory.createConditionalExpressionNode(lhsExpr, questionMark, middleExpr, qualifiedNameRef.colon,
                endExpr);
    }

    private STNode parseConditionalExprRhs(STNode lhsExpr, STNode questionMark, STNode middleExpr,
                                           boolean isInConditionalExpr) {
        STNode colon = parseColon();
        endContext();
        
        
        STNode endExpr = parseExpression(OperatorPrecedence.ANON_FUNC_OR_LET, true, false,
                isInConditionalExpr);
        return STNodeFactory.createConditionalExpressionNode(lhsExpr, questionMark, middleExpr, colon, endExpr);
    }

    /**
     * Parse enum declaration.
     * <p>
     * module-enum-decl :=
     * metadata
     * [public] enum identifier { enum-member (, enum-member)* } [;]
     * enum-member := metadata identifier [= const-expr]
     * </p>
     *
     * @param metadata
     * @param qualifier
     * @return Parsed enum node.
     */
    private STNode parseEnumDeclaration(STNode metadata, STNode qualifier) {
        startContext(ParserRuleContext.MODULE_ENUM_DECLARATION);
        STNode enumKeywordToken = parseEnumKeyword();
        STNode identifier = parseIdentifier(ParserRuleContext.MODULE_ENUM_NAME);
        STNode openBraceToken = parseOpenBrace();
        STNode enumMemberList = parseEnumMemberList();
        STNode closeBraceToken = parseCloseBrace();
        STNode semicolon = parseOptionalSemicolon();

        endContext();
        openBraceToken = cloneWithDiagnosticIfListEmpty(enumMemberList, openBraceToken,
                DiagnosticErrorCode.ERROR_MISSING_ENUM_MEMBER);
        return STNodeFactory.createEnumDeclarationNode(metadata, qualifier, enumKeywordToken, identifier,
                openBraceToken, enumMemberList, closeBraceToken, semicolon);
    }

    /**
     * Parse 'enum' keyword.
     *
     * @return enum keyword node
     */
    private STNode parseEnumKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ENUM_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ENUM_KEYWORD);
            return parseEnumKeyword();
        }
    }

    /**
     * Parse enum member list.
     * <p>
     * enum-member := metadata identifier [= const-expr]
     * </p>
     *
     * @return enum member list node.
     */
    private STNode parseEnumMemberList() {
        startContext(ParserRuleContext.ENUM_MEMBER_LIST);

        
        if (peek().kind == SyntaxKind.CLOSE_BRACE_TOKEN) {
            return STNodeFactory.createEmptyNodeList();
        }

        
        List<STNode> enumMemberList = new ArrayList<>();
        STNode enumMember = parseEnumMember();

        
        STNode enumMemberRhs;
        while (peek().kind != SyntaxKind.CLOSE_BRACE_TOKEN) {
            enumMemberRhs = parseEnumMemberEnd();
            if (enumMemberRhs == null) {
                break;
            }
            enumMemberList.add(enumMember);
            enumMemberList.add(enumMemberRhs);
            enumMember = parseEnumMember();
        }

        enumMemberList.add(enumMember);

        endContext();
        return STNodeFactory.createNodeList(enumMemberList);
    }

    /**
     * Parse enum member.
     * <p>
     * enum-member := metadata identifier [= const-expr]
     * </p>
     *
     * @return Parsed enum member node.
     */
    private STNode parseEnumMember() {
        STNode metadata;
        switch (peek().kind) {
            case DOCUMENTATION_STRING:
            case AT_TOKEN:
                metadata = parseMetaData();
                break;
            default:
                metadata = STNodeFactory.createEmptyNode();
        }

        STNode identifierNode = parseIdentifier(ParserRuleContext.ENUM_MEMBER_NAME);
        return parseEnumMemberRhs(metadata, identifierNode);
    }

    private STNode parseEnumMemberRhs(STNode metadata, STNode identifierNode) {
        STNode equalToken, constExprNode;
        switch (peek().kind) {
            case EQUAL_TOKEN:
                equalToken = parseAssignOp();
                constExprNode = parseExpression();
                break;
            case COMMA_TOKEN:
            case CLOSE_BRACE_TOKEN:
                equalToken = STNodeFactory.createEmptyNode();
                constExprNode = STNodeFactory.createEmptyNode();
                break;
            default:
                recover(peek(), ParserRuleContext.ENUM_MEMBER_RHS);
                return parseEnumMemberRhs(metadata, identifierNode);
        }

        return STNodeFactory.createEnumMemberNode(metadata, identifierNode, equalToken, constExprNode);
    }

    private STNode parseEnumMemberEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACE_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.ENUM_MEMBER_END);
                return parseEnumMemberEnd();
        }
    }

    private STNode parseTransactionStmtOrVarDecl(STNode annots, List<STNode> qualifiers, STToken transactionKeyword) {
        switch (peek().kind) {
            case OPEN_BRACE_TOKEN:
                reportInvalidStatementAnnots(annots, qualifiers);
                reportInvalidQualifierList(qualifiers);
                return parseTransactionStatement(transactionKeyword);
            case COLON_TOKEN:
                if (getNextNextToken().kind == SyntaxKind.IDENTIFIER_TOKEN) {
                    STNode typeDesc = parseQualifiedIdentifierWithPredeclPrefix(transactionKeyword, false);
                    return parseVarDeclTypeDescRhs(typeDesc, annots, qualifiers, true, false);
                }
                
            default:
                Solution solution = recover(peek(), ParserRuleContext.TRANSACTION_STMT_RHS_OR_TYPE_REF);

                if (solution.action == Action.KEEP ||
                        (solution.action == Action.INSERT && solution.tokenKind == SyntaxKind.COLON_TOKEN)) {
                    
                    STNode typeDesc = parseQualifiedIdentifierWithPredeclPrefix(transactionKeyword, false);
                    return parseVarDeclTypeDescRhs(typeDesc, annots, qualifiers, true, false);
                }

                return parseTransactionStmtOrVarDecl(annots, qualifiers, transactionKeyword);
        }
    }

    /**
     * Parse transaction statement.
     * <p>
     * <code>transaction-stmt := `transaction` block-stmt [on-fail-clause]</code>
     *
     * @return Transaction statement node
     */
    private STNode parseTransactionStatement(STNode transactionKeyword) {
        startContext(ParserRuleContext.TRANSACTION_STMT);
        STNode blockStmt = parseBlockNode();
        endContext();
        STNode onFailClause = parseOptionalOnFailClause();
        return STNodeFactory.createTransactionStatementNode(transactionKeyword, blockStmt, onFailClause);
    }

    /**
     * Parse commit action.
     * <p>
     * <code>commit-action := "commit"</code>
     *
     * @return Commit action node
     */
    private STNode parseCommitAction() {
        STNode commitKeyword = parseCommitKeyword();
        return STNodeFactory.createCommitActionNode(commitKeyword);
    }

    /**
     * Parse commit keyword.
     *
     * @return parsed node
     */
    private STNode parseCommitKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.COMMIT_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.COMMIT_KEYWORD);
            return parseCommitKeyword();
        }
    }

    /**
     * Parse retry statement.
     * <p>
     * <code>
     * retry-stmt := `retry` retry-spec block-stmt [on-fail-clause]
     * <br/>
     * retry-spec :=  [type-parameter] [ `(` arg-list `)` ]
     * </code>
     *
     * @return Retry statement node
     */
    private STNode parseRetryStatement() {
        startContext(ParserRuleContext.RETRY_STMT);
        STNode retryKeyword = parseRetryKeyword();
        
        STNode retryStmt = parseRetryKeywordRhs(retryKeyword);
        return retryStmt;
    }

    private STNode parseRetryKeywordRhs(STNode retryKeyword) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case LT_TOKEN:
                STNode typeParam = parseTypeParameter();
                return parseRetryTypeParamRhs(retryKeyword, typeParam);
            case OPEN_PAREN_TOKEN:
            case OPEN_BRACE_TOKEN:
            case TRANSACTION_KEYWORD:
                typeParam = STNodeFactory.createEmptyNode();
                return parseRetryTypeParamRhs(retryKeyword, typeParam);
            default:
                recover(peek(), ParserRuleContext.RETRY_KEYWORD_RHS);
                return parseRetryKeywordRhs(retryKeyword);
        }
    }

    private STNode parseRetryTypeParamRhs(STNode retryKeyword, STNode typeParam) {
        STNode args;
        switch (peek().kind) {
            case OPEN_PAREN_TOKEN:
                args = parseParenthesizedArgList();
                break;
            case OPEN_BRACE_TOKEN:
            case TRANSACTION_KEYWORD:
                args = STNodeFactory.createEmptyNode();
                break;
            default:
                recover(peek(), ParserRuleContext.RETRY_TYPE_PARAM_RHS);
                return parseRetryTypeParamRhs(retryKeyword, typeParam);
        }

        STNode blockStmt = parseRetryBody();
        endContext(); 
        STNode onFailClause = parseOptionalOnFailClause();
        return STNodeFactory.createRetryStatementNode(retryKeyword, typeParam, args, blockStmt, onFailClause);
    }

    private STNode parseRetryBody() {
        switch (peek().kind) {
            case OPEN_BRACE_TOKEN:
                return parseBlockNode();
            case TRANSACTION_KEYWORD:
                return parseTransactionStatement(consume());
            default:
                recover(peek(), ParserRuleContext.RETRY_BODY);
                return parseRetryBody();
        }
    }

    /**
     * Parse optional on fail clause.
     *
     * @return Parsed node
     */
    private STNode parseOptionalOnFailClause() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.ON_KEYWORD) {
            return parseOnFailClause();
        }

        if (isEndOfRegularCompoundStmt(nextToken.kind)) {
            return STNodeFactory.createEmptyNode();
        }

        recover(nextToken, ParserRuleContext.REGULAR_COMPOUND_STMT_RHS);
        return parseOptionalOnFailClause();
    }

    private boolean isEndOfRegularCompoundStmt(SyntaxKind nodeKind) {
        switch (nodeKind) {
            case CLOSE_BRACE_TOKEN:
            case SEMICOLON_TOKEN:
            case AT_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return isStatementStartingToken(nodeKind);
        }
    }

    private boolean isStatementStartingToken(SyntaxKind nodeKind) {
        switch (nodeKind) {
            case FINAL_KEYWORD:

                
            case IF_KEYWORD:
            case WHILE_KEYWORD:
            case DO_KEYWORD:
            case PANIC_KEYWORD:
            case CONTINUE_KEYWORD:
            case BREAK_KEYWORD:
            case RETURN_KEYWORD:
            case LOCK_KEYWORD:
            case OPEN_BRACE_TOKEN:
            case FORK_KEYWORD:
            case FOREACH_KEYWORD:
            case XMLNS_KEYWORD:
            case TRANSACTION_KEYWORD:
            case RETRY_KEYWORD:
            case ROLLBACK_KEYWORD:
            case MATCH_KEYWORD:
            case FAIL_KEYWORD:

                
            case CHECK_KEYWORD:
            case CHECKPANIC_KEYWORD:
            case TRAP_KEYWORD:
            case START_KEYWORD:
            case FLUSH_KEYWORD:
            case LEFT_ARROW_TOKEN:
            case WAIT_KEYWORD:
            case COMMIT_KEYWORD:

                
                
                
            case WORKER_KEYWORD:
            case TYPE_KEYWORD: 
            case CONST_KEYWORD: 
                return true;
            default:
                
                if (isTypeStartingToken(nodeKind)) {
                    return true;
                }

                
                if (isValidExpressionStart(nodeKind, 1)) {
                    return true;
                }

                return false;
        }
    }

    /**
     * Parse on fail clause.
     * <p>
     * <code>
     * on-fail-clause := on fail [typed-binding-pattern] statement-block
     * </code>
     *
     * @return On fail clause node
     */
    private STNode parseOnFailClause() {
        startContext(ParserRuleContext.ON_FAIL_CLAUSE);
        STNode onKeyword = parseOnKeyword();
        STNode failKeyword = parseFailKeyword();
        STToken token = peek();
        STNode typeDescriptor = STNodeFactory.createEmptyNode();
        STNode identifier = STNodeFactory.createEmptyNode();
        if (token.kind != SyntaxKind.OPEN_BRACE_TOKEN) {
            typeDescriptor = parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true, false,
                    TypePrecedence.DEFAULT);
            identifier = parseIdentifier(ParserRuleContext.VARIABLE_NAME);
        }
        STNode blockStatement = parseBlockNode();
        endContext();
        return STNodeFactory.createOnFailClauseNode(onKeyword, failKeyword, typeDescriptor, identifier,
                blockStatement);
    }

    /**
     * Parse retry keyword.
     *
     * @return parsed node
     */
    private STNode parseRetryKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.RETRY_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.RETRY_KEYWORD);
            return parseRetryKeyword();
        }
    }

    /**
     * Parse transaction statement.
     * <p>
     * <code>rollback-stmt := "rollback" [expression] ";"</code>
     *
     * @return Rollback statement node
     */
    private STNode parseRollbackStatement() {
        startContext(ParserRuleContext.ROLLBACK_STMT);
        STNode rollbackKeyword = parseRollbackKeyword();
        STNode expression;
        if (peek().kind == SyntaxKind.SEMICOLON_TOKEN) {
            expression = STNodeFactory.createEmptyNode();
        } else {
            expression = parseExpression();
        }

        STNode semicolon = parseSemicolon();
        endContext();
        return STNodeFactory.createRollbackStatementNode(rollbackKeyword, expression, semicolon);
    }

    /**
     * Parse rollback keyword.
     *
     * @return Rollback keyword node
     */
    private STNode parseRollbackKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.ROLLBACK_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.ROLLBACK_KEYWORD);
            return parseRollbackKeyword();
        }
    }

    /**
     * Parse transactional expression.
     * <p>
     * <code>transactional-expr := "transactional"</code>
     *
     * @return Transactional expression node
     */
    private STNode parseTransactionalExpression() {
        STNode transactionalKeyword = parseTransactionalKeyword();
        return STNodeFactory.createTransactionalExpressionNode(transactionalKeyword);
    }

    /**
     * Parse transactional keyword.
     *
     * @return Transactional keyword node
     */
    private STNode parseTransactionalKeyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.TRANSACTIONAL_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.TRANSACTIONAL_KEYWORD);
            return parseTransactionalKeyword();
        }
    }

    /**
     * Parse base16 literal.
     * <p>
     * <code>
     * byte-array-literal := Base16Literal | Base64Literal
     * <br/>
     * Base16Literal := base16 WS ` HexGroup* WS `
     * <br/>
     * Base64Literal := base64 WS ` Base64Group* [PaddedBase64Group] WS `
     * </code>
     *
     * @return parsed node
     */
    private STNode parseByteArrayLiteral() {
        STNode type;
        if (peek().kind == SyntaxKind.BASE16_KEYWORD) {
            type = parseBase16Keyword();
        } else {
            type = parseBase64Keyword();
        }

        STNode startingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_START);
        if (startingBackTick.isMissing()) {
            
            startingBackTick = SyntaxErrors.createMissingToken(SyntaxKind.BACKTICK_TOKEN);
            STNode endingBackTick = SyntaxErrors.createMissingToken(SyntaxKind.BACKTICK_TOKEN);
            STNode content = STNodeFactory.createEmptyNode();
            STNode byteArrayLiteral =
                    STNodeFactory.createByteArrayLiteralNode(type, startingBackTick, content, endingBackTick);
            byteArrayLiteral =
                    SyntaxErrors.addDiagnostic(byteArrayLiteral, DiagnosticErrorCode.ERROR_MISSING_BYTE_ARRAY_CONTENT);
            return byteArrayLiteral;
        }

        STNode content = parseByteArrayContent();
        return parseByteArrayLiteral(type, startingBackTick, content);
    }

    /**
     * Parse byte array literal.
     *
     * @param typeKeyword      keyword token, possible values are `base16` and `base64`
     * @param startingBackTick starting backtick token
     * @param byteArrayContent byte array literal content to be validated
     * @return parsed byte array literal node
     */
    private STNode parseByteArrayLiteral(STNode typeKeyword, STNode startingBackTick, STNode byteArrayContent) {
        STNode content = STNodeFactory.createEmptyNode();
        STNode newStartingBackTick = startingBackTick;
        STNodeList items = (STNodeList) byteArrayContent;
        if (items.size() == 1) {
            STNode item = items.get(0);
            if (typeKeyword.kind == SyntaxKind.BASE16_KEYWORD && !isValidBase16LiteralContent(item.toString())) {
                newStartingBackTick = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(startingBackTick, item,
                        DiagnosticErrorCode.ERROR_INVALID_BASE16_CONTENT_IN_BYTE_ARRAY_LITERAL);
            } else if (typeKeyword.kind == SyntaxKind.BASE64_KEYWORD && !isValidBase64LiteralContent(item.toString())) {
                newStartingBackTick = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(startingBackTick, item,
                        DiagnosticErrorCode.ERROR_INVALID_BASE64_CONTENT_IN_BYTE_ARRAY_LITERAL);
            } else if (item.kind != SyntaxKind.TEMPLATE_STRING) {
                newStartingBackTick = SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(startingBackTick, item,
                        DiagnosticErrorCode.ERROR_INVALID_CONTENT_IN_BYTE_ARRAY_LITERAL);
            } else {
                content = item;
            }
        } else if (items.size() > 1) {
            
            STNode clonedStartingBackTick = startingBackTick;
            for (int index = 0; index < items.size(); index++) {
                STNode item = items.get(index);
                clonedStartingBackTick =
                        SyntaxErrors.cloneWithTrailingInvalidNodeMinutiae(clonedStartingBackTick, item);
            }
            newStartingBackTick = SyntaxErrors.addDiagnostic(clonedStartingBackTick,
                    DiagnosticErrorCode.ERROR_INVALID_CONTENT_IN_BYTE_ARRAY_LITERAL);
        }

        STNode endingBackTick = parseBacktickToken(ParserRuleContext.TEMPLATE_END);
        return STNodeFactory.createByteArrayLiteralNode(typeKeyword, newStartingBackTick, content, endingBackTick);
    }

    /**
     * Parse <code>base16</code> keyword.
     *
     * @return base16 keyword node
     */
    private STNode parseBase16Keyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.BASE16_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.BASE16_KEYWORD);
            return parseBase16Keyword();
        }
    }

    /**
     * Parse <code>base64</code> keyword.
     *
     * @return base64 keyword node
     */
    private STNode parseBase64Keyword() {
        STToken token = peek();
        if (token.kind == SyntaxKind.BASE64_KEYWORD) {
            return consume();
        } else {
            recover(token, ParserRuleContext.BASE64_KEYWORD);
            return parseBase64Keyword();
        }
    }

    /**
     * Validate and parse byte array literal content.
     * An error is reported, if the content is invalid.
     *
     * @return parsed node
     */
    private STNode parseByteArrayContent() {
        STToken nextToken = peek();

        List<STNode> items = new ArrayList<>();
        while (!isEndOfBacktickContent(nextToken.kind)) {
            STNode content = parseTemplateItem();
            items.add(content);
            nextToken = peek();
        }

        return STNodeFactory.createNodeList(items);
    }

    /**
     * Validate base16 literal content.
     * <p>
     * <code>
     * Base16Literal := base16 WS ` HexGroup* WS `
     * <br/>
     * HexGroup := WS HexDigit WS HexDigit
     * <br/>
     * WS := WhiteSpaceChar*
     * <br/>
     * WhiteSpaceChar := 0x9 | 0xA | 0xD | 0x20
     * </code>
     *
     * @param content the string surrounded by the backticks
     * @return <code>true</code>, if the string content is valid. <code>false</code> otherwise.
     */
    static boolean isValidBase16LiteralContent(String content) {
        char[] charArray = content.toCharArray();
        int hexDigitCount = 0;

        for (char c : charArray) {
            switch (c) {
                case LexerTerminals.TAB:
                case LexerTerminals.NEWLINE:
                case LexerTerminals.CARRIAGE_RETURN:
                case LexerTerminals.SPACE:
                    break;
                default:
                    if (isHexDigit(c)) {
                        hexDigitCount++;
                    } else {
                        return false;
                    }
                    break;
            }
        }

        return hexDigitCount % 2 == 0;
    }

    /**
     * Validate base64 literal content.
     * <p>
     * <code>
     * Base64Literal := base64 WS ` Base64Group* [PaddedBase64Group] WS `
     * <br/>
     * Base64Group := WS Base64Char WS Base64Char WS Base64Char WS Base64Char
     * <br/>
     * PaddedBase64Group :=
     * WS Base64Char WS Base64Char WS Base64Char WS PaddingChar
     * | WS Base64Char WS Base64Char WS PaddingChar WS PaddingChar
     * <br/>
     * Base64Char := A .. Z | a .. z | 0 .. 9 | + | /
     * <br/>
     * PaddingChar := =
     * <br/>
     * WS := WhiteSpaceChar*
     * <br/>
     * WhiteSpaceChar := 0x9 | 0xA | 0xD | 0x20
     * </code>
     *
     * @param content the string surrounded by the backticks
     * @return <code>true</code>, if the string content is valid. <code>false</code> otherwise.
     */
    static boolean isValidBase64LiteralContent(String content) {
        char[] charArray = content.toCharArray();
        int base64CharCount = 0;
        int paddingCharCount = 0;

        for (char c : charArray) {
            switch (c) {
                case LexerTerminals.TAB:
                case LexerTerminals.NEWLINE:
                case LexerTerminals.CARRIAGE_RETURN:
                case LexerTerminals.SPACE:
                    break;
                case LexerTerminals.EQUAL:
                    paddingCharCount++;
                    break;
                default:
                    if (isBase64Char(c)) {
                        if (paddingCharCount == 0) {
                            base64CharCount++;
                        } else {
                            return false;
                        }
                    } else {
                        return false;
                    }
                    break;
            }
        }

        if (paddingCharCount > 2) {
            return false;
        } else if (paddingCharCount == 0) {
            return base64CharCount % 4 == 0;
        } else {
            return base64CharCount % 4 == 4 - paddingCharCount;
        }
    }

    /**
     * <p>
     * Check whether a given char is a base64 char.
     * </p>
     * <code>Base64Char := A .. Z | a .. z | 0 .. 9 | + | /</code>
     *
     * @param c character to check
     * @return <code>true</code>, if the character represents a base64 char. <code>false</code> otherwise.
     */
    static boolean isBase64Char(int c) {
        if ('a' <= c && c <= 'z') {
            return true;
        }
        if ('A' <= c && c <= 'Z') {
            return true;
        }
        if (c == '+' || c == '/') {
            return true;
        }
        return isDigit(c);
    }

    static boolean isHexDigit(int c) {
        if ('a' <= c && c <= 'f') {
            return true;
        }
        if ('A' <= c && c <= 'F') {
            return true;
        }
        return isDigit(c);
    }

    static boolean isDigit(int c) {
        return ('0' <= c && c <= '9');
    }

    /**
     * Parse xml filter expression.
     * <p>
     * <code>xml-filter-expr := expression .< xml-name-pattern ></code>
     *
     * @param lhsExpr Preceding expression of .< token
     * @return Parsed node
     */
    private STNode parseXMLFilterExpression(STNode lhsExpr) {
        STNode xmlNamePatternChain = parseXMLFilterExpressionRhs();
        return STNodeFactory.createXMLFilterExpressionNode(lhsExpr, xmlNamePatternChain);
    }

    /**
     * Parse xml filter expression rhs.
     * <p>
     * <code>filer-expression-rhs := .< xml-name-pattern ></code>
     *
     * @return Parsed node
     */
    private STNode parseXMLFilterExpressionRhs() {
        STNode dotLTToken = parseDotLTToken();
        return parseXMLNamePatternChain(dotLTToken);
    }

    /**
     * Parse xml name pattern chain.
     * <p>
     * <code>
     * xml-name-pattern-chain := filer-expression-rhs | xml-element-children-step | xml-element-descendants-step
     * <br/>
     * filer-expression-rhs := .< xml-name-pattern >
     * <br/>
     * xml-element-children-step := /< xml-name-pattern >
     * <br/>
     * xml-element-descendants-step := /**\/<xml-name-pattern >
     * </code>
     *
     * @param startToken Preceding token of xml name pattern
     * @return Parsed node
     */
    private STNode parseXMLNamePatternChain(STNode startToken) {
        startContext(ParserRuleContext.XML_NAME_PATTERN);
        STNode xmlNamePattern = parseXMLNamePattern();
        STNode gtToken = parseGTToken();
        endContext();

        startToken = cloneWithDiagnosticIfListEmpty(xmlNamePattern, startToken,
                DiagnosticErrorCode.ERROR_MISSING_XML_ATOMIC_NAME_PATTERN);
        return STNodeFactory.createXMLNamePatternChainingNode(startToken, xmlNamePattern, gtToken);
    }

    /**
     * Parse <code> .< </code> token.
     *
     * @return Parsed node
     */
    private STNode parseDotLTToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.DOT_LT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.DOT_LT_TOKEN);
            return parseDotLTToken();
        }
    }

    /**
     * Parse xml name pattern.
     * <p>
     * <code>xml-name-pattern := xml-atomic-name-pattern [| xml-atomic-name-pattern]*</code>
     *
     * @return Parsed node
     */
    private STNode parseXMLNamePattern() {
        List<STNode> xmlAtomicNamePatternList = new ArrayList<>();
        STToken nextToken = peek();

        
        if (isEndOfXMLNamePattern(nextToken.kind)) {
            return STNodeFactory.createNodeList(xmlAtomicNamePatternList);
        }

        
        STNode xmlAtomicNamePattern = parseXMLAtomicNamePattern();
        xmlAtomicNamePatternList.add(xmlAtomicNamePattern);

        
        STNode separator;
        while (!isEndOfXMLNamePattern(peek().kind)) {
            separator = parseXMLNamePatternSeparator();
            if (separator == null) {
                break;
            }
            xmlAtomicNamePatternList.add(separator);

            xmlAtomicNamePattern = parseXMLAtomicNamePattern();
            xmlAtomicNamePatternList.add(xmlAtomicNamePattern);
        }

        return STNodeFactory.createNodeList(xmlAtomicNamePatternList);
    }

    private boolean isEndOfXMLNamePattern(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case GT_TOKEN:
            case EOF_TOKEN:
                return true;
            case IDENTIFIER_TOKEN:
            case ASTERISK_TOKEN:
            case COLON_TOKEN:
            default:
                return false;
        }
    }

    private STNode parseXMLNamePatternSeparator() {
        STToken token = peek();
        switch (token.kind) {
            case PIPE_TOKEN:
                return consume();
            case GT_TOKEN:
            case EOF_TOKEN:
                return null;
            default:
                recover(token, ParserRuleContext.XML_NAME_PATTERN_RHS);
                return parseXMLNamePatternSeparator();
        }
    }

    /**
     * Parse xml atomic name pattern.
     * <p>
     * <code>
     * xml-atomic-name-pattern :=
     * *
     * | identifier
     * | xml-namespace-prefix : identifier
     * | xml-namespace-prefix : *
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseXMLAtomicNamePattern() {
        startContext(ParserRuleContext.XML_ATOMIC_NAME_PATTERN);
        STNode atomicNamePattern = parseXMLAtomicNamePatternBody();
        endContext();
        return atomicNamePattern;
    }

    private STNode parseXMLAtomicNamePatternBody() {
        STToken token = peek();
        STNode identifier;
        switch (token.kind) {
            case ASTERISK_TOKEN:
                return consume();
            case IDENTIFIER_TOKEN:
                identifier = consume();
                break;
            default:
                recover(token, ParserRuleContext.XML_ATOMIC_NAME_PATTERN_START);
                return parseXMLAtomicNamePatternBody();
        }

        return parseXMLAtomicNameIdentifier(identifier);
    }

    private STNode parseXMLAtomicNameIdentifier(STNode identifier) {
        STToken token = peek();
        if (token.kind == SyntaxKind.COLON_TOKEN) {
            STNode colon = consume();
            STToken nextToken = peek();
            if (nextToken.kind == SyntaxKind.IDENTIFIER_TOKEN || nextToken.kind == SyntaxKind.ASTERISK_TOKEN) {
                STToken endToken = consume();
                return STNodeFactory.createXMLAtomicNamePatternNode(identifier, colon, endToken);
            }
        }
        return STNodeFactory.createSimpleNameReferenceNode(identifier);
    }

    /**
     * Parse xml step expression.
     * <p>
     * <code>xml-step-expr := expression xml-step-start</code>
     *
     * @param lhsExpr Preceding expression of /*, /<, or /**\/< token
     * @return Parsed node
     */
    private STNode parseXMLStepExpression(STNode lhsExpr) {
        STNode xmlStepStart = parseXMLStepStart();
        return STNodeFactory.createXMLStepExpressionNode(lhsExpr, xmlStepStart);
    }

    /**
     * Parse xml filter expression rhs.
     * <p>
     * <code>
     * xml-step-start :=
     * xml-all-children-step
     * | xml-element-children-step
     * | xml-element-descendants-step
     * <br/>
     * xml-all-children-step := /*
     * </code>
     *
     * @return Parsed node
     */
    private STNode parseXMLStepStart() {
        STToken token = peek();
        STNode startToken;

        switch (token.kind) {
            case SLASH_ASTERISK_TOKEN:
                return consume();
            case DOUBLE_SLASH_DOUBLE_ASTERISK_LT_TOKEN:
                startToken = parseDoubleSlashDoubleAsteriskLTToken();
                break;
            case SLASH_LT_TOKEN:
            default:
                startToken = parseSlashLTToken();
                break;
        }
        return parseXMLNamePatternChain(startToken);
    }

    /**
     * Parse <code> /< </code> token.
     *
     * @return Parsed node
     */
    private STNode parseSlashLTToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.SLASH_LT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.SLASH_LT_TOKEN);
            return parseSlashLTToken();
        }
    }

    /**
     * Parse <code> /< </code> token.
     *
     * @return Parsed node
     */
    private STNode parseDoubleSlashDoubleAsteriskLTToken() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.DOUBLE_SLASH_DOUBLE_ASTERISK_LT_TOKEN) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.DOUBLE_SLASH_DOUBLE_ASTERISK_LT_TOKEN);
            return parseDoubleSlashDoubleAsteriskLTToken();
        }
    }

    /**
     * Parse match statement.
     * <p>
     * <code>match-stmt := match action-or-expr { match-clause+ } [on-fail-clause]</code>
     *
     * @return Match statement
     */
    private STNode parseMatchStatement() {
        startContext(ParserRuleContext.MATCH_STMT);
        STNode matchKeyword = parseMatchKeyword();
        STNode actionOrExpr = parseActionOrExpression();
        startContext(ParserRuleContext.MATCH_BODY);
        STNode openBrace = parseOpenBrace();
        
        List<STNode> matchClausesList = new ArrayList<>();
        while (!isEndOfMatchClauses(peek().kind)) {
            STNode clause = parseMatchClause();
            matchClausesList.add(clause);
        }
        STNode matchClauses =  STNodeFactory.createNodeList(matchClausesList);
        
        if (isNodeListEmpty(matchClauses)) {
            openBrace = SyntaxErrors.addDiagnostic(openBrace,
                    DiagnosticErrorCode.ERROR_MATCH_STATEMENT_SHOULD_HAVE_ONE_OR_MORE_MATCH_CLAUSES);
        }

        STNode closeBrace = parseCloseBrace();
        endContext();
        endContext();
        STNode onFailClause = parseOptionalOnFailClause();
        return STNodeFactory.createMatchStatementNode(matchKeyword, actionOrExpr, openBrace, matchClauses, closeBrace,
                onFailClause);
    }

    /**
     * Parse match keyword.
     *
     * @return Match keyword node
     */
    private STNode parseMatchKeyword() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.MATCH_KEYWORD) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.MATCH_KEYWORD);
            return parseMatchKeyword();
        }
    }

    private boolean isEndOfMatchClauses(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case TYPE_KEYWORD:
                return true;
            default:
                return isEndOfStatements();
        }
    }

    /**
     * Parse a single match match clause.
     * <p>
     * <code>
     * match-clause := match-pattern-list [match-guard] => block-stmt
     * <br/>
     * match-guard := if expression
     * </code>
     *
     * @return A match clause
     */
    private STNode parseMatchClause() {
        STNode matchPatterns = parseMatchPatternList();
        STNode matchGuard = parseMatchGuard();
        STNode rightDoubleArrow = parseDoubleRightArrow();
        STNode blockStmt = parseBlockNode();
        
        if (isNodeListEmpty(matchPatterns)) {
            STToken identifier = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
            STNode constantPattern = STNodeFactory.createSimpleNameReferenceNode(identifier);
            matchPatterns = STNodeFactory.createNodeList(constantPattern);
            
            DiagnosticErrorCode errorCode = DiagnosticErrorCode.ERROR_MISSING_MATCH_PATTERN;
            if (matchGuard != null) {
                matchGuard = SyntaxErrors.addDiagnostic(matchGuard, errorCode);
            } else {
                rightDoubleArrow = SyntaxErrors.addDiagnostic(rightDoubleArrow, errorCode);
            }
        }
        
        return STNodeFactory.createMatchClauseNode(matchPatterns, matchGuard, rightDoubleArrow, blockStmt);
    }

    /**
     * Parse match guard.
     * <p>
     * <code>match-guard := if expression</code>
     *
     * @return Match guard
     */
    private STNode parseMatchGuard() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IF_KEYWORD:
                STNode ifKeyword = parseIfKeyword();
                STNode expr = parseExpression(DEFAULT_OP_PRECEDENCE, true, false, true, false);
                return STNodeFactory.createMatchGuardNode(ifKeyword, expr);
            case RIGHT_DOUBLE_ARROW_TOKEN:
                return STNodeFactory.createEmptyNode();
            default:
                recover(nextToken, ParserRuleContext.OPTIONAL_MATCH_GUARD);
                return parseMatchGuard();
        }
    }

    /**
     * Parse match patterns list.
     * <p>
     * <code>match-pattern-list := match-pattern (| match-pattern)*</code>
     *
     * @return Match patterns list
     */
    private STNode parseMatchPatternList() {
        startContext(ParserRuleContext.MATCH_PATTERN);
        List<STNode> matchClauses = new ArrayList<>();
        while (!isEndOfMatchPattern(peek().kind)) {
            STNode clause = parseMatchPattern();
            if (clause == null) {
                break;
            }
            matchClauses.add(clause);

            STNode seperator = parseMatchPatternListMemberRhs();
            if (seperator == null) {
                break;
            }
            matchClauses.add(seperator);
        }

        endContext();
        return STNodeFactory.createNodeList(matchClauses);
    }

    private boolean isEndOfMatchPattern(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case PIPE_TOKEN:
            case IF_KEYWORD:
            case RIGHT_DOUBLE_ARROW_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse match pattern.
     * <p>
     * <code>
     * match-pattern := var binding-pattern
     * | wildcard-match-pattern
     * | const-pattern
     * | list-match-pattern
     * | mapping-match-pattern
     * | error-match-pattern
     * </code>
     *
     * @return Match pattern
     */
    private STNode parseMatchPattern() {
        STToken nextToken = peek();
        if (isPredeclaredIdentifier(nextToken.kind)) {
            
            STNode typeRefOrConstExpr = parseQualifiedIdentifier(ParserRuleContext.MATCH_PATTERN);
            return parseErrorMatchPatternOrConsPattern(typeRefOrConstExpr);
        }

        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
            case NULL_KEYWORD:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case PLUS_TOKEN:
            case MINUS_TOKEN:
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
            case STRING_LITERAL_TOKEN:
                return parseSimpleConstExpr();
            case VAR_KEYWORD:
                return parseVarTypedBindingPattern();
            case OPEN_BRACKET_TOKEN:
                return parseListMatchPattern();
            case OPEN_BRACE_TOKEN:
                return parseMappingMatchPattern();
            case ERROR_KEYWORD:
                return parseErrorMatchPattern();
            default:
                recover(nextToken, ParserRuleContext.MATCH_PATTERN_START);
                return parseMatchPattern();
        }
    }

    private STNode parseMatchPatternListMemberRhs() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case PIPE_TOKEN:
                return parsePipeToken();
            case IF_KEYWORD:
            case RIGHT_DOUBLE_ARROW_TOKEN:
                
                return null;
            default:
                recover(nextToken, ParserRuleContext.MATCH_PATTERN_LIST_MEMBER_RHS);
                return parseMatchPatternListMemberRhs();
        }
    }

    /**
     * Parse var typed binding pattern.
     * <p>
     * <code>var binding-pattern</code>
     * </p>
     *
     * @return Parsed typed binding pattern node
     */
    private STNode parseVarTypedBindingPattern() {
        STNode varKeyword = parseVarKeyword();
        STNode varTypeDesc = createBuiltinSimpleNameReference(varKeyword);
        STNode bindingPattern = parseBindingPattern();
        return STNodeFactory.createTypedBindingPatternNode(varTypeDesc, bindingPattern);
    }

    /**
     * Parse var keyword.
     *
     * @return Var keyword node
     */
    private STNode parseVarKeyword() {
        STToken nextToken = peek();
        if (nextToken.kind == SyntaxKind.VAR_KEYWORD) {
            return consume();
        } else {
            recover(nextToken, ParserRuleContext.VAR_KEYWORD);
            return parseVarKeyword();
        }
    }

    /**
     * Parse list match pattern.
     * <p>
     * <code>
     * list-match-pattern := [ list-member-match-patterns ]
     * list-member-match-patterns :=
     * match-pattern (, match-pattern)* [, rest-match-pattern]
     * | [ rest-match-pattern ]
     * </code>
     * </p>
     *
     * @return Parsed list match pattern node
     */
    private STNode parseListMatchPattern() {
        startContext(ParserRuleContext.LIST_MATCH_PATTERN);
        STNode openBracketToken = parseOpenBracket();
        List<STNode> matchPatternList = new ArrayList<>();
        STNode listMatchPatternMemberRhs = null;
        boolean isEndOfFields = false;

        while (!isEndOfListMatchPattern()) {
            STNode listMatchPatternMember = parseListMatchPatternMember();
            matchPatternList.add(listMatchPatternMember);
            listMatchPatternMemberRhs = parseListMatchPatternMemberRhs();

            if (listMatchPatternMember.kind == SyntaxKind.REST_MATCH_PATTERN) {
                isEndOfFields = true;
                break;
            }

            if (listMatchPatternMemberRhs != null) {
                matchPatternList.add(listMatchPatternMemberRhs);
            } else {
                break;
            }
        }

        
        
        while (isEndOfFields && listMatchPatternMemberRhs != null) {
            updateLastNodeInListWithInvalidNode(matchPatternList, listMatchPatternMemberRhs, null);

            if (peek().kind == SyntaxKind.CLOSE_BRACKET_TOKEN) {
                break;
            }

            STNode invalidField = parseListMatchPatternMember();
            updateLastNodeInListWithInvalidNode(matchPatternList, invalidField,
                    DiagnosticErrorCode.ERROR_MATCH_PATTERN_AFTER_REST_MATCH_PATTERN);
            listMatchPatternMemberRhs = parseListMatchPatternMemberRhs();
        }

        STNode matchPatternListNode = STNodeFactory.createNodeList(matchPatternList);
        STNode closeBracketToken = parseCloseBracket();
        endContext();

        return STNodeFactory.createListMatchPatternNode(openBracketToken, matchPatternListNode, closeBracketToken);
    }

    public boolean isEndOfListMatchPattern() {
        switch (peek().kind) {
            case CLOSE_BRACKET_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseListMatchPatternMember() {
        STNode nextToken = peek();
        switch (nextToken.kind) {
            case ELLIPSIS_TOKEN:
                return parseRestMatchPattern();
            default:
                
                return parseMatchPattern();
        }
    }

    /**
     * Parse rest match pattern.
     * <p>
     * <code>
     * rest-match-pattern := ... var variable-name
     * </code>
     * </p>
     *
     * @return Parsed rest match pattern node
     */
    private STNode parseRestMatchPattern() {
        startContext(ParserRuleContext.REST_MATCH_PATTERN);
        STNode ellipsisToken = parseEllipsis();
        STNode varKeywordToken = parseVarKeyword();
        STNode variableName = parseVariableName();
        endContext();

        STSimpleNameReferenceNode simpleNameReferenceNode =
                (STSimpleNameReferenceNode) STNodeFactory.createSimpleNameReferenceNode(variableName);
        return STNodeFactory.createRestMatchPatternNode(ellipsisToken, varKeywordToken, simpleNameReferenceNode);
    }

    private STNode parseListMatchPatternMemberRhs() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACKET_TOKEN:
            case EOF_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.LIST_MATCH_PATTERN_MEMBER_RHS);
                return parseListMatchPatternMemberRhs();
        }
    }

    /**
     * Parse mapping match pattern.
     * <p>
     * mapping-match-pattern := { field-match-patterns }
     * <br/>
     * field-match-patterns := field-match-pattern (, field-match-pattern)* [, rest-match-pattern]
     * | [ rest-match-pattern ]
     * <br/>
     * field-match-pattern := field-name : match-pattern
     * <br/>
     * rest-match-pattern := ... var variable-name
     * </p>
     *
     * @return Parsed Node.
     */
    private STNode parseMappingMatchPattern() {
        startContext(ParserRuleContext.MAPPING_MATCH_PATTERN);
        STNode openBraceToken = parseOpenBrace();
        STNode fieldMatchPatterns = parseFieldMatchPatternList();
        STNode closeBraceToken = parseCloseBrace();
        endContext();
        return STNodeFactory.createMappingMatchPatternNode(openBraceToken, fieldMatchPatterns, closeBraceToken);
    }

    private STNode parseFieldMatchPatternList() {
        List<STNode> fieldMatchPatterns = new ArrayList<>();

        STNode fieldMatchPatternMember = parseFieldMatchPatternMember();
        if (fieldMatchPatternMember == null) {
            return STNodeFactory.createEmptyNodeList();
        }

        fieldMatchPatterns.add(fieldMatchPatternMember);
        if (fieldMatchPatternMember.kind == SyntaxKind.REST_MATCH_PATTERN) {
            invalidateExtraFieldMatchPatterns(fieldMatchPatterns);
            return STNodeFactory.createNodeList(fieldMatchPatterns);
        }

        return parseFieldMatchPatternList(fieldMatchPatterns);
    }

    private STNode parseFieldMatchPatternList(List<STNode> fieldMatchPatterns) {
        while (!isEndOfMappingMatchPattern()) {
            STNode fieldMatchPatternRhs = parseFieldMatchPatternRhs();
            if (fieldMatchPatternRhs == null) {
                break;
            }

            fieldMatchPatterns.add(fieldMatchPatternRhs);
            STNode fieldMatchPatternMember = parseFieldMatchPatternMember();
            if (fieldMatchPatternMember == null) {
                fieldMatchPatternMember = createMissingFieldMatchPattern();
            }

            fieldMatchPatterns.add(fieldMatchPatternMember);
            if (fieldMatchPatternMember.kind == SyntaxKind.REST_MATCH_PATTERN) {
                invalidateExtraFieldMatchPatterns(fieldMatchPatterns);
                break;
            }
        }

        return STNodeFactory.createNodeList(fieldMatchPatterns);
    }

    private STNode createMissingFieldMatchPattern() {
        STNode fieldName = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
        STNode colon = SyntaxErrors.createMissingToken(SyntaxKind.COLON_TOKEN);
        STNode identifier = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
        STNode matchPattern = STNodeFactory.createSimpleNameReferenceNode(identifier);
        STNode fieldMatchPatternMember = STNodeFactory.createFieldMatchPatternNode(fieldName, colon, matchPattern);
        fieldMatchPatternMember = SyntaxErrors.addDiagnostic(fieldMatchPatternMember,
                DiagnosticErrorCode.ERROR_MISSING_FIELD_MATCH_PATTERN_MEMBER);
        return fieldMatchPatternMember;
    }

    /**
     * Parse and invalidate all field match pattern members after a rest-match-pattern.
     *
     * @param fieldMatchPatterns field-match-patterns list
     */
    private void invalidateExtraFieldMatchPatterns(List<STNode> fieldMatchPatterns) {
        while (!isEndOfMappingMatchPattern()) {
            STNode fieldMatchPatternRhs = parseFieldMatchPatternRhs();
            if (fieldMatchPatternRhs == null) {
                break;
            }

            STNode fieldMatchPatternMember = parseFieldMatchPatternMember();
            if (fieldMatchPatternMember == null) {
                updateLastNodeInListWithInvalidNode(fieldMatchPatterns, fieldMatchPatternRhs,
                        DiagnosticErrorCode.ERROR_INVALID_TOKEN, ((STToken) fieldMatchPatternRhs).text());
            } else {
                updateLastNodeInListWithInvalidNode(fieldMatchPatterns, fieldMatchPatternRhs, null);
                updateLastNodeInListWithInvalidNode(fieldMatchPatterns, fieldMatchPatternMember,
                        DiagnosticErrorCode.ERROR_MATCH_PATTERN_AFTER_REST_MATCH_PATTERN);
            }
        }
    }

    private STNode parseFieldMatchPatternMember() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
                return parseFieldMatchPattern();
            case ELLIPSIS_TOKEN:
                return parseRestMatchPattern();
            case CLOSE_BRACE_TOKEN:
            case EOF_TOKEN:
                
                return null;
            default:
                recover(nextToken, ParserRuleContext.FIELD_MATCH_PATTERNS_START);
                return parseFieldMatchPatternMember();
        }
    }

    /**
     * Parse filed match pattern.
     * <p>
     * field-match-pattern := field-name : match-pattern
     * </p>
     *
     * @return Parsed field match pattern node
     */
    public STNode parseFieldMatchPattern() {
        STNode fieldNameNode = parseVariableName();
        STNode colonToken = parseColon();
        STNode matchPattern = parseMatchPattern();
        return STNodeFactory.createFieldMatchPatternNode(fieldNameNode, colonToken, matchPattern);
    }

    public boolean isEndOfMappingMatchPattern() {
        switch (peek().kind) {
            case CLOSE_BRACE_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseFieldMatchPatternRhs() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACE_TOKEN:
            case EOF_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.FIELD_MATCH_PATTERN_MEMBER_RHS);
                return parseFieldMatchPatternRhs();
        }
    }

    private STNode parseErrorMatchPatternOrConsPattern(STNode typeRefOrConstExpr) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                STNode errorKeyword = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.ERROR_KEYWORD,
                        ParserRuleContext.ERROR_KEYWORD);
                startContext(ParserRuleContext.ERROR_MATCH_PATTERN); 
                return parseErrorMatchPattern(errorKeyword, typeRefOrConstExpr);
            default:
                if (isMatchPatternEnd(peek().kind)) {
                    return typeRefOrConstExpr;
                }
                recover(peek(), ParserRuleContext.ERROR_MATCH_PATTERN_OR_CONST_PATTERN);
                return parseErrorMatchPatternOrConsPattern(typeRefOrConstExpr);
        }
    }

    private boolean isMatchPatternEnd(SyntaxKind tokenKind) {
        switch (tokenKind) {
            case RIGHT_DOUBLE_ARROW_TOKEN:
            case COMMA_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case CLOSE_BRACKET_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case PIPE_TOKEN:
            case IF_KEYWORD:
            case EOF_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse functional match pattern.
     * <p>
     * error-match-pattern := error [error-type-reference] ( error-arg-list-match-pattern )
     * error-arg-list-match-pattern :=
     * error-message-match-pattern [, error-cause-match-pattern] [, error-field-match-patterns]
     * | [error-field-match-patterns]
     * error-message-match-pattern := simple-match-pattern
     * error-cause-match-pattern := simple-match-pattern | error-match-pattern
     * simple-match-pattern :=
     * wildcard-match-pattern
     * | const-pattern
     * | var variable-name
     * error-field-match-patterns :=
     * named-arg-match-pattern (, named-arg-match-pattern)* [, rest-match-pattern]
     * | rest-match-pattern
     * named-arg-match-pattern := arg-name = match-pattern
     * </p>
     *
     * @return Parsed functional match pattern node.
     */
    private STNode parseErrorMatchPattern() {
        startContext(ParserRuleContext.ERROR_MATCH_PATTERN);
        STNode errorKeyword = consume();
        return parseErrorMatchPattern(errorKeyword);
    }

    private STNode parseErrorMatchPattern(STNode errorKeyword) {
        STToken nextToken = peek();
        STNode typeRef;
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                typeRef = STNodeFactory.createEmptyNode();
                break;
            default:
                if (isPredeclaredIdentifier(nextToken.kind)) {
                    typeRef = parseTypeReference();
                    break;
                }
                recover(peek(), ParserRuleContext.ERROR_MATCH_PATTERN_ERROR_KEYWORD_RHS);
                return parseErrorMatchPattern(errorKeyword);
        }
        return parseErrorMatchPattern(errorKeyword, typeRef);
    }

    private STNode parseErrorMatchPattern(STNode errorKeyword, STNode typeRef) {
        STNode openParenthesisToken = parseOpenParenthesis();
        STNode argListMatchPatternNode = parseErrorArgListMatchPatterns();
        STNode closeParenthesisToken = parseCloseParenthesis();
        endContext();
        return STNodeFactory.createErrorMatchPatternNode(errorKeyword, typeRef, openParenthesisToken,
                argListMatchPatternNode, closeParenthesisToken);
    }

    private STNode parseErrorArgListMatchPatterns() {
        List<STNode> argListMatchPatterns = new ArrayList<>();

        if (isEndOfErrorFieldMatchPatterns()) {
            return STNodeFactory.createNodeList(argListMatchPatterns);
        }
        startContext(ParserRuleContext.ERROR_ARG_LIST_MATCH_PATTERN_FIRST_ARG);
        STNode firstArg = parseErrorArgListMatchPattern(ParserRuleContext.ERROR_ARG_LIST_MATCH_PATTERN_START);
        endContext();

        if (isSimpleMatchPattern(firstArg.kind)) {

            argListMatchPatterns.add(firstArg);
            STNode argEnd = parseErrorArgListMatchPatternEnd(ParserRuleContext.ERROR_MESSAGE_MATCH_PATTERN_END);
            if (argEnd != null) {
                
                STNode secondArg = parseErrorArgListMatchPattern(ParserRuleContext.ERROR_MESSAGE_MATCH_PATTERN_RHS);
                if (isValidSecondArgMatchPattern(secondArg.kind)) {
                    argListMatchPatterns.add(argEnd);
                    argListMatchPatterns.add(secondArg);
                } else {
                    updateLastNodeInListWithInvalidNode(argListMatchPatterns, argEnd, null);
                    updateLastNodeInListWithInvalidNode(argListMatchPatterns, secondArg,
                            DiagnosticErrorCode.ERROR_MATCH_PATTERN_NOT_ALLOWED);
                }
            }
        } else {
            if (firstArg.kind != SyntaxKind.NAMED_ARG_MATCH_PATTERN &&
                    firstArg.kind != SyntaxKind.REST_MATCH_PATTERN) {
                addInvalidNodeToNextToken(firstArg, DiagnosticErrorCode.ERROR_MATCH_PATTERN_NOT_ALLOWED);
            } else {
                argListMatchPatterns.add(firstArg);
            }
        }

        parseErrorFieldMatchPatterns(argListMatchPatterns);
        return STNodeFactory.createNodeList(argListMatchPatterns);
    }

    private boolean isSimpleMatchPattern(SyntaxKind matchPatternKind) {
        switch (matchPatternKind) {
            case IDENTIFIER_TOKEN:
            case SIMPLE_NAME_REFERENCE:
            case QUALIFIED_NAME_REFERENCE:
            case NUMERIC_LITERAL:
            case STRING_LITERAL:
            case NULL_LITERAL:
            case NIL_LITERAL:
            case BOOLEAN_LITERAL:
            case TYPED_BINDING_PATTERN:
            case UNARY_EXPRESSION:
                return true;
            default:
                return false;
        }
    }

    private boolean isValidSecondArgMatchPattern(SyntaxKind syntaxKind) {
        switch (syntaxKind) {
            case ERROR_MATCH_PATTERN:
            case NAMED_ARG_MATCH_PATTERN:
            case REST_MATCH_PATTERN:
                return true;
            default:
                if (isSimpleMatchPattern(syntaxKind)) {
                    return true;
                }
                return false;
        }
    }

    /**
     * Parse error field match patterns.
     * error-field-match-patterns :=
     * named-arg-match-pattern (, named-arg-match-pattern)* [, rest-match-pattern]
     * | rest-match-pattern
     * named-arg-match-pattern := arg-name = match-pattern
     * @param argListMatchPatterns
     */
    private void parseErrorFieldMatchPatterns(List<STNode> argListMatchPatterns) {
        SyntaxKind lastValidArgKind = SyntaxKind.NAMED_ARG_MATCH_PATTERN;
        while (!isEndOfErrorFieldMatchPatterns()) {
            STNode argEnd = parseErrorArgListMatchPatternEnd(ParserRuleContext.ERROR_FIELD_MATCH_PATTERN_RHS);
            if (argEnd == null) {
                
                break;
            }
            STNode currentArg = parseErrorArgListMatchPattern(ParserRuleContext.ERROR_FIELD_MATCH_PATTERN);
            DiagnosticErrorCode errorCode = validateErrorFieldMatchPatternOrder(lastValidArgKind, currentArg.kind);
            if (errorCode == null) {
                argListMatchPatterns.add(argEnd);
                argListMatchPatterns.add(currentArg);
                lastValidArgKind = currentArg.kind;
            } else if (argListMatchPatterns.size() == 0) {
                addInvalidNodeToNextToken(argEnd, null);
                addInvalidNodeToNextToken(currentArg, errorCode);
            } else {
                updateLastNodeInListWithInvalidNode(argListMatchPatterns, argEnd, null);
                updateLastNodeInListWithInvalidNode(argListMatchPatterns, currentArg, errorCode);
            }
        }
    }

    private boolean isEndOfErrorFieldMatchPatterns() {
        
        return isEndOfErrorFieldBindingPatterns();
    }

    private STNode parseErrorArgListMatchPatternEnd(ParserRuleContext currentCtx) {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return consume();
            case CLOSE_PAREN_TOKEN:
                return null;
            default:
                recover(peek(), currentCtx);
                return parseErrorArgListMatchPatternEnd(currentCtx);
        }
    }

    private STNode parseErrorArgListMatchPattern(ParserRuleContext context) {
        STToken nextToken = peek();
        if (isPredeclaredIdentifier(nextToken.kind)) {
            return parseNamedArgOrSimpleMatchPattern();
        }

        switch (nextToken.kind) {
            case ELLIPSIS_TOKEN:
                return parseRestMatchPattern();
            case OPEN_PAREN_TOKEN:
            case NULL_KEYWORD:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case PLUS_TOKEN:
            case MINUS_TOKEN:
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
            case STRING_LITERAL_TOKEN:
            case OPEN_BRACKET_TOKEN:
            case OPEN_BRACE_TOKEN:
            case ERROR_KEYWORD:
                return parseMatchPattern();
            case VAR_KEYWORD:
                STNode varType = createBuiltinSimpleNameReference(consume());
                STNode variableName = createCaptureOrWildcardBP(parseVariableName());
                return STNodeFactory.createTypedBindingPatternNode(varType, variableName);
            case CLOSE_PAREN_TOKEN:
                return SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                        DiagnosticErrorCode.ERROR_MISSING_MATCH_PATTERN);
            default:
                recover(nextToken, context);
                return parseErrorArgListMatchPattern(context);
        }
    }

    private STNode parseNamedArgOrSimpleMatchPattern() {
        STNode constRefExpr = parseQualifiedIdentifier(ParserRuleContext.MATCH_PATTERN);
        if (constRefExpr.kind == SyntaxKind.QUALIFIED_NAME_REFERENCE || peek().kind != SyntaxKind.EQUAL_TOKEN) {
            return constRefExpr;
        }

        
        return parseNamedArgMatchPattern(((STSimpleNameReferenceNode) constRefExpr).name);
    }

    /**
     * Parses the next named arg match pattern.
     * <br/>
     * <code>named-arg-match-pattern := arg-name = match-pattern</code>
     * <br/>
     * <br/>
     *
     * @return arg match pattern list node added the new arg match pattern
     */
    private STNode parseNamedArgMatchPattern(STNode identifier) {
        startContext(ParserRuleContext.NAMED_ARG_MATCH_PATTERN);
        STNode equalToken = parseAssignOp();
        STNode matchPattern = parseMatchPattern();
        endContext();
        return STNodeFactory.createNamedArgMatchPatternNode(identifier, equalToken, matchPattern);
    }

    private DiagnosticErrorCode validateErrorFieldMatchPatternOrder(SyntaxKind prevArgKind, SyntaxKind currentArgKind) {
        switch (currentArgKind) {
            case NAMED_ARG_MATCH_PATTERN:
            case REST_MATCH_PATTERN:
                
                if (prevArgKind == SyntaxKind.REST_MATCH_PATTERN) {
                    return DiagnosticErrorCode.ERROR_REST_ARG_FOLLOWED_BY_ANOTHER_ARG;
                }
                return null;
            default:
                return DiagnosticErrorCode.ERROR_MATCH_PATTERN_NOT_ALLOWED;
        }
    }

    /**
     * Parse markdown documentation.
     *
     * @return markdown documentation node
     */
    private STNode parseMarkdownDocumentation() {
        List<STNode> markdownDocLineList = new ArrayList<>();

        
        
        
        
        
        STToken nextToken = peek();
        while (nextToken.kind == SyntaxKind.DOCUMENTATION_STRING) {
            STToken documentationString = consume();
            STNode parsedDocLines = parseDocumentationString(documentationString);
            appendParsedDocumentationLines(markdownDocLineList, parsedDocLines);
            nextToken = peek();
        }

        STNode markdownDocLines = STNodeFactory.createNodeList(markdownDocLineList);
        return STNodeFactory.createMarkdownDocumentationNode(markdownDocLines);
    }

    /**
     * Parse documentation string.
     *
     * @return markdown documentation line list node
     */
    private STNode parseDocumentationString(STToken documentationStringToken) {
        List<STNode> leadingTriviaList = getLeadingTriviaList(documentationStringToken.leadingMinutiae());
        Collection<STNodeDiagnostic> diagnostics = new ArrayList<>((documentationStringToken.diagnostics()));

        CharReader charReader = CharReader.from(documentationStringToken.text());
        DocumentationLexer documentationLexer = new DocumentationLexer(charReader, leadingTriviaList, diagnostics);
        AbstractTokenReader tokenReader = new TokenReader(documentationLexer);
        DocumentationParser documentationParser = new DocumentationParser(tokenReader);
        return documentationParser.parse();
    }

    private List<STNode> getLeadingTriviaList(STNode leadingMinutiaeNode) {
        List<STNode> leadingTriviaList = new ArrayList<>();
        int bucketCount = leadingMinutiaeNode.bucketCount();
        for (int i = 0; i < bucketCount; i++) {
            leadingTriviaList.add(leadingMinutiaeNode.childInBucket(i));
        }

        return leadingTriviaList;
    }

    private void appendParsedDocumentationLines(List<STNode> markdownDocLineList, STNode parsedDocLines) {
        int bucketCount = parsedDocLines.bucketCount();
        for (int i = 0; i < bucketCount; i++) {
            STNode markdownDocLine = parsedDocLines.childInBucket(i);
            markdownDocLineList.add(markdownDocLine);
        }
    }

    

    /**
     * Parse any statement that starts with a token that has ambiguity between being
     * a type-desc or an expression.
     *
     * @param annots Annotations
     * @param qualifiers
     * @return Statement node
     */
    private STNode parseStmtStartsWithTypeOrExpr(STNode annots, List<STNode> qualifiers) {
        startContext(ParserRuleContext.AMBIGUOUS_STMT);
        STNode typeOrExpr = parseTypedBindingPatternOrExpr(qualifiers, true);
        return parseStmtStartsWithTypedBPOrExprRhs(annots, typeOrExpr);
    }

    private STNode parseStmtStartsWithTypedBPOrExprRhs(STNode annots, STNode typedBindingPatternOrExpr) {
        if (typedBindingPatternOrExpr.kind == SyntaxKind.TYPED_BINDING_PATTERN) {
            List<STNode> varDeclQualifiers = new ArrayList<>();
            switchContext(ParserRuleContext.VAR_DECL_STMT);
            return parseVarDeclRhs(annots, varDeclQualifiers, typedBindingPatternOrExpr, false);
        }

        STNode expr = getExpression(typedBindingPatternOrExpr);
        expr = getExpression(parseExpressionRhs(DEFAULT_OP_PRECEDENCE, expr, false, true));
        return parseStatementStartWithExprRhs(expr);
    }

    private STNode parseTypedBindingPatternOrExpr(boolean allowAssignment) {
        List<STNode> typeDescQualifiers = new ArrayList<>();
        return parseTypedBindingPatternOrExpr(typeDescQualifiers, allowAssignment);
    }

    private STNode parseTypedBindingPatternOrExpr(List<STNode> qualifiers, boolean allowAssignment) {
        parseTypeDescQualifiers(qualifiers);
        STToken nextToken = peek();
        STNode typeOrExpr;
        if (isPredeclaredIdentifier(nextToken.kind)) {
            reportInvalidQualifierList(qualifiers);
            typeOrExpr = parseQualifiedIdentifier(ParserRuleContext.TYPE_NAME_OR_VAR_NAME);
            return parseTypedBindingPatternOrExprRhs(typeOrExpr, allowAssignment);
        }
        
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                reportInvalidQualifierList(qualifiers);
                return parseTypedBPOrExprStartsWithOpenParenthesis();
            case FUNCTION_KEYWORD:
                return parseAnonFuncExprOrTypedBPWithFuncType(qualifiers);
            case OPEN_BRACKET_TOKEN:
                reportInvalidQualifierList(qualifiers);
                typeOrExpr = parseTupleTypeDescOrListConstructor(STNodeFactory.createEmptyNodeList());
                return parseTypedBindingPatternOrExprRhs(typeOrExpr, allowAssignment);
            
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case STRING_LITERAL_TOKEN:
            case NULL_KEYWORD:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
                reportInvalidQualifierList(qualifiers);
                STNode basicLiteral = parseBasicLiteral();
                return parseTypedBindingPatternOrExprRhs(basicLiteral, allowAssignment);
            default:
                if (isValidExpressionStart(nextToken.kind, 1)) {
                    reportInvalidQualifierList(qualifiers);
                    return parseActionOrExpressionInLhs(STNodeFactory.createEmptyNodeList());
                }

                return parseTypedBindingPattern(qualifiers, ParserRuleContext.VAR_DECL_STMT);
        }
    }

    /**
     * Parse the component after the ambiguous starting node. Ambiguous node could be either an expr
     * or a type-desc. The component followed by this ambiguous node could be the binding-pattern or
     * the expression-rhs.
     *
     * @param typeOrExpr      Type desc or the expression
     * @param allowAssignment Flag indicating whether to allow assignment. i.e.: whether this is a
     *                        valid lvalue expression
     * @return Typed-binding-pattern node or an expression node
     */
    private STNode parseTypedBindingPatternOrExprRhs(STNode typeOrExpr, boolean allowAssignment) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case PIPE_TOKEN:
            case BITWISE_AND_TOKEN:
                STToken nextNextToken = peek(2);
                if (nextNextToken.kind == SyntaxKind.EQUAL_TOKEN) {
                    return typeOrExpr;
                }

                STNode pipeOrAndToken = parseBinaryOperator();
                STNode rhsTypedBPOrExpr = parseTypedBindingPatternOrExpr(allowAssignment);
                if (rhsTypedBPOrExpr.kind == SyntaxKind.TYPED_BINDING_PATTERN) {
                    STTypedBindingPatternNode typedBP = (STTypedBindingPatternNode) rhsTypedBPOrExpr;
                    typeOrExpr = getTypeDescFromExpr(typeOrExpr);

                    STNode newTypeDesc = mergeTypes(typeOrExpr, pipeOrAndToken, typedBP.typeDescriptor);
                    return STNodeFactory.createTypedBindingPatternNode(newTypeDesc, typedBP.bindingPattern);
                }

                
                if (peek().kind == SyntaxKind.EQUAL_TOKEN) {
                    return createCaptureBPWithMissingVarName(typeOrExpr, pipeOrAndToken, rhsTypedBPOrExpr);
                }

                return STNodeFactory.createBinaryExpressionNode(SyntaxKind.BINARY_EXPRESSION, typeOrExpr,
                        pipeOrAndToken, rhsTypedBPOrExpr);
            case SEMICOLON_TOKEN:
                if (isExpression(typeOrExpr.kind)) {
                    return typeOrExpr;
                }

                if (isDefiniteTypeDesc(typeOrExpr.kind) || !isAllBasicLiterals(typeOrExpr)) {
                    
                    STNode typeDesc = getTypeDescFromExpr(typeOrExpr);
                    return parseTypeBindingPatternStartsWithAmbiguousNode(typeDesc);
                }

                return typeOrExpr;
            case IDENTIFIER_TOKEN:
            case QUESTION_MARK_TOKEN:
                if (isAmbiguous(typeOrExpr) || isDefiniteTypeDesc(typeOrExpr.kind)) {
                    
                    STNode typeDesc = getTypeDescFromExpr(typeOrExpr);
                    return parseTypeBindingPatternStartsWithAmbiguousNode(typeDesc);
                }

                return typeOrExpr;
            case EQUAL_TOKEN:
                return typeOrExpr;
            case OPEN_BRACKET_TOKEN:
                return parseTypedBindingPatternOrMemberAccess(typeOrExpr, false, allowAssignment,
                        ParserRuleContext.AMBIGUOUS_STMT);
            case OPEN_BRACE_TOKEN: 
            case ERROR_KEYWORD: 
                STNode typeDesc = getTypeDescFromExpr(typeOrExpr);
                return parseTypeBindingPatternStartsWithAmbiguousNode(typeDesc);
            default:
                
                if (isCompoundAssignment(nextToken.kind)) {
                    return typeOrExpr;
                }

                
                
                if (isValidExprRhsStart(nextToken.kind, typeOrExpr.kind)) {
                    return typeOrExpr;
                }

                STToken token = peek();
                SyntaxKind typeOrExprKind = typeOrExpr.kind;
                if (typeOrExprKind == SyntaxKind.QUALIFIED_NAME_REFERENCE ||
                        typeOrExprKind == SyntaxKind.SIMPLE_NAME_REFERENCE) {
                    recover(token, ParserRuleContext.BINDING_PATTERN_OR_VAR_REF_RHS);
                } else {
                    recover(token, ParserRuleContext.BINDING_PATTERN_OR_EXPR_RHS);
                }

                return parseTypedBindingPatternOrExprRhs(typeOrExpr, allowAssignment);
        }
    }

    private STNode createCaptureBPWithMissingVarName(STNode lhsType, STNode separatorToken, STNode rhsType) {
        lhsType = getTypeDescFromExpr(lhsType);
        rhsType = getTypeDescFromExpr(rhsType);

        STNode newTypeDesc = mergeTypes(lhsType, separatorToken, rhsType);

        STNode identifier = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                ParserRuleContext.VARIABLE_NAME);
        STNode captureBP = STNodeFactory.createCaptureBindingPatternNode(identifier);

        return STNodeFactory.createTypedBindingPatternNode(newTypeDesc, captureBP);
    }

    private STNode parseTypeBindingPatternStartsWithAmbiguousNode(STNode typeDesc) {
        

        
        
        typeDesc = parseComplexTypeDescriptor(typeDesc, ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true);
        return parseTypedBindingPatternTypeRhs(typeDesc, ParserRuleContext.VAR_DECL_STMT);
    }

    private STNode parseTypedBPOrExprStartsWithOpenParenthesis() {
        STNode exprOrTypeDesc = parseTypedDescOrExprStartsWithOpenParenthesis();
        if (isDefiniteTypeDesc(exprOrTypeDesc.kind)) {
            return parseTypeBindingPatternStartsWithAmbiguousNode(exprOrTypeDesc);
        }

        return parseTypedBindingPatternOrExprRhs(exprOrTypeDesc, false);
    }

    private boolean isDefiniteTypeDesc(SyntaxKind kind) {
        return kind.compareTo(SyntaxKind.RECORD_TYPE_DESC) >= 0 && kind.compareTo(SyntaxKind.FUTURE_TYPE_DESC) <= 0;
    }

    private boolean isDefiniteExpr(SyntaxKind kind) {
        if (kind == SyntaxKind.QUALIFIED_NAME_REFERENCE || kind == SyntaxKind.SIMPLE_NAME_REFERENCE) {
            return false;
        }

        return kind.compareTo(SyntaxKind.BINARY_EXPRESSION) >= 0 && kind.compareTo(SyntaxKind.ERROR_CONSTRUCTOR) <= 0;
    }

    private boolean isDefiniteAction(SyntaxKind kind) {
        return kind.compareTo(SyntaxKind.REMOTE_METHOD_CALL_ACTION) >= 0 && 
                kind.compareTo(SyntaxKind.CLIENT_RESOURCE_ACCESS_ACTION) <= 0;
    }

    /**
     * Parse type or expression that starts with open parenthesis. Possible options are:
     * 1) () - nil type-desc or nil-literal
     * 2) (T) - Parenthesized type-desc
     * 3) (expr) - Parenthesized expression
     * 4) (param, param, ..) - Anon function params
     *
     * @return Type-desc or expression node
     */
    private STNode parseTypedDescOrExprStartsWithOpenParenthesis() {
        STNode openParen = parseOpenParenthesis();
        STToken nextToken = peek();

        if (nextToken.kind == SyntaxKind.CLOSE_PAREN_TOKEN) {
            STNode closeParen = parseCloseParenthesis();
            return parseTypeOrExprStartWithEmptyParenthesis(openParen, closeParen);
        }

        STNode typeOrExpr = parseTypeDescOrExpr();
        if (isAction(typeOrExpr)) {
            STNode closeParen = parseCloseParenthesis();
            return STNodeFactory.createBracedExpressionNode(SyntaxKind.BRACED_ACTION, openParen, typeOrExpr,
                    closeParen);
        }

        if (isExpression(typeOrExpr.kind)) {
            startContext(ParserRuleContext.BRACED_EXPR_OR_ANON_FUNC_PARAMS);
            return parseBracedExprOrAnonFuncParamRhs(openParen, typeOrExpr, false);
        }

        STNode typeDescNode = getTypeDescFromExpr(typeOrExpr);
        typeDescNode = parseComplexTypeDescriptor(typeDescNode, ParserRuleContext.TYPE_DESC_IN_PARENTHESIS, false);

        STNode closeParen = parseCloseParenthesis();
        return STNodeFactory.createParenthesisedTypeDescriptorNode(openParen, typeDescNode, closeParen);
    }

    /**
     * Parse type-desc or expression. This method does not handle binding patterns.
     *
     * @return Type-desc node or expression node
     */
    private STNode parseTypeDescOrExpr() {
        List<STNode> typeDescQualifiers = new ArrayList<>();
        return parseTypeDescOrExpr(typeDescQualifiers);
    }

    private STNode parseTypeDescOrExpr(List<STNode> qualifiers) {
        parseTypeDescQualifiers(qualifiers);
        STToken nextToken = peek();
        STNode typeOrExpr;
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                reportInvalidQualifierList(qualifiers);
                typeOrExpr = parseTypedDescOrExprStartsWithOpenParenthesis();
                break;
            case FUNCTION_KEYWORD:
                typeOrExpr = parseAnonFuncExprOrFuncTypeDesc(qualifiers);
                break;
            case IDENTIFIER_TOKEN:
                reportInvalidQualifierList(qualifiers);
                typeOrExpr = parseQualifiedIdentifier(ParserRuleContext.TYPE_NAME_OR_VAR_NAME);
                return parseTypeDescOrExprRhs(typeOrExpr);
            case OPEN_BRACKET_TOKEN:
                reportInvalidQualifierList(qualifiers);
                typeOrExpr = parseTupleTypeDescOrListConstructor(STNodeFactory.createEmptyNodeList());
                break;
            
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case STRING_LITERAL_TOKEN:
            case NULL_KEYWORD:
            case TRUE_KEYWORD:
            case FALSE_KEYWORD:
            case DECIMAL_FLOATING_POINT_LITERAL_TOKEN:
            case HEX_FLOATING_POINT_LITERAL_TOKEN:
                reportInvalidQualifierList(qualifiers);
                STNode basicLiteral = parseBasicLiteral();
                return parseTypeDescOrExprRhs(basicLiteral);
            default:
                if (isValidExpressionStart(nextToken.kind, 1)) {
                    reportInvalidQualifierList(qualifiers);
                    return parseActionOrExpressionInLhs(STNodeFactory.createEmptyNodeList());
                }
                return parseTypeDescriptor(qualifiers, ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN);
        }

        if (isDefiniteTypeDesc(typeOrExpr.kind)) {
            return parseComplexTypeDescriptor(typeOrExpr, ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true);
        }

        return parseTypeDescOrExprRhs(typeOrExpr);
    }

    private boolean isExpression(SyntaxKind kind) {
        switch (kind) {
            case NUMERIC_LITERAL:
            case STRING_LITERAL_TOKEN:
            case NIL_LITERAL:
            case NULL_LITERAL:
            case BOOLEAN_LITERAL:
                return true;
            default:
                return kind.compareTo(SyntaxKind.BINARY_EXPRESSION) >= 0 &&
                        kind.compareTo(SyntaxKind.ERROR_CONSTRUCTOR) <= 0;
        }
    }

    /**
     * Parse statement that starts with an empty parenthesis. Empty parenthesis can be
     * 1) Nil literal
     * 2) Nil type-desc
     * 3) Anon-function params
     *
     * @param openParen  Open parenthesis
     * @param closeParen Close parenthesis
     * @return Parsed node
     */
    private STNode parseTypeOrExprStartWithEmptyParenthesis(STNode openParen, STNode closeParen) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case RIGHT_DOUBLE_ARROW_TOKEN:
                STNode params = STNodeFactory.createEmptyNodeList();
                STNode anonFuncParam =
                        STNodeFactory.createImplicitAnonymousFunctionParameters(openParen, params, closeParen);
                return parseImplicitAnonFunc(anonFuncParam, false);
            default:
                return STNodeFactory.createNilLiteralNode(openParen, closeParen);
        }
    }

    private STNode parseAnonFuncExprOrTypedBPWithFuncType(List<STNode> qualifiers) {
        STNode exprOrTypeDesc = parseAnonFuncExprOrFuncTypeDesc(qualifiers);
        if (isAction(exprOrTypeDesc) || isExpression(exprOrTypeDesc.kind)) {
            return exprOrTypeDesc;
        }

        return parseTypedBindingPatternTypeRhs(exprOrTypeDesc, ParserRuleContext.VAR_DECL_STMT);
    }

    /**
     * Parse anon-func-expr or function-type-desc, by resolving the ambiguity.
     *
     * @param qualifiers Preceding qualifiers
     * @return Anon-func-expr or function-type-desc
     */
    private STNode parseAnonFuncExprOrFuncTypeDesc(List<STNode> qualifiers) {
        startContext(ParserRuleContext.FUNC_TYPE_DESC_OR_ANON_FUNC);
        STNode qualifierList;
        STNode functionKeyword = parseFunctionKeyword();
        STNode funcSignature;

        if (peek().kind == SyntaxKind.OPEN_PAREN_TOKEN) {
            funcSignature = parseFuncSignature(true);

            
            STNode[] nodes = createFuncTypeQualNodeList(qualifiers, functionKeyword, true);
            qualifierList = nodes[0];
            functionKeyword = nodes[1];

            endContext();
            return parseAnonFuncExprOrFuncTypeDesc(qualifierList, functionKeyword, funcSignature);
        }

        funcSignature = STNodeFactory.createEmptyNode();

        
        STNode[] nodes = createFuncTypeQualNodeList(qualifiers, functionKeyword, false);
        qualifierList = nodes[0];
        functionKeyword = nodes[1];

        STNode funcTypeDesc = STNodeFactory.createFunctionTypeDescriptorNode(qualifierList, functionKeyword,
                                                                             funcSignature);
        if (getCurrentContext() != ParserRuleContext.STMT_START_BRACKETED_LIST) {
            switchContext(ParserRuleContext.VAR_DECL_STMT);
            return parseComplexTypeDescriptor(funcTypeDesc, ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true);
        }
        return parseComplexTypeDescriptor(funcTypeDesc, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
    }

    private STNode parseAnonFuncExprOrFuncTypeDesc(STNode qualifierList, STNode functionKeyword, STNode funcSignature) {
        ParserRuleContext currentCtx = getCurrentContext();
        switch (peek().kind) {
            case OPEN_BRACE_TOKEN:
            case RIGHT_DOUBLE_ARROW_TOKEN:
                if (currentCtx != ParserRuleContext.STMT_START_BRACKETED_LIST) {
                    switchContext(ParserRuleContext.EXPRESSION_STATEMENT);
                }
                startContext(ParserRuleContext.ANON_FUNC_EXPRESSION);
                
                funcSignature = validateAndGetFuncParams((STFunctionSignatureNode) funcSignature);

                STNode funcBody = parseAnonFuncBody(false);
                STNode annots = STNodeFactory.createEmptyNodeList();
                STNode anonFunc = STNodeFactory.createExplicitAnonymousFunctionExpressionNode(annots, qualifierList,
                        functionKeyword, funcSignature, funcBody);
                return parseExpressionRhs(DEFAULT_OP_PRECEDENCE, anonFunc, false, true);
            case IDENTIFIER_TOKEN:
            default:
                STNode funcTypeDesc = STNodeFactory.createFunctionTypeDescriptorNode(qualifierList, functionKeyword,
                        funcSignature);
                if (currentCtx != ParserRuleContext.STMT_START_BRACKETED_LIST) {
                    switchContext(ParserRuleContext.VAR_DECL_STMT);
                    return parseComplexTypeDescriptor(funcTypeDesc, ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN,
                            true);
                }
                return parseComplexTypeDescriptor(funcTypeDesc, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
        }
    }

    private STNode parseTypeDescOrExprRhs(STNode typeOrExpr) {
        STToken nextToken = peek();
        STNode typeDesc;
        switch (nextToken.kind) {
            case PIPE_TOKEN:
            case BITWISE_AND_TOKEN:
                STToken nextNextToken = peek(2);
                if (nextNextToken.kind == SyntaxKind.EQUAL_TOKEN) {
                    return typeOrExpr;
                }

                STNode pipeOrAndToken = parseBinaryOperator();
                STNode rhsTypeDescOrExpr = parseTypeDescOrExpr();
                if (isExpression(rhsTypeDescOrExpr.kind)) {
                    return STNodeFactory.createBinaryExpressionNode(SyntaxKind.BINARY_EXPRESSION, typeOrExpr,
                            pipeOrAndToken, rhsTypeDescOrExpr);
                }

                typeDesc = getTypeDescFromExpr(typeOrExpr);
                rhsTypeDescOrExpr = getTypeDescFromExpr(rhsTypeDescOrExpr);
                return mergeTypes(typeDesc, pipeOrAndToken, rhsTypeDescOrExpr);
            case IDENTIFIER_TOKEN:
            case QUESTION_MARK_TOKEN:
                
                
                
                typeDesc = parseComplexTypeDescriptor(getTypeDescFromExpr(typeOrExpr), 
                        ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, false);
                return typeDesc;
            case SEMICOLON_TOKEN:
                return getTypeDescFromExpr(typeOrExpr);
            case EQUAL_TOKEN:
            case CLOSE_PAREN_TOKEN:
            case CLOSE_BRACE_TOKEN:
            case CLOSE_BRACKET_TOKEN:
            case EOF_TOKEN:
            case COMMA_TOKEN:
                return typeOrExpr;
            case OPEN_BRACKET_TOKEN:
                return parseTypedBindingPatternOrMemberAccess(typeOrExpr, false, true,
                        ParserRuleContext.AMBIGUOUS_STMT);
            case ELLIPSIS_TOKEN:
                STNode ellipsis = parseEllipsis();
                typeOrExpr = getTypeDescFromExpr(typeOrExpr);
                return STNodeFactory.createRestDescriptorNode(typeOrExpr, ellipsis);
            default:
                
                if (isCompoundAssignment(nextToken.kind)) {
                    return typeOrExpr;
                }

                
                
                if (isValidExprRhsStart(nextToken.kind, typeOrExpr.kind)) {
                    return parseExpressionRhs(DEFAULT_OP_PRECEDENCE, typeOrExpr, false, false, false, false);
                }

                recover(peek(), ParserRuleContext.TYPE_DESC_OR_EXPR_RHS);
                return parseTypeDescOrExprRhs(typeOrExpr);
        }
    }

    private boolean isAmbiguous(STNode node) {
        switch (node.kind) {
            case SIMPLE_NAME_REFERENCE:
            case QUALIFIED_NAME_REFERENCE:
            case NIL_LITERAL:
            case NULL_LITERAL:
            case NUMERIC_LITERAL:
            case STRING_LITERAL:
            case BOOLEAN_LITERAL:
            case BRACKETED_LIST:
                return true;
            case BINARY_EXPRESSION:
                STBinaryExpressionNode binaryExpr = (STBinaryExpressionNode) node;
                if (binaryExpr.operator.kind != SyntaxKind.PIPE_TOKEN ||
                        binaryExpr.operator.kind == SyntaxKind.BITWISE_AND_TOKEN) {
                    return false;
                }
                return isAmbiguous(binaryExpr.lhsExpr) && isAmbiguous(binaryExpr.rhsExpr);
            case BRACED_EXPRESSION:
                return isAmbiguous(((STBracedExpressionNode) node).expression);
            case INDEXED_EXPRESSION:
                STIndexedExpressionNode indexExpr = (STIndexedExpressionNode) node;
                if (!isAmbiguous(indexExpr.containerExpression)) {
                    return false;
                }

                STNode keys = indexExpr.keyExpression;
                for (int i = 0; i < keys.bucketCount(); i++) {
                    STNode item = keys.childInBucket(i);
                    if (item.kind == SyntaxKind.COMMA_TOKEN) {
                        continue;
                    }

                    if (!isAmbiguous(item)) {
                        return false;
                    }
                }
                return true;
            default:
                return false;
        }
    }

    private boolean isAllBasicLiterals(STNode node) {
        switch (node.kind) {
            case NIL_LITERAL:
            case NULL_LITERAL:
            case NUMERIC_LITERAL:
            case STRING_LITERAL:
            case BOOLEAN_LITERAL:
                return true;
            case BINARY_EXPRESSION:
                STBinaryExpressionNode binaryExpr = (STBinaryExpressionNode) node;
                if (binaryExpr.operator.kind != SyntaxKind.PIPE_TOKEN ||
                        binaryExpr.operator.kind == SyntaxKind.BITWISE_AND_TOKEN) {
                    return false;
                }
                return isAmbiguous(binaryExpr.lhsExpr) && isAmbiguous(binaryExpr.rhsExpr);
            case BRACED_EXPRESSION:
                return isAmbiguous(((STBracedExpressionNode) node).expression);
            case BRACKETED_LIST:
                STAmbiguousCollectionNode list = (STAmbiguousCollectionNode) node;
                for (STNode member : list.members) {
                    if (member.kind == SyntaxKind.COMMA_TOKEN) {
                        continue;
                    }

                    if (!isAllBasicLiterals(member)) {
                        return false;
                    }
                }

                return true;
            case UNARY_EXPRESSION:
                STUnaryExpressionNode unaryExpr = (STUnaryExpressionNode) node;
                if (unaryExpr.unaryOperator.kind != SyntaxKind.PLUS_TOKEN &&
                        unaryExpr.unaryOperator.kind != SyntaxKind.MINUS_TOKEN) {
                    return false;
                }

                return isNumericLiteral(unaryExpr.expression);
            default:
                return false;
        }
    }

    private boolean isNumericLiteral(STNode node) {
        switch (node.kind) {
            case NUMERIC_LITERAL:
                return true;
            default:
                return false;
        }
    }

    

    /**
     * Parse binding-patterns.
     * <p>
     * <code>
     * binding-pattern := capture-binding-pattern
     * | wildcard-binding-pattern
     * | list-binding-pattern
     * | mapping-binding-pattern
     * | functional-binding-pattern
     * <br/><br/>
     * <p>
     * capture-binding-pattern := variable-name
     * variable-name := identifier
     * <br/><br/>
     * <p>
     * wildcard-binding-pattern := _
     * list-binding-pattern := [ list-member-binding-patterns ]
     * <br/>
     * list-member-binding-patterns := binding-pattern (, binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * <br/><br/>
     * <p>
     * mapping-binding-pattern := { field-binding-patterns }
     * field-binding-patterns := field-binding-pattern (, field-binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * <br/>
     * field-binding-pattern := field-name : binding-pattern | variable-name
     * <br/>
     * rest-binding-pattern := ... variable-name
     * <p>
     * <br/><br/>
     * functional-binding-pattern := functionally-constructible-type-reference ( arg-list-binding-pattern )
     * <br/>
     * arg-list-binding-pattern := positional-arg-binding-patterns [, other-arg-binding-patterns]
     * | other-arg-binding-patterns
     * <br/>
     * positional-arg-binding-patterns := positional-arg-binding-pattern (, positional-arg-binding-pattern)*
     * <br/>
     * positional-arg-binding-pattern := binding-pattern
     * <br/>
     * other-arg-binding-patterns := named-arg-binding-patterns [, rest-binding-pattern]
     * | [rest-binding-pattern]
     * <br/>
     * named-arg-binding-patterns := named-arg-binding-pattern (, named-arg-binding-pattern)*
     * <br/>
     * named-arg-binding-pattern := arg-name = binding-pattern
     * </code>
     *
     * @return binding-pattern node
     */
    private STNode parseBindingPattern() {
        switch (peek().kind) {
            case OPEN_BRACKET_TOKEN:
                return parseListBindingPattern();
            case IDENTIFIER_TOKEN:
                return parseBindingPatternStartsWithIdentifier();
            case OPEN_BRACE_TOKEN:
                return parseMappingBindingPattern();
            case ERROR_KEYWORD:
                return parseErrorBindingPattern();
            default:
                recover(peek(), ParserRuleContext.BINDING_PATTERN);
                return parseBindingPattern();
        }
    }

    private STNode parseBindingPatternStartsWithIdentifier() {
        STNode argNameOrBindingPattern =
                parseQualifiedIdentifier(ParserRuleContext.BINDING_PATTERN_STARTING_IDENTIFIER);
        STToken secondToken = peek();
        if (secondToken.kind == SyntaxKind.OPEN_PAREN_TOKEN) {
            startContext(ParserRuleContext.ERROR_BINDING_PATTERN);
            STNode errorKeyword = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.ERROR_KEYWORD,
                    ParserRuleContext.ERROR_KEYWORD);
            return parseErrorBindingPattern(errorKeyword, argNameOrBindingPattern);
        }

        if (argNameOrBindingPattern.kind != SyntaxKind.SIMPLE_NAME_REFERENCE) {
            STNode identifier = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
            identifier = SyntaxErrors.cloneWithLeadingInvalidNodeMinutiae(identifier, argNameOrBindingPattern,
                    DiagnosticErrorCode.ERROR_FIELD_BP_INSIDE_LIST_BP);
            return STNodeFactory.createCaptureBindingPatternNode(identifier);
        }

        return createCaptureOrWildcardBP(((STSimpleNameReferenceNode) argNameOrBindingPattern).name);
    }

    private STNode createCaptureOrWildcardBP(STNode varName) {
        STNode bindingPattern;
        if (isWildcardBP(varName)) {
            bindingPattern = getWildcardBindingPattern(varName);
        } else {
            bindingPattern = STNodeFactory.createCaptureBindingPatternNode(varName);
        }
        return bindingPattern;
    }

    /**
     * Parse list-binding-patterns.
     * <p>
     * <code>
     * list-binding-pattern := [ list-member-binding-patterns ]
     * <br/>
     * list-member-binding-patterns := binding-pattern (, binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * </code>
     *
     * @return list-binding-pattern node
     */
    private STNode parseListBindingPattern() {
        startContext(ParserRuleContext.LIST_BINDING_PATTERN);
        STNode openBracket = parseOpenBracket();
        List<STNode> bindingPatternsList = new ArrayList<>();
        STNode listBindingPattern = parseListBindingPattern(openBracket, bindingPatternsList);
        endContext();
        return listBindingPattern;
    }

    private STNode parseListBindingPattern(STNode openBracket, List<STNode> bindingPatternsList) {
        if (isEndOfListBindingPattern(peek().kind) && bindingPatternsList.size() == 0) {
            
            STNode closeBracket = parseCloseBracket();
            STNode bindingPatternsNode = STNodeFactory.createNodeList(bindingPatternsList);
            return STNodeFactory.createListBindingPatternNode(openBracket, bindingPatternsNode, closeBracket);
        }
        STNode listBindingPatternMember = parseListBindingPatternMember();
        bindingPatternsList.add(listBindingPatternMember);
        STNode listBindingPattern = parseListBindingPattern(openBracket, listBindingPatternMember, bindingPatternsList);
        return listBindingPattern;
    }

    private STNode parseListBindingPattern(STNode openBracket, STNode firstMember, List<STNode> bindingPatterns) {
        STNode member = firstMember;
        
        STToken token = peek(); 
        STNode listBindingPatternRhs = null;
        while (!isEndOfListBindingPattern(token.kind) && member.kind != SyntaxKind.REST_BINDING_PATTERN) {
            listBindingPatternRhs = parseListBindingPatternMemberRhs();
            if (listBindingPatternRhs == null) {
                break;
            }

            bindingPatterns.add(listBindingPatternRhs);
            member = parseListBindingPatternMember();
            bindingPatterns.add(member);
            token = peek();
        }

        STNode closeBracket = parseCloseBracket();
        STNode bindingPatternsNode = STNodeFactory.createNodeList(bindingPatterns);
        return STNodeFactory.createListBindingPatternNode(openBracket, bindingPatternsNode, closeBracket);
    }

    private STNode parseListBindingPatternMemberRhs() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACKET_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.LIST_BINDING_PATTERN_MEMBER_END);
                return parseListBindingPatternMemberRhs();
        }
    }

    private boolean isEndOfListBindingPattern(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case CLOSE_BRACKET_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse list-binding-pattern member.
     * <p>
     * <code>
     * list-binding-pattern := [ list-member-binding-patterns ]
     * <br/>
     * list-member-binding-patterns := binding-pattern (, binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * </code>
     *
     * @return List binding pattern member
     */
    private STNode parseListBindingPatternMember() {
        switch (peek().kind) {
            case ELLIPSIS_TOKEN:
                return parseRestBindingPattern();
            case OPEN_BRACKET_TOKEN:
            case IDENTIFIER_TOKEN:
            case OPEN_BRACE_TOKEN:
            case ERROR_KEYWORD:
                return parseBindingPattern();
            default:
                recover(peek(), ParserRuleContext.LIST_BINDING_PATTERN_MEMBER);
                return parseListBindingPatternMember();
        }
    }

    /**
     * Parse rest binding pattern.
     * <p>
     * <code>
     * rest-binding-pattern := ... variable-name
     * </code>
     *
     * @return Rest binding pattern node
     */
    private STNode parseRestBindingPattern() {
        startContext(ParserRuleContext.REST_BINDING_PATTERN);
        STNode ellipsis = parseEllipsis();
        STNode varName = parseVariableName();
        endContext();

        STSimpleNameReferenceNode simpleNameReferenceNode =
                (STSimpleNameReferenceNode) STNodeFactory.createSimpleNameReferenceNode(varName);
        return STNodeFactory.createRestBindingPatternNode(ellipsis, simpleNameReferenceNode);
    }

    /**
     * Parse Typed-binding-pattern.
     * <p>
     * <code>
     * typed-binding-pattern := inferable-type-descriptor binding-pattern
     * <br/><br/>
     * inferable-type-descriptor := type-descriptor | var
     * </code>
     *
     * @return Typed binding pattern node
     */
    private STNode parseTypedBindingPattern(ParserRuleContext context) {
        List<STNode> typeDescQualifiers = new ArrayList<>();
        return parseTypedBindingPattern(typeDescQualifiers, context);
    }

    private STNode parseTypedBindingPattern(List<STNode> qualifiers, ParserRuleContext context) {
        STNode typeDesc = parseTypeDescriptor(qualifiers,
                ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true, false, TypePrecedence.DEFAULT);
        STNode typeBindingPattern = parseTypedBindingPatternTypeRhs(typeDesc, context);
        return typeBindingPattern;
    }

    /**
     * Parse mapping-binding-patterns.
     * <p>
     * <code>
     * mapping-binding-pattern := { field-binding-patterns }
     * <br/><br/>
     * field-binding-patterns := field-binding-pattern (, field-binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * <br/><br/>
     * field-binding-pattern := field-name : binding-pattern | variable-name
     * </code>
     *
     * @return mapping-binding-pattern node
     */
    private STNode parseMappingBindingPattern() {
        startContext(ParserRuleContext.MAPPING_BINDING_PATTERN);
        STNode openBrace = parseOpenBrace();

        STToken token = peek();
        if (isEndOfMappingBindingPattern(token.kind)) {
            STNode closeBrace = parseCloseBrace();
            STNode bindingPatternsNode = STNodeFactory.createEmptyNodeList();
            endContext();
            return STNodeFactory.createMappingBindingPatternNode(openBrace, bindingPatternsNode, closeBrace);
        }

        List<STNode> bindingPatterns = new ArrayList<>();
        STNode prevMember = parseMappingBindingPatternMember();
        if (prevMember.kind != SyntaxKind.REST_BINDING_PATTERN) {
            bindingPatterns.add(prevMember);
        }
        return parseMappingBindingPattern(openBrace, bindingPatterns, prevMember);
    }

    private STNode parseMappingBindingPattern(STNode openBrace, List<STNode> bindingPatterns, STNode prevMember) {
        STToken token = peek(); 
        STNode mappingBindingPatternRhs = null;
        while (!isEndOfMappingBindingPattern(token.kind) && prevMember.kind != SyntaxKind.REST_BINDING_PATTERN) {
            mappingBindingPatternRhs = parseMappingBindingPatternEnd();
            if (mappingBindingPatternRhs == null) {
                break;
            }

            bindingPatterns.add(mappingBindingPatternRhs);
            prevMember = parseMappingBindingPatternMember();
            if (prevMember.kind == SyntaxKind.REST_BINDING_PATTERN) {
                break;
            }
            bindingPatterns.add(prevMember);
            token = peek();
        }

        if (prevMember.kind == SyntaxKind.REST_BINDING_PATTERN) {
            bindingPatterns.add(prevMember);
        }

        STNode closeBrace = parseCloseBrace();
        STNode bindingPatternsNode = STNodeFactory.createNodeList(bindingPatterns);
        endContext();
        return STNodeFactory.createMappingBindingPatternNode(openBrace, bindingPatternsNode, closeBrace);
    }

    /**
     * Parse mapping-binding-pattern entry.
     * <p>
     * <code>
     * mapping-binding-pattern := { field-binding-patterns }
     * <br/><br/>
     * field-binding-patterns := field-binding-pattern (, field-binding-pattern)* [, rest-binding-pattern]
     * | [ rest-binding-pattern ]
     * <br/><br/>
     * field-binding-pattern := field-name : binding-pattern
     * | variable-name
     * </code>
     *
     * @return mapping-binding-pattern node
     */
    private STNode parseMappingBindingPatternMember() {
        STToken token = peek();
        switch (token.kind) {
            case ELLIPSIS_TOKEN:
                return parseRestBindingPattern();
            default:
                return parseFieldBindingPattern();
        }
    }

    private STNode parseMappingBindingPatternEnd() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACE_TOKEN:
                return null;
            default:
                recover(nextToken, ParserRuleContext.MAPPING_BINDING_PATTERN_END);
                return parseMappingBindingPatternEnd();
        }
    }

    /**
     * Parse field-binding-pattern.
     * <code>field-binding-pattern := field-name : binding-pattern | varname</code>
     *
     * @return field-binding-pattern node
     */
    private STNode parseFieldBindingPattern() {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN:
                STNode identifier = parseIdentifier(ParserRuleContext.FIELD_BINDING_PATTERN_NAME);
                STNode simpleNameReference = STNodeFactory.createSimpleNameReferenceNode(identifier);
                return parseFieldBindingPattern(simpleNameReference);
            default:
                recover(nextToken, ParserRuleContext.FIELD_BINDING_PATTERN_NAME);
                return parseFieldBindingPattern();
        }
    }

    private STNode parseFieldBindingPattern(STNode simpleNameReference) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case COMMA_TOKEN:
            case CLOSE_BRACE_TOKEN:
                return STNodeFactory.createFieldBindingPatternVarnameNode(simpleNameReference);
            case COLON_TOKEN:
                STNode colon = parseColon();
                STNode bindingPattern = parseBindingPattern();
                return STNodeFactory.createFieldBindingPatternFullNode(simpleNameReference, colon, bindingPattern);
            default:
                recover(nextToken, ParserRuleContext.FIELD_BINDING_PATTERN_END);
                return parseFieldBindingPattern(simpleNameReference);
        }
    }

    private boolean isEndOfMappingBindingPattern(SyntaxKind nextTokenKind) {
        return nextTokenKind == SyntaxKind.CLOSE_BRACE_TOKEN || isEndOfModuleLevelNode(1);
    }

    private STNode parseErrorTypeDescOrErrorBP(STNode annots) {
        STToken nextNextToken = peek(2);
        switch (nextNextToken.kind) {
            case OPEN_PAREN_TOKEN:
                return parseAsErrorBindingPattern();
            case LT_TOKEN:
                return parseAsErrorTypeDesc(annots);
            case IDENTIFIER_TOKEN:
                
                SyntaxKind nextNextNextTokenKind = peek(3).kind;
                if (nextNextNextTokenKind == SyntaxKind.COLON_TOKEN ||
                        nextNextNextTokenKind == SyntaxKind.OPEN_PAREN_TOKEN) {
                    return parseAsErrorBindingPattern();
                }
                
            default:
                return parseAsErrorTypeDesc(annots);
        }
    }

    private STNode parseAsErrorBindingPattern() {
        startContext(ParserRuleContext.ASSIGNMENT_STMT);
        return parseAssignmentStmtRhs(parseErrorBindingPattern());
    }

    private STNode parseAsErrorTypeDesc(STNode annots) {
        STNode finalKeyword = STNodeFactory.createEmptyNode();
        return parseVariableDecl(getAnnotations(annots), finalKeyword);
    }

    /**
     * Parse error binding pattern node.
     * <p>
     * <code>error-binding-pattern := error [error-type-reference] ( error-arg-list-binding-pattern )</code>
     * <br/><br/>
     * error-arg-list-binding-pattern :=
     * error-message-binding-pattern [, error-cause-binding-pattern] [, error-field-binding-patterns]
     * | [error-field-binding-patterns]
     * <br/><br/>
     * error-message-binding-pattern := simple-binding-pattern
     * <br/><br/>
     * error-cause-binding-pattern := simple-binding-pattern | error-binding-pattern
     * <br/><br/>
     * simple-binding-pattern := capture-binding-pattern | wildcard-binding-pattern
     * <br/><br/>
     * error-field-binding-patterns :=
     * named-arg-binding-pattern (, named-arg-binding-pattern)* [, rest-binding-pattern]
     * | rest-binding-pattern
     * <br/><br/>
     * named-arg-binding-pattern := arg-name = binding-pattern
     *
     * @return Error binding pattern node.
     */
    private STNode parseErrorBindingPattern() {
        startContext(ParserRuleContext.ERROR_BINDING_PATTERN);
        STNode errorKeyword = parseErrorKeyword();
        return parseErrorBindingPattern(errorKeyword);
    }

    private STNode parseErrorBindingPattern(STNode errorKeyword) {
        STToken nextToken = peek();
        STNode typeRef;
        switch (nextToken.kind) {
            case OPEN_PAREN_TOKEN:
                typeRef = STNodeFactory.createEmptyNode();
                break;
            default:
                if (isPredeclaredIdentifier(nextToken.kind)) {
                    typeRef = parseTypeReference();
                    break;
                }
                recover(peek(), ParserRuleContext.ERROR_BINDING_PATTERN_ERROR_KEYWORD_RHS);
                return parseErrorBindingPattern(errorKeyword);
        }
        return parseErrorBindingPattern(errorKeyword, typeRef);
    }

    private STNode parseErrorBindingPattern(STNode errorKeyword, STNode typeRef) {
        STNode openParenthesis = parseOpenParenthesis();
        STNode argListBindingPatterns = parseErrorArgListBindingPatterns();
        STNode closeParenthesis = parseCloseParenthesis();
        endContext();
        return STNodeFactory.createErrorBindingPatternNode(errorKeyword, typeRef, openParenthesis,
                argListBindingPatterns, closeParenthesis);
    }

    /**
     * Parse error arg list binding pattern.
     * <p>
     * <code>
     * error-arg-list-binding-pattern :=
     * error-message-binding-pattern [, error-cause-binding-pattern] [, error-field-binding-patterns]
     * | [error-field-binding-patterns]
     * <br/><br/>
     * <p>
     * error-message-binding-pattern := simple-binding-pattern
     * <br/><br/>
     * <p>
     * error-cause-binding-pattern := simple-binding-pattern | error-binding-pattern
     * <br/><br/>
     * <p>
     * simple-binding-pattern := capture-binding-pattern | wildcard-binding-pattern
     * <br/><br/>
     * <p>
     * error-field-binding-patterns :=
     * named-arg-binding-pattern (, named-arg-binding-pattern)* [, rest-binding-pattern]
     * | rest-binding-pattern
     * <br/><br/>
     * <p>
     * named-arg-binding-pattern := arg-name = binding-pattern
     * </code>
     *
     * @return Error arg list binding patterns.
     */
    private STNode parseErrorArgListBindingPatterns() {
        List<STNode> argListBindingPatterns = new ArrayList<>();
        if (isEndOfErrorFieldBindingPatterns()) {
            return STNodeFactory.createNodeList(argListBindingPatterns);
        }
        return parseErrorArgListBindingPatterns(argListBindingPatterns);
    }

    private STNode parseErrorArgListBindingPatterns(List<STNode> argListBindingPatterns) {
        STNode firstArg = parseErrorArgListBindingPattern(ParserRuleContext.ERROR_ARG_LIST_BINDING_PATTERN_START, true);
        if (firstArg == null) {
            
            return STNodeFactory.createNodeList(argListBindingPatterns);
        }

        switch (firstArg.kind) {
            case CAPTURE_BINDING_PATTERN:
            case WILDCARD_BINDING_PATTERN:
                argListBindingPatterns.add(firstArg);
                return parseErrorArgListBPWithoutErrorMsg(argListBindingPatterns);
            case ERROR_BINDING_PATTERN:
                STNode missingIdentifier = SyntaxErrors.createMissingToken(SyntaxKind.IDENTIFIER_TOKEN);
                STNode missingErrorMsgBP = STNodeFactory.createCaptureBindingPatternNode(missingIdentifier);
                missingErrorMsgBP = SyntaxErrors.addDiagnostic(missingErrorMsgBP,
                        DiagnosticErrorCode.ERROR_MISSING_ERROR_MESSAGE_BINDING_PATTERN);
                STNode missingComma = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.COMMA_TOKEN,
                        DiagnosticErrorCode.ERROR_MISSING_COMMA_TOKEN);
                argListBindingPatterns.add(missingErrorMsgBP);
                argListBindingPatterns.add(missingComma);
                argListBindingPatterns.add(firstArg);
                return parseErrorArgListBPWithoutErrorMsgAndCause(argListBindingPatterns, firstArg.kind);
            case REST_BINDING_PATTERN:
            case NAMED_ARG_BINDING_PATTERN:
                argListBindingPatterns.add(firstArg);
                return parseErrorArgListBPWithoutErrorMsgAndCause(argListBindingPatterns, firstArg.kind);
            default:
                
                
                addInvalidNodeToNextToken(firstArg, DiagnosticErrorCode.ERROR_BINDING_PATTERN_NOT_ALLOWED);
                return parseErrorArgListBindingPatterns(argListBindingPatterns);
        }
    }

    private STNode parseErrorArgListBPWithoutErrorMsg(List<STNode> argListBindingPatterns) {
        STNode argEnd = parseErrorArgsBindingPatternEnd(ParserRuleContext.ERROR_MESSAGE_BINDING_PATTERN_END);
        if (argEnd == null) {
            
            return STNodeFactory.createNodeList(argListBindingPatterns);
        }

        STNode secondArg = parseErrorArgListBindingPattern(ParserRuleContext.ERROR_MESSAGE_BINDING_PATTERN_RHS, false);
        assert secondArg != null; 
        switch (secondArg.kind) {
            case CAPTURE_BINDING_PATTERN:
            case WILDCARD_BINDING_PATTERN:
            case ERROR_BINDING_PATTERN:
            case REST_BINDING_PATTERN:
            case NAMED_ARG_BINDING_PATTERN:
                argListBindingPatterns.add(argEnd);
                argListBindingPatterns.add(secondArg);
                return parseErrorArgListBPWithoutErrorMsgAndCause(argListBindingPatterns, secondArg.kind);
            default:
                
                
                updateLastNodeInListWithInvalidNode(argListBindingPatterns, argEnd, null);
                updateLastNodeInListWithInvalidNode(argListBindingPatterns, secondArg,
                        DiagnosticErrorCode.ERROR_BINDING_PATTERN_NOT_ALLOWED);
                return parseErrorArgListBPWithoutErrorMsg(argListBindingPatterns);
        }
    }

    private STNode parseErrorArgListBPWithoutErrorMsgAndCause(List<STNode> argListBindingPatterns,
                                                              SyntaxKind lastValidArgKind) {
        while (!isEndOfErrorFieldBindingPatterns()) {
            STNode argEnd = parseErrorArgsBindingPatternEnd(ParserRuleContext.ERROR_FIELD_BINDING_PATTERN_END);
            if (argEnd == null) {
                
                break;
            }
            STNode currentArg = parseErrorArgListBindingPattern(ParserRuleContext.ERROR_FIELD_BINDING_PATTERN, false);
            assert currentArg != null; 
            DiagnosticErrorCode errorCode = validateErrorFieldBindingPatternOrder(lastValidArgKind, currentArg.kind);
            if (errorCode == null) {
                argListBindingPatterns.add(argEnd);
                argListBindingPatterns.add(currentArg);
                lastValidArgKind = currentArg.kind;
            } else if (argListBindingPatterns.size() == 0) {
                addInvalidNodeToNextToken(argEnd, null);
                addInvalidNodeToNextToken(currentArg, errorCode);
            } else {
                updateLastNodeInListWithInvalidNode(argListBindingPatterns, argEnd, null);
                updateLastNodeInListWithInvalidNode(argListBindingPatterns, currentArg, errorCode);
            }
        }

        return STNodeFactory.createNodeList(argListBindingPatterns);
    }

    private boolean isEndOfErrorFieldBindingPatterns() {
        SyntaxKind nextTokenKind = peek().kind;
        switch (nextTokenKind) {
            case CLOSE_PAREN_TOKEN:
            case EOF_TOKEN:
                return true;
            default:
                return false;
        }
    }

    private STNode parseErrorArgsBindingPatternEnd(ParserRuleContext currentCtx) {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return consume();
            case CLOSE_PAREN_TOKEN:
                return null;
            default:
                recover(peek(), currentCtx);
                return parseErrorArgsBindingPatternEnd(currentCtx);
        }
    }

    private STNode parseErrorArgListBindingPattern(ParserRuleContext context, boolean isFirstArg) {
        switch (peek().kind) {
            case ELLIPSIS_TOKEN:
                return parseRestBindingPattern();
            case IDENTIFIER_TOKEN:
                
                STNode argNameOrSimpleBindingPattern = consume();
                return parseNamedOrSimpleArgBindingPattern(argNameOrSimpleBindingPattern);
            case OPEN_BRACKET_TOKEN:
            case OPEN_BRACE_TOKEN:
            case ERROR_KEYWORD:
                return parseBindingPattern();
            case CLOSE_PAREN_TOKEN:
                if (isFirstArg) {
                    
                    return null;
                }
                
            default:
                recover(peek(), context);
                return parseErrorArgListBindingPattern(context, isFirstArg);
        }
    }

    private STNode parseNamedOrSimpleArgBindingPattern(STNode argNameOrSimpleBindingPattern) {
        STToken secondToken = peek();
        switch (secondToken.kind) {
            case EQUAL_TOKEN:
                STNode equal = consume();
                STNode bindingPattern = parseBindingPattern();
                return STNodeFactory.createNamedArgBindingPatternNode(argNameOrSimpleBindingPattern,
                        equal, bindingPattern);
            case COMMA_TOKEN:
            case CLOSE_PAREN_TOKEN:
            default:
                return createCaptureOrWildcardBP(argNameOrSimpleBindingPattern);
        }
    }

    private DiagnosticErrorCode validateErrorFieldBindingPatternOrder(SyntaxKind prevArgKind,
                                                                      SyntaxKind currentArgKind) {
        switch (currentArgKind) {
            case NAMED_ARG_BINDING_PATTERN:
            case REST_BINDING_PATTERN:
                
                if (prevArgKind == SyntaxKind.REST_BINDING_PATTERN) {
                    return DiagnosticErrorCode.ERROR_REST_ARG_FOLLOWED_BY_ANOTHER_ARG;
                }
                return null;
            case CAPTURE_BINDING_PATTERN:
            case WILDCARD_BINDING_PATTERN:
            case ERROR_BINDING_PATTERN:
            case LIST_BINDING_PATTERN:
            case MAPPING_BINDING_PATTERN:
            default:
                return DiagnosticErrorCode.ERROR_BINDING_PATTERN_NOT_ALLOWED;
        }
    }

    

    /*
     * This parses Typed binding patterns and deals with ambiguity between types,
     * and binding patterns. An example is 'T[a]'.
     * The ambiguity lies in between:
     * 1) Array Type
     * 2) List binding pattern
     * 3) Member access expression.
     */

    /**
     * Parse the component after the type-desc, of a typed-binding-pattern.
     *
     * @param typeDesc Starting type-desc of the typed-binding-pattern
     * @return Typed-binding pattern
     */
    private STNode parseTypedBindingPatternTypeRhs(STNode typeDesc, ParserRuleContext context) {
        return parseTypedBindingPatternTypeRhs(typeDesc, context, true);
    }

    private STNode parseTypedBindingPatternTypeRhs(STNode typeDesc, ParserRuleContext context, boolean isRoot) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN: 
            case OPEN_BRACE_TOKEN: 
            case ERROR_KEYWORD: 
                STNode bindingPattern = parseBindingPattern();
                return STNodeFactory.createTypedBindingPatternNode(typeDesc, bindingPattern);
            case OPEN_BRACKET_TOKEN:
                
                STNode typedBindingPattern = parseTypedBindingPatternOrMemberAccess(typeDesc, true, true, context);
                assert typedBindingPattern.kind == SyntaxKind.TYPED_BINDING_PATTERN;
                return typedBindingPattern;
            case CLOSE_PAREN_TOKEN:
            case COMMA_TOKEN:
            case CLOSE_BRACKET_TOKEN:
            case CLOSE_BRACE_TOKEN:
                if (!isRoot) {
                    return typeDesc;
                }
                
            default:
                recover(nextToken, ParserRuleContext.TYPED_BINDING_PATTERN_TYPE_RHS);
                return parseTypedBindingPatternTypeRhs(typeDesc, context, isRoot);
        }
    }

    /**
     * Parse typed-binding pattern with list, array-type-desc, or member-access-expr.
     *
     * @param typeDescOrExpr        Type desc or the expression at the start
     * @param isTypedBindingPattern Is this is a typed-binding-pattern. If this is `false`, then it's still ambiguous
     * @return Parsed node
     */
    private STNode parseTypedBindingPatternOrMemberAccess(STNode typeDescOrExpr, boolean isTypedBindingPattern,
                                                          boolean allowAssignment, ParserRuleContext context) {
        startContext(ParserRuleContext.BRACKETED_LIST);
        STNode openBracket = parseOpenBracket();

        
        if (isBracketedListEnd(peek().kind)) {
            return parseAsArrayTypeDesc(typeDescOrExpr, openBracket, STNodeFactory.createEmptyNode(), context);
        }

        
        STNode member = parseBracketedListMember(isTypedBindingPattern);
        SyntaxKind currentNodeType = getBracketedListNodeType(member, isTypedBindingPattern);
        switch (currentNodeType) {
            case ARRAY_TYPE_DESC:
                STNode typedBindingPattern = parseAsArrayTypeDesc(typeDescOrExpr, openBracket, member, context);
                return typedBindingPattern;
            case LIST_BINDING_PATTERN:
                
                
                STNode bindingPattern = parseAsListBindingPattern(openBracket, new ArrayList<>(), member, false);
                STNode typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                return STNodeFactory.createTypedBindingPatternNode(typeDesc, bindingPattern);
            case INDEXED_EXPRESSION:
                return parseAsMemberAccessExpr(typeDescOrExpr, openBracket, member);
            case ARRAY_TYPE_DESC_OR_MEMBER_ACCESS:
                break;
            case NONE:
            default:
                
                

                
                STNode memberEnd = parseBracketedListMemberEnd();
                if (memberEnd != null) {
                    
                    List<STNode> memberList = new ArrayList<>();
                    memberList.add(getBindingPattern(member, true));
                    memberList.add(memberEnd);
                    bindingPattern = parseAsListBindingPattern(openBracket, memberList);
                    typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                    return STNodeFactory.createTypedBindingPatternNode(typeDesc, bindingPattern);
                }
        }

        
        
        
        
        
        STNode closeBracket = parseCloseBracket();
        endContext();
        return parseTypedBindingPatternOrMemberAccessRhs(typeDescOrExpr, openBracket, member, closeBracket,
                isTypedBindingPattern, allowAssignment, context);
    }

    private STNode parseAsMemberAccessExpr(STNode typeNameOrExpr, STNode openBracket, STNode member) {
        member = parseExpressionRhs(DEFAULT_OP_PRECEDENCE, member, false, true);
        STNode closeBracket = parseCloseBracket();
        endContext();
        STNode keyExpr = STNodeFactory.createNodeList(member);
        STNode memberAccessExpr =
                STNodeFactory.createIndexedExpressionNode(typeNameOrExpr, openBracket, keyExpr, closeBracket);
        return parseExpressionRhs(DEFAULT_OP_PRECEDENCE, memberAccessExpr, false, false);
    }

    private boolean isBracketedListEnd(SyntaxKind nextTokenKind) {
        switch (nextTokenKind) {
            case EOF_TOKEN:
            case CLOSE_BRACKET_TOKEN:
                return true;
            default:
                return false;
        }
    }

    /**
     * Parse a member of an ambiguous bracketed list. This member could be:
     * 1) Array length
     * 2) Key expression of a member-access-expr
     * 3) A member-binding pattern of a list-binding-pattern.
     *
     * @param isTypedBindingPattern Is this in a definite typed-binding pattern
     * @return Parsed member node
     */
    private STNode parseBracketedListMember(boolean isTypedBindingPattern) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case DECIMAL_INTEGER_LITERAL_TOKEN:
            case HEX_INTEGER_LITERAL_TOKEN:
            case ASTERISK_TOKEN:
            case STRING_LITERAL_TOKEN:
                return parseBasicLiteral();
            case CLOSE_BRACKET_TOKEN:
                return STNodeFactory.createEmptyNode();
            case OPEN_BRACE_TOKEN:
            case ERROR_KEYWORD: 
            case ELLIPSIS_TOKEN: 
            case OPEN_BRACKET_TOKEN: 
                return parseStatementStartBracketedListMember();
            case IDENTIFIER_TOKEN:
                if (isTypedBindingPattern) {
                    return parseQualifiedIdentifier(ParserRuleContext.VARIABLE_REF);
                }
                break;
            default:
                if ((!isTypedBindingPattern && isValidExpressionStart(nextToken.kind, 1)) ||
                        isQualifiedIdentifierPredeclaredPrefix(nextToken.kind)) {
                    break;
                }

                ParserRuleContext recoverContext =
                        isTypedBindingPattern ? ParserRuleContext.LIST_BINDING_MEMBER_OR_ARRAY_LENGTH
                                : ParserRuleContext.BRACKETED_LIST_MEMBER;
                recover(peek(), recoverContext);
                return parseBracketedListMember(isTypedBindingPattern);
        }

        STNode expr = parseExpression();
        if (isWildcardBP(expr)) {
            return getWildcardBindingPattern(expr);
        }

        
        return expr;
    }

    /**
     * Treat the current node as an array, and parse the remainder of the binding pattern.
     *
     * @param typeDesc    Type-desc
     * @param openBracket Open bracket
     * @param member      Member
     * @return Parsed node
     */
    private STNode parseAsArrayTypeDesc(STNode typeDesc, STNode openBracket, STNode member, ParserRuleContext context) {
        
        
        typeDesc = getTypeDescFromExpr(typeDesc);
        switchContext(ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN);
        startContext(ParserRuleContext.ARRAY_TYPE_DESCRIPTOR);
        STNode closeBracket = parseCloseBracket();
        endContext();
        endContext();
        return parseTypedBindingPatternOrMemberAccessRhs(typeDesc, openBracket, member, closeBracket, true, true,
                context);
    }

    private STNode parseBracketedListMemberEnd() {
        switch (peek().kind) {
            case COMMA_TOKEN:
                return parseComma();
            case CLOSE_BRACKET_TOKEN:
                return null;
            default:
                recover(peek(), ParserRuleContext.BRACKETED_LIST_MEMBER_END);
                return parseBracketedListMemberEnd();
        }
    }

    /**
     * We reach here to break ambiguity of T[a]. This could be:
     * 1) Array Type Desc
     * 2) Member access on LHS
     * 3) Typed-binding-pattern
     *
     * @param typeDescOrExpr        Type name or the expr that precede the open-bracket.
     * @param openBracket           Open bracket
     * @param member                Member
     * @param closeBracket          Open bracket
     * @param isTypedBindingPattern Is this is a typed-binding-pattern.
     * @return Specific node that matches to T[a], after solving ambiguity.
     */
    private STNode parseTypedBindingPatternOrMemberAccessRhs(STNode typeDescOrExpr, STNode openBracket, STNode member,
                                                             STNode closeBracket, boolean isTypedBindingPattern,
                                                             boolean allowAssignment, ParserRuleContext context) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case IDENTIFIER_TOKEN: 
            case OPEN_BRACE_TOKEN: 
            case ERROR_KEYWORD: 
                
                STNode typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                STNode arrayTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, typeDesc);
                return parseTypedBindingPatternTypeRhs(arrayTypeDesc, context);
            case OPEN_BRACKET_TOKEN: 
                if (isTypedBindingPattern) {
                    typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                    arrayTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, typeDesc);
                    return parseTypedBindingPatternTypeRhs(arrayTypeDesc, context);
                }

                
                STNode keyExpr = getKeyExpr(member);
                STNode expr =
                        STNodeFactory.createIndexedExpressionNode(typeDescOrExpr, openBracket, keyExpr, closeBracket);
                return parseTypedBindingPatternOrMemberAccess(expr, false, allowAssignment, context);
            case QUESTION_MARK_TOKEN:
                
                typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                arrayTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, typeDesc);
                typeDesc = parseComplexTypeDescriptor(arrayTypeDesc,
                        ParserRuleContext.TYPE_DESC_IN_TYPE_BINDING_PATTERN, true);
                return parseTypedBindingPatternTypeRhs(typeDesc, context);
            case PIPE_TOKEN:
            case BITWISE_AND_TOKEN:
                
                return parseComplexTypeDescInTypedBPOrExprRhs(typeDescOrExpr, openBracket, member, closeBracket,
                        isTypedBindingPattern);
            case IN_KEYWORD:
                
                if (context != ParserRuleContext.FOREACH_STMT &&
                        context != ParserRuleContext.FROM_CLAUSE &&
                        context != ParserRuleContext.JOIN_CLAUSE) {
                    break;
                }
                return createTypedBindingPattern(typeDescOrExpr, openBracket, member, closeBracket);
            case EQUAL_TOKEN: 
                if (context == ParserRuleContext.FOREACH_STMT || context == ParserRuleContext.FROM_CLAUSE) {
                    
                    
                    break;
                }

                
                if (isTypedBindingPattern || !allowAssignment || !isValidLVExpr(typeDescOrExpr)) {
                    return createTypedBindingPattern(typeDescOrExpr, openBracket, member, closeBracket);
                }

                keyExpr = getKeyExpr(member);
                typeDescOrExpr = getExpression(typeDescOrExpr);
                return STNodeFactory.createIndexedExpressionNode(typeDescOrExpr, openBracket, keyExpr, closeBracket);
            case SEMICOLON_TOKEN: 
                if (context == ParserRuleContext.FOREACH_STMT || context == ParserRuleContext.FROM_CLAUSE) {
                    
                    
                    break;
                }

                return createTypedBindingPattern(typeDescOrExpr, openBracket, member, closeBracket);
            case CLOSE_BRACE_TOKEN: 
            case COMMA_TOKEN:
                if (context == ParserRuleContext.AMBIGUOUS_STMT) {
                    keyExpr = getKeyExpr(member);
                    return STNodeFactory.createIndexedExpressionNode(typeDescOrExpr, openBracket, keyExpr,
                            closeBracket);
                }
                
            default:
                if (!isTypedBindingPattern && isValidExprRhsStart(nextToken.kind, closeBracket.kind)) {
                    
                    keyExpr = getKeyExpr(member);
                    typeDescOrExpr = getExpression(typeDescOrExpr);
                    return STNodeFactory.createIndexedExpressionNode(typeDescOrExpr, openBracket, keyExpr,
                            closeBracket);
                }

                break;
        }

        ParserRuleContext recoveryCtx = ParserRuleContext.BRACKETED_LIST_RHS;
        if (isTypedBindingPattern) {
            recoveryCtx = ParserRuleContext.TYPE_DESC_RHS_OR_BP_RHS;
        }

        recover(peek(), recoveryCtx);
        return parseTypedBindingPatternOrMemberAccessRhs(typeDescOrExpr, openBracket, member, closeBracket,
                isTypedBindingPattern, allowAssignment, context);
    }

    private STNode getKeyExpr(STNode member) {
        if (member == null) {
            STToken keyIdentifier = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                    DiagnosticErrorCode.ERROR_MISSING_KEY_EXPR_IN_MEMBER_ACCESS_EXPR);
            STNode missingVarRef = STNodeFactory.createSimpleNameReferenceNode(keyIdentifier);

            return STNodeFactory.createNodeList(missingVarRef);
        }
        return STNodeFactory.createNodeList(member);
    }

    private STNode createTypedBindingPattern(STNode typeDescOrExpr, STNode openBracket, STNode member,
                                             STNode closeBracket) {
        STNode bindingPatterns = STNodeFactory.createEmptyNodeList();
        if (!isEmpty(member)) {
            SyntaxKind memberKind = member.kind;
            if (memberKind == SyntaxKind.NUMERIC_LITERAL || memberKind == SyntaxKind.ASTERISK_LITERAL) {
                STNode typeDesc = getTypeDescFromExpr(typeDescOrExpr);
                STNode arrayTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, typeDesc);
                STToken identifierToken = SyntaxErrors.createMissingTokenWithDiagnostics(SyntaxKind.IDENTIFIER_TOKEN,
                        DiagnosticErrorCode.ERROR_MISSING_VARIABLE_NAME);
                STNode variableName = STNodeFactory.createCaptureBindingPatternNode(identifierToken);
                return STNodeFactory.createTypedBindingPatternNode(arrayTypeDesc, variableName);
            }

            STNode bindingPattern = getBindingPattern(member, true);
            bindingPatterns = STNodeFactory.createNodeList(bindingPattern);
        }

        STNode bindingPattern = STNodeFactory.createListBindingPatternNode(openBracket, bindingPatterns, closeBracket);
        STNode typeDesc = getTypeDescFromExpr(typeDescOrExpr);
        return STNodeFactory.createTypedBindingPatternNode(typeDesc, bindingPattern);
    }

    /**
     * Parse a union or intersection type-desc/binary-expression that involves ambiguous
     * bracketed list in lhs.
     * <p>
     * e.g: <code>(T[a] & R..)</code> or <code>(T[a] | R.. )</code>
     * <p>
     * Complexity occurs in scenarios such as <code>T[a] |/& R[b]</code>. If the token after this
     * is another binding-pattern, then <code>(T[a] |/& R[b])</code> becomes the type-desc. However,
     * if the token follows this is an equal or semicolon, then <code>(T[a] |/& R)</code> becomes
     * the type-desc, and <code>[b]</code> becomes the binding pattern.
     *
     * @param typeDescOrExpr Type desc or the expression
     * @param openBracket    Open bracket
     * @param member         Member
     * @param closeBracket   Close bracket
     * @return Parsed node
     */
    private STNode parseComplexTypeDescInTypedBPOrExprRhs(STNode typeDescOrExpr, STNode openBracket, STNode member,
                                                             STNode closeBracket, boolean isTypedBindingPattern) {
        STNode pipeOrAndToken = parseUnionOrIntersectionToken();
        STNode typedBindingPatternOrExpr = parseTypedBindingPatternOrExpr(false);

        if (typedBindingPatternOrExpr.kind == SyntaxKind.TYPED_BINDING_PATTERN) {
            
            
            STNode lhsTypeDesc = getTypeDescFromExpr(typeDescOrExpr);
            lhsTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, lhsTypeDesc);

            STTypedBindingPatternNode rhsTypedBindingPattern = (STTypedBindingPatternNode) typedBindingPatternOrExpr;
            STNode rhsTypeDesc = rhsTypedBindingPattern.typeDescriptor;

            STNode newTypeDesc = mergeTypes(lhsTypeDesc, pipeOrAndToken, rhsTypeDesc);
            return STNodeFactory.createTypedBindingPatternNode(newTypeDesc, rhsTypedBindingPattern.bindingPattern);
        }

        if (isTypedBindingPattern) {
            
            STNode lhsTypeDesc = getTypeDescFromExpr(typeDescOrExpr);
            lhsTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, lhsTypeDesc);
            return createCaptureBPWithMissingVarName(lhsTypeDesc, pipeOrAndToken, typedBindingPatternOrExpr);
        }

        STNode keyExpr = getExpression(member);
        STNode containerExpr = getExpression(typeDescOrExpr);
        STNode lhsExpr =
                STNodeFactory.createIndexedExpressionNode(containerExpr, openBracket, keyExpr, closeBracket);
        return STNodeFactory.createBinaryExpressionNode(SyntaxKind.BINARY_EXPRESSION, lhsExpr, pipeOrAndToken,
                typedBindingPatternOrExpr);

    }

    /**
     * Merges two types separated by <code>|</code> or <code>&</code> into one type, while taking precedence
     * and associativity into account.
     *
     * @param lhsTypeDesc    lhs type
     * @param pipeOrAndToken pipe or bitwise-and token
     * @param rhsTypeDesc    rhs type
     * @return a TypeDescriptorNode
     */
    private STNode mergeTypes(STNode lhsTypeDesc, STNode pipeOrAndToken, STNode rhsTypeDesc) {
        if (pipeOrAndToken.kind == SyntaxKind.PIPE_TOKEN) {
            return mergeTypesWithUnion(lhsTypeDesc, pipeOrAndToken, rhsTypeDesc);
        } else {
            return mergeTypesWithIntersection(lhsTypeDesc, pipeOrAndToken, rhsTypeDesc);
        }
    }

    /**
     * Merges two types separated by <code>|</code> into one type, while taking precedence
     * and associativity into account.
     *
     * @param lhsTypeDesc lhs type
     * @param pipeToken   pipe token
     * @param rhsTypeDesc rhs type
     * @return a TypeDescriptorNode
     */
    private STNode mergeTypesWithUnion(STNode lhsTypeDesc, STNode pipeToken, STNode rhsTypeDesc) {
        if (rhsTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            
            
            

            STUnionTypeDescriptorNode rhsUnionTypeDesc = (STUnionTypeDescriptorNode) rhsTypeDesc;
            return replaceLeftMostUnionWithAUnion(lhsTypeDesc, pipeToken, rhsUnionTypeDesc);
        } else {
            return createUnionTypeDesc(lhsTypeDesc, pipeToken, rhsTypeDesc);
        }
    }

    /**
     * Merges two types separated by <code>&</code> into one type, while taking precedence
     * and associativity into account.
     *
     * @param lhsTypeDesc     lhs type
     * @param bitwiseAndToken bitwise-and token
     * @param rhsTypeDesc     rhs type
     * @return a TypeDescriptorNode
     */
    private STNode mergeTypesWithIntersection(STNode lhsTypeDesc, STNode bitwiseAndToken, STNode rhsTypeDesc) {
        if (lhsTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            
            STUnionTypeDescriptorNode lhsUnionTypeDesc = (STUnionTypeDescriptorNode) lhsTypeDesc;
            if (rhsTypeDesc.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
                rhsTypeDesc = replaceLeftMostIntersectionWithAIntersection(lhsUnionTypeDesc.rightTypeDesc,
                        bitwiseAndToken, (STIntersectionTypeDescriptorNode) rhsTypeDesc);
                return createUnionTypeDesc(lhsUnionTypeDesc.leftTypeDesc, lhsUnionTypeDesc.pipeToken, rhsTypeDesc);
            } else if (rhsTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
                rhsTypeDesc = replaceLeftMostUnionWithAIntersection(lhsUnionTypeDesc.rightTypeDesc,
                        bitwiseAndToken, (STUnionTypeDescriptorNode) rhsTypeDesc);
                return replaceLeftMostUnionWithAUnion(lhsUnionTypeDesc.leftTypeDesc,
                        lhsUnionTypeDesc.pipeToken, (STUnionTypeDescriptorNode) rhsTypeDesc);
            } else {
                rhsTypeDesc = createIntersectionTypeDesc(lhsUnionTypeDesc.rightTypeDesc, bitwiseAndToken, rhsTypeDesc);
                return createUnionTypeDesc(lhsUnionTypeDesc.leftTypeDesc, lhsUnionTypeDesc.pipeToken, rhsTypeDesc);
            }
        }

        if (rhsTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            
            
            

            STUnionTypeDescriptorNode rhsUnionTypeDesc = (STUnionTypeDescriptorNode) rhsTypeDesc;
            return replaceLeftMostUnionWithAIntersection(lhsTypeDesc, bitwiseAndToken, rhsUnionTypeDesc);
        } else if (rhsTypeDesc.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
            
            
            

            STIntersectionTypeDescriptorNode rhsIntSecTypeDesc = (STIntersectionTypeDescriptorNode) rhsTypeDesc;
            return replaceLeftMostIntersectionWithAIntersection(lhsTypeDesc, bitwiseAndToken, rhsIntSecTypeDesc);
        } else {
            return createIntersectionTypeDesc(lhsTypeDesc, bitwiseAndToken, rhsTypeDesc);
        }
    }

    private STNode replaceLeftMostUnionWithAUnion(STNode typeDesc, STNode pipeToken,
                                                  STUnionTypeDescriptorNode unionTypeDesc) {
        STNode leftTypeDesc = unionTypeDesc.leftTypeDesc;

        
        
        if (leftTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            return unionTypeDesc.replace(unionTypeDesc.leftTypeDesc,
                    replaceLeftMostUnionWithAUnion(typeDesc, pipeToken, (STUnionTypeDescriptorNode) leftTypeDesc));
        }

        
        
        leftTypeDesc = createUnionTypeDesc(typeDesc, pipeToken, leftTypeDesc);
        return unionTypeDesc.replace(unionTypeDesc.leftTypeDesc, leftTypeDesc);
    }
    
    private STNode replaceLeftMostUnionWithAIntersection(STNode typeDesc, STNode bitwiseAndToken,
                                                         STUnionTypeDescriptorNode unionTypeDesc) {
        STNode leftTypeDesc = unionTypeDesc.leftTypeDesc;
        
        
        
        if (leftTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            return unionTypeDesc.replace(unionTypeDesc.leftTypeDesc,
                    replaceLeftMostUnionWithAIntersection(typeDesc, bitwiseAndToken,
                            (STUnionTypeDescriptorNode) leftTypeDesc));
        }

        
        
        if (leftTypeDesc.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
            return unionTypeDesc.replace(unionTypeDesc.leftTypeDesc, 
                    replaceLeftMostIntersectionWithAIntersection(typeDesc, bitwiseAndToken,
                            (STIntersectionTypeDescriptorNode) leftTypeDesc));
        }

        
        
        leftTypeDesc = createIntersectionTypeDesc(typeDesc, bitwiseAndToken, leftTypeDesc);
        return unionTypeDesc.replace(unionTypeDesc.leftTypeDesc, leftTypeDesc);
    }

    private STNode replaceLeftMostIntersectionWithAIntersection(STNode typeDesc,
                                                                STNode bitwiseAndToken,
                                                                STIntersectionTypeDescriptorNode intersectionTypeDesc) {
        STNode leftTypeDesc = intersectionTypeDesc.leftTypeDesc;

        
        
        if (leftTypeDesc.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
            return intersectionTypeDesc.replace(intersectionTypeDesc.leftTypeDesc,
                    replaceLeftMostIntersectionWithAIntersection(typeDesc, bitwiseAndToken,
                            (STIntersectionTypeDescriptorNode) leftTypeDesc));
        }

        
        
        leftTypeDesc = createIntersectionTypeDesc(typeDesc, bitwiseAndToken, leftTypeDesc);
        return intersectionTypeDesc.replace(intersectionTypeDesc.leftTypeDesc, leftTypeDesc);
    }
    
    private STNode getArrayTypeDesc(STNode openBracket, STNode member, STNode closeBracket, STNode lhsTypeDesc) {
        if (lhsTypeDesc.kind == SyntaxKind.UNION_TYPE_DESC) {
            STUnionTypeDescriptorNode unionTypeDesc = (STUnionTypeDescriptorNode) lhsTypeDesc;
            STNode middleTypeDesc = getArrayTypeDesc(openBracket, member, closeBracket, unionTypeDesc.rightTypeDesc);
            lhsTypeDesc = mergeTypesWithUnion(unionTypeDesc.leftTypeDesc, unionTypeDesc.pipeToken, middleTypeDesc);
        } else if (lhsTypeDesc.kind == SyntaxKind.INTERSECTION_TYPE_DESC) {
            STIntersectionTypeDescriptorNode intersectionTypeDesc = (STIntersectionTypeDescriptorNode) lhsTypeDesc;
            STNode middleTypeDesc =
                    getArrayTypeDesc(openBracket, member, closeBracket, intersectionTypeDesc.rightTypeDesc);
            lhsTypeDesc = mergeTypesWithIntersection(intersectionTypeDesc.leftTypeDesc,
                    intersectionTypeDesc.bitwiseAndToken, middleTypeDesc);
        } else {
            lhsTypeDesc = createArrayTypeDesc(lhsTypeDesc, openBracket, member, closeBracket);
        }

        return lhsTypeDesc;
    }

    /**
     * Parse union (|) or intersection (&) type operator.
     *
     * @return pipe or bitwise and token
     */
    private STNode parseUnionOrIntersectionToken() {
        STToken token = peek();
        if (token.kind == SyntaxKind.PIPE_TOKEN || token.kind == SyntaxKind.BITWISE_AND_TOKEN) {
            return consume();
        } else {
            recover(token, ParserRuleContext.UNION_OR_INTERSECTION_TOKEN);
            return parseUnionOrIntersectionToken();
        }
    }

    /**
     * Infer the type of the ambiguous bracketed list, based on the type of the member.
     *
     * @param memberNode Member node
     * @return Inferred type of the bracketed list
     */
    private SyntaxKind getBracketedListNodeType(STNode memberNode, boolean isTypedBindingPattern) {
        if (isEmpty(memberNode)) {
            
            return SyntaxKind.NONE;
        }

        if (isDefiniteTypeDesc(memberNode.kind)) {
            return SyntaxKind.TUPLE_TYPE_DESC;
        }

        switch (memberNode.kind) {
            case ASTERISK_LITERAL:
                return SyntaxKind.ARRAY_TYPE_DESC;
            case CAPTURE_BINDING_PATTERN:
            case LIST_BINDING_PATTERN:
            case REST_BINDING_PATTERN:
            case MAPPING_BINDING_PATTERN:
            case WILDCARD_BINDING_PATTERN:
                return SyntaxKind.LIST_BINDING_PATTERN;
            case QUALIFIED_NAME_REFERENCE: 
            case REST_TYPE:
                return SyntaxKind.TUPLE_TYPE_DESC;
            case NUMERIC_LITERAL: 
                if (isTypedBindingPattern) {
                    return SyntaxKind.ARRAY_TYPE_DESC;
                }
                return SyntaxKind.ARRAY_TYPE_DESC_OR_MEMBER_ACCESS;
            case SIMPLE_NAME_REFERENCE: 
            case BRACKETED_LIST: 
            case MAPPING_BP_OR_MAPPING_CONSTRUCTOR:
                return SyntaxKind.NONE;
            case ERROR_CONSTRUCTOR:
                if (isPossibleErrorBindingPattern((STErrorConstructorExpressionNode) memberNode)) {
                    return SyntaxKind.NONE;
                }
                return SyntaxKind.INDEXED_EXPRESSION;
            default:
                if (isTypedBindingPattern) {
                    return SyntaxKind.NONE;
                }
                return SyntaxKind.INDEXED_EXPRESSION;
        }
    }

    

    /*
     * This section tries to break the ambiguity in parsing a statement that starts with a open-bracket.
     * The ambiguity lies in between:
     * 1) Assignment that starts with list binding pattern
     * 2) Var-decl statement that starts with tuple type
     * 3) Statement that starts with list constructor, such as sync-send, etc.
     */

    /**
     * Parse any statement that starts with an open-bracket.
     *
     * @param annots Annotations attached to the statement.
     * @return Parsed node
     */
    private STNode parseStatementStartsWithOpenBracket(STNode annots, boolean possibleMappingField) {
        startContext(ParserRuleContext.ASSIGNMENT_OR_VAR_DECL_STMT);
        return parseStatementStartsWithOpenBracket(annots, true, possibleMappingField);
    }

    private STNode parseMemberBracketedList() {
        STNode annots = STNodeFactory.createEmptyNodeList();
        return parseStatementStartsWithOpenBracket(annots, false, false);
    }

    /**
     * The bracketed list at the start of a statement can be one of the following.
     * 1) List binding pattern
     * 2) Tuple type
     * 3) List constructor
     *
     * @param isRoot Is this the root of the list
     * @return Parsed node
     */
    private STNode parseStatementStartsWithOpenBracket(STNode annots, boolean isRoot, boolean possibleMappingField) {
        startContext(ParserRuleContext.STMT_START_BRACKETED_LIST);
        STNode openBracket = parseOpenBracket();
        List<STNode> memberList = new ArrayList<>();
        while (!isBracketedListEnd(peek().kind)) {
            
            STNode member = parseStatementStartBracketedListMember();
            SyntaxKind currentNodeType = getStmtStartBracketedListType(member);

            switch (currentNodeType) {
                case TUPLE_TYPE_DESC:
                    member = parseComplexTypeDescriptor(member, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
                    member = createMemberOrRestNode(STNodeFactory.createEmptyNodeList(), member);
                    
                    
                    return parseAsTupleTypeDesc(annots, openBracket, memberList, member, isRoot);
                case MEMBER_TYPE_DESC:
                case REST_TYPE:
                    return parseAsTupleTypeDesc(annots, openBracket, memberList, member, isRoot);
                case LIST_BINDING_PATTERN:
                    
                    
                    return parseAsListBindingPattern(openBracket, memberList, member, isRoot);
                case LIST_CONSTRUCTOR:
                    
                    
                    return parseAsListConstructor(openBracket, memberList, member, isRoot);
                case LIST_BP_OR_LIST_CONSTRUCTOR:
                    return parseAsListBindingPatternOrListConstructor(openBracket, memberList, member, isRoot);
                case TUPLE_TYPE_DESC_OR_LIST_CONST:
                    return parseAsTupleTypeDescOrListConstructor(annots, openBracket, memberList, member, isRoot);
                case NONE:
                default:
                    memberList.add(member);
                    break;
            }

            
            STNode memberEnd = parseBracketedListMemberEnd();
            if (memberEnd == null) {
                break;
            }
            memberList.add(memberEnd);
        }

        
        STNode closeBracket = parseCloseBracket();
        STNode bracketedList = parseStatementStartBracketedListRhs(annots, openBracket, memberList, closeBracket,
                isRoot, possibleMappingField);
        return bracketedList;
    }

    /**
     * Parse a member of a list-binding-pattern, tuple-type-desc, or
     * list-constructor-expr, when the parent is ambiguous.
     *
     * @return Parsed node
     */
    private STNode parseStatementStartBracketedListMember() {
        List<STNode> typeDescQualifiers = new ArrayList<>();
        return parseStatementStartBracketedListMember(typeDescQualifiers);
    }

    private STNode parseStatementStartBracketedListMember(List<STNode> qualifiers) {
        parseTypeDescQualifiers(qualifiers);
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_BRACKET_TOKEN:
                reportInvalidQualifierList(qualifiers);
                return parseMemberBracketedList();
            case IDENTIFIER_TOKEN:
                reportInvalidQualifierList(qualifiers);
                STNode identifier = parseQualifiedIdentifier(ParserRuleContext.VARIABLE_REF);
                if (isWildcardBP(identifier)) {
                    STNode varName = ((STSimpleNameReferenceNode) identifier).name;
                    return getWildcardBindingPattern(varName);
                }

                nextToken = peek();
                if (nextToken.kind == SyntaxKind.ELLIPSIS_TOKEN) {
                    STNode ellipsis = parseEllipsis();
                    return STNodeFactory.createRestDescriptorNode(identifier, ellipsis);
                }

                if (nextToken.kind != SyntaxKind.OPEN_BRACKET_TOKEN && isValidTypeContinuationToken(nextToken)) {
                    
                    return parseComplexTypeDescriptor(identifier, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
                }

                return parseExpressionRhs(DEFAULT_OP_PRECEDENCE, identifier, false, true);
            case OPEN_BRACE_TOKEN:
                
                reportInvalidQualifierList(qualifiers);
                return parseMappingBindingPatterOrMappingConstructor();
            case ERROR_KEYWORD:
                reportInvalidQualifierList(qualifiers);
                STToken nextNextToken = getNextNextToken();
                if (nextNextToken.kind == SyntaxKind.OPEN_PAREN_TOKEN ||
                        nextNextToken.kind == SyntaxKind.IDENTIFIER_TOKEN) {
                    return parseErrorBindingPatternOrErrorConstructor();
                }
                
                return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
            case ELLIPSIS_TOKEN:
                
                reportInvalidQualifierList(qualifiers);
                return parseRestBindingOrSpreadMember();
            case XML_KEYWORD:
            case STRING_KEYWORD:
                reportInvalidQualifierList(qualifiers);
                if (getNextNextToken().kind == SyntaxKind.BACKTICK_TOKEN) {
                    return parseExpression(false);
                }
                return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
            case TABLE_KEYWORD:
            case STREAM_KEYWORD:
                reportInvalidQualifierList(qualifiers);
                if (getNextNextToken().kind == SyntaxKind.LT_TOKEN) {
                    return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
                }
                return parseExpression(false);
            case OPEN_PAREN_TOKEN:
                return parseTypeDescOrExpr(qualifiers);
            case FUNCTION_KEYWORD:
                return parseAnonFuncExprOrFuncTypeDesc(qualifiers);
            case AT_TOKEN:
                return parseTupleMember();
            default:
                if (isValidExpressionStart(nextToken.kind, 1)) {
                    reportInvalidQualifierList(qualifiers);
                    return parseExpression(false);
                }

                if (isTypeStartingToken(nextToken.kind)) {
                    return parseTypeDescriptor(qualifiers, ParserRuleContext.TYPE_DESC_IN_TUPLE);
                }

                recover(peek(), ParserRuleContext.STMT_START_BRACKETED_LIST_MEMBER);
                return parseStatementStartBracketedListMember(qualifiers);
        }
    }

    private STNode parseRestBindingOrSpreadMember() {
        STNode ellipsis = parseEllipsis();
        STNode expr = parseExpression();
        if (expr.kind == SyntaxKind.SIMPLE_NAME_REFERENCE) {
            return STNodeFactory.createRestBindingPatternNode(ellipsis, expr);
        } else {
            return STNodeFactory.createSpreadMemberNode(ellipsis, expr);
        }
    }

    private STNode parseAsTupleTypeDescOrListConstructor(STNode annots, STNode openBracket, List<STNode> memberList,
                                                         STNode member, boolean isRoot) {
        memberList.add(member);
        STNode memberEnd = parseBracketedListMemberEnd();

        STNode tupleTypeDescOrListCons;
        if (memberEnd == null) {
            
            STNode closeBracket = parseCloseBracket();
            tupleTypeDescOrListCons =
                    parseTupleTypeDescOrListConstructorRhs(openBracket, memberList, closeBracket, isRoot);
        } else {
            memberList.add(memberEnd);
            tupleTypeDescOrListCons = parseTupleTypeDescOrListConstructor(annots, openBracket, memberList, isRoot);
        }

        return tupleTypeDescOrListCons;
    }

    /**
     * Parse tuple type desc or list constructor.
     *
     * @return Parsed node
     */
    private STNode parseTupleTypeDescOrListConstructor(STNode annots) {
        startContext(ParserRuleContext.BRACKETED_LIST);
        STNode openBracket = parseOpenBracket();
        List<STNode> memberList = new ArrayList<>();
        return parseTupleTypeDescOrListConstructor(annots, openBracket, memberList, false);
    }

    private STNode parseTupleTypeDescOrListConstructor(STNode annots, STNode openBracket, List<STNode> memberList,
                                                       boolean isRoot) {
        
        STToken nextToken = peek();
        while (!isBracketedListEnd(nextToken.kind)) {
            
            STNode member = parseTupleTypeDescOrListConstructorMember(annots);
            SyntaxKind currentNodeType = getParsingNodeTypeOfTupleTypeOrListCons(member);

            switch (currentNodeType) {
                case LIST_CONSTRUCTOR:
                    
                    
                    return parseAsListConstructor(openBracket, memberList, member, isRoot);
                case REST_TYPE:
                case MEMBER_TYPE_DESC:
                    return parseAsTupleTypeDesc(annots, openBracket, memberList, member, isRoot);
                case TUPLE_TYPE_DESC:
                    member = parseComplexTypeDescriptor(member, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
                    member = createMemberOrRestNode(STNodeFactory.createEmptyNodeList(), member);
                    
                    
                    return parseAsTupleTypeDesc(annots, openBracket, memberList, member, isRoot);
                case TUPLE_TYPE_DESC_OR_LIST_CONST:
                default:
                    memberList.add(member);
                    break;
            }

            
            STNode memberEnd = parseBracketedListMemberEnd();
            if (memberEnd == null) {
                break;
            }
            memberList.add(memberEnd);
            nextToken = peek();
        }

        
        STNode closeBracket = parseCloseBracket();
        return parseTupleTypeDescOrListConstructorRhs(openBracket, memberList, closeBracket, isRoot);
    }

    private STNode parseTupleTypeDescOrListConstructorMember(STNode annots) {
        STToken nextToken = peek();
        switch (nextToken.kind) {
            case OPEN_BRACKET_TOKEN:
                
                return parseTupleTypeDescOrListConstructor(annots);
            case IDENTIFIER_TOKEN:
                STNode identifier = parseQualifiedIdentifier(ParserRuleContext.VARIABLE_REF);
                
                if (peek().kind == SyntaxKind.ELLIPSIS_TOKEN) {
                    STNode ellipsis = parseEllipsis();
                    return STNodeFactory.createRestDescriptorNode(identifier, ellipsis);
                }
                return parseExpressionRhs(DEFAULT_OP_PRECEDENCE, identifier, false, false);
            case OPEN_BRACE_TOKEN:
                
                return parseMappingConstructorExpr();
            case ERROR_KEYWORD:
                STToken nextNextToken = getNextNextToken();
                if (nextNextToken.kind == SyntaxKind.OPEN_PAREN_TOKEN ||
                        nextNextToken.kind == SyntaxKind.IDENTIFIER_TOKEN) {
                    return parseErrorConstructorExpr(false);
                }
                
                return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
            case XML_KEYWORD:
            case STRING_KEYWORD:
                if (getNextNextToken().kind == SyntaxKind.BACKTICK_TOKEN) {
                    return parseExpression(false);
                }
                return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
            case TABLE_KEYWORD:
            case STREAM_KEYWORD:
                if (getNextNextToken().kind == SyntaxKind.LT_TOKEN) {
                    return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
                }
                return parseExpression(false);
            case OPEN_PAREN_TOKEN:
                return parseTypeDescOrExpr();
            case AT_TOKEN:
                return parseTupleMember();
            default:
                if (isValidExpressionStart(nextToken.kind, 1)) {
                    return parseExpression(false);
                }

                if (isTypeStartingToken(nextToken.kind)) {
                    return parseTypeDescriptor(ParserRuleContext.TYPE_DESC_IN_TUPLE);
                }

                recover(peek(), ParserRuleContext.TUPLE_TYPE_DESC_OR_LIST_CONST_MEMBER);
                return parseTupleTypeDescOrListConstructorMember(annots);
        }
    }

    private SyntaxKind getParsingNodeTypeOfTupleTypeOrListCons(STNode memberNode) {
        
        return getStmtStartBracketedListType(memberNode);
    }

    private STNode parseTupleTypeDescOrListConstructorRhs(STNode openBracket, List<STNode> members, STNode closeBracket,
                                                          boolean isRoot) {
        STNode tupleTypeOrListConst;
        switch (peek().kind) {
            case COMMA_TOKEN: 
            case CLOSE_BRACE_TOKEN: 
            case CLOSE_BRACKET_TOKEN: 
            case PIPE_TOKEN: 
            case BITWISE_AND_TOKEN: 
                if (!isRoot) {
                    endContext();
                    return new STAmbiguousCollectionNode(SyntaxKind.TUPLE_TYPE_DESC_OR_LIST_CONST, openBracket, members,
                            closeBracket);
                }
                
            default:
                if (isValidExprRhsStart(peek().kind, closeBracket.kind) ||
                        (isRoot && peek().kind == SyntaxKind.EQUAL_TOKEN)) {
                    members = getExpressionList(members, false);
                    STNode memberExpressions = STNodeFactory.createNodeList(members);
                    tupleTypeOrListConst = STNodeFactory.createListConstructorExpressionNode(openBracket,
                            memberExpressions, closeBracket);
                    break;
                }

                
                STNode memberTypeDescs = STNodeFactory.createNodeList(getTupleMemberList(members));
                STNode tupleTypeDesc =
                        STNodeFactory.createTupleTypeDescriptorNode(openBracket, memberTypeDescs, closeBracket);
                tupleTypeOrListConst =
                        parseComplexTypeDescriptor(tupleTypeDesc, ParserRuleContext.TYPE_DESC_IN_TUPLE, false);
        }

        endContext();

        if (!isRoot) {
            return tupleTypeOrListConst;
        }

        STNode annots = STNodeFactory.createEmptyNodeList();
        return parseStmtStartsWithTupleTypeOrExprRhs(annots, tupleTypeOrListConst, isRoot);

    }