File: Parser\BlockContexts\SingleLineElseContext.vb
Web Access
Project: src\roslyn\src\Compilers\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.vbproj (Microsoft.CodeAnalysis.VisualBasic)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.

Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
'-----------------------------------------------------------------------------
' Contains the definition of the BlockContext
'-----------------------------------------------------------------------------

Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax

    Friend NotInheritable Class SingleLineElseContext
        Inherits SingleLineIfOrElseBlockContext

        Friend Sub New(kind As SyntaxKind, statement As StatementSyntax, prevContext As BlockContext)
            MyBase.New(kind, statement, prevContext)

            Debug.Assert(kind = SyntaxKind.SingleLineElseClause)
            Debug.Assert(prevContext.BlockKind = SyntaxKind.SingleLineIfStatement)
        End Sub

        Friend Overrides Function ProcessSyntax(node As VisualBasicSyntaxNode) As BlockContext

            Select Case node.Kind
                Case SyntaxKind.IfStatement
                    ' Single line if statements can always open a new context. 
                    ' A single line if has a "then" on the line and is not followed by a ":", EOL or EOF.
                    ' Multi line if's can only open a new context if they are followed by ":"
                    Dim ifStmt = DirectCast(node, IfStatementSyntax)

                    ' single line if 
                    If ifStmt.ThenKeyword IsNot Nothing AndAlso Not SyntaxFacts.IsTerminator(Parser.CurrentToken.Kind) Then
                        Return MyBase.ProcessSyntax(node)
                    End If

                ' multi-line if handled after select

                Case SyntaxKind.ElseIfStatement
                    ' ElseIf ends the context. This is an error case. Let the outer context process them.
                    ' Previously we explicitly added a missing terminator.  Now, terminators are added automatically 
                    ' if a statement is added next to a statement.
                    Return EndBlock(Nothing).ProcessSyntax(node)

                Case SyntaxKind.CatchStatement, SyntaxKind.FinallyStatement
                    ' A Catch or Finally always closes a single line else
                    Add(Parser.ReportSyntaxError(node, If(node.Kind = SyntaxKind.CatchStatement, ERRID.ERR_CatchNoMatchingTry, ERRID.ERR_FinallyNoMatchingTry)))
                    Return Me.EndBlock(Nothing)

            End Select

            Return MyBase.ProcessSyntax(node)
        End Function

        Friend Overrides Function CreateBlockSyntax(endStmt As StatementSyntax) As VisualBasicSyntaxNode
            Debug.Assert(endStmt Is Nothing)
            Return CreateElseBlockSyntax()
        End Function

        Private Function CreateElseBlockSyntax() As SingleLineElseClauseSyntax
            Debug.Assert(BeginStatement IsNot Nothing)

            Dim elseStatement = DirectCast(BeginStatement, ElseStatementSyntax)

            Dim result = SyntaxFactory.SingleLineElseClause(elseStatement.ElseKeyword, OptionalBody())

            FreeStatements()

            Return result
        End Function

        Friend Overrides Function EndBlock(statement As StatementSyntax) As BlockContext
            Debug.Assert(statement Is Nothing)

            Dim context = PrevBlock.ProcessSyntax(CreateElseBlockSyntax())
            Debug.Assert(context Is PrevBlock)

            Return context.EndBlock(Nothing)
        End Function

        Friend Overrides Function ProcessStatementTerminator(lambdaContext As BlockContext) As BlockContext
            Dim token = Parser.CurrentToken
            Select Case token.Kind
                Case SyntaxKind.StatementTerminatorToken, SyntaxKind.EndOfFileToken
EndBlock:
                    ' A single-line Else is terminated at the end of the line.
                    Dim context = EndBlock(Nothing)
                    Return context.ProcessStatementTerminator(lambdaContext)

                Case SyntaxKind.ColonToken
                    ' A colon only represents the end of the single-line Else
                    ' if there are no statements before the colon.
                    If _statements.Count > 0 Then
                        Parser.ConsumeColonInSingleLineExpression()
                        Return Me
                    End If
                    GoTo EndBlock

                Case Else
                    Throw ExceptionUtilities.UnexpectedValue(token.Kind)
            End Select
        End Function

        Friend Overrides Function ResyncAndProcessStatementTerminator(statement As StatementSyntax, lambdaContext As BlockContext) As BlockContext
            Dim token = Parser.CurrentToken
            Select Case token.Kind
                Case SyntaxKind.StatementTerminatorToken, SyntaxKind.EndOfFileToken, SyntaxKind.ColonToken
                    Return ProcessStatementTerminator(lambdaContext)

                Case SyntaxKind.ElseKeyword
                    If TreatElseAsStatementTerminator Then
                        ' Terminated by Else from containing block.
                        Parser.ConsumedStatementTerminator(allowLeadingMultilineTrivia:=False)
                        Return ProcessElseAsStatementTerminator()
                    End If

                    Return MyBase.ResyncAndProcessStatementTerminator(statement, lambdaContext)

                Case Else
                    ' Terminated if we've already seen at least one statement.
                    If _statements.Count > 0 Then
                        If TreatOtherAsStatementTerminator Then
                            Return ProcessOtherAsStatementTerminator()
                        End If

                        Return MyBase.ResyncAndProcessStatementTerminator(statement, lambdaContext)
                    End If

                    Parser.ConsumedStatementTerminator(allowLeadingMultilineTrivia:=False)
                    Return Me
            End Select
        End Function

        Private ReadOnly Property TreatElseAsStatementTerminator As Boolean
            Get
                ' We can treat 'Else' as a valid statement terminator only
                ' if there is a line-If without Else up the block context chain, or
                ' if we are inside a single-line statement lambda and the 'Else' terminates the lambda.
                Debug.Assert(BlockKind = SyntaxKind.SingleLineElseClause)
                Debug.Assert(PrevBlock.BlockKind = SyntaxKind.SingleLineIfStatement)

                Dim possiblyLineIfWithoutElse = PrevBlock.PrevBlock

                While possiblyLineIfWithoutElse.BlockKind <> SyntaxKind.SingleLineIfStatement
                    Select Case possiblyLineIfWithoutElse.BlockKind
                        Case SyntaxKind.SingleLineElseClause
                            ' This is a line-If with Else, jump out of it
                            Debug.Assert(possiblyLineIfWithoutElse.PrevBlock.BlockKind = SyntaxKind.SingleLineIfStatement)
                            possiblyLineIfWithoutElse = possiblyLineIfWithoutElse.PrevBlock.PrevBlock

                        Case SyntaxKind.SingleLineSubLambdaExpression
                            Return True

                        Case Else
                            Return False
                    End Select
                End While

                Debug.Assert(possiblyLineIfWithoutElse.BlockKind = SyntaxKind.SingleLineIfStatement)
                Return True
            End Get
        End Property

        Private Function ProcessElseAsStatementTerminator() As BlockContext
            Dim context = EndBlock(Nothing) ' This gets us out of entire line-if context

            While context.BlockKind <> SyntaxKind.SingleLineIfStatement
                Select Case context.BlockKind
                    Case SyntaxKind.SingleLineElseClause
                        ' This is a line-If with Else, jump out of it
                        context = context.EndBlock(Nothing) ' This gets us out of entire line-if context

                    Case SyntaxKind.SingleLineSubLambdaExpression
                        ' This will force termination of the single line lambda
                        Return context.PrevBlock

                    Case Else
                        Throw ExceptionUtilities.UnexpectedValue(context.BlockKind)
                End Select
            End While

            Return context
        End Function

    End Class

End Namespace