File: Parser\ParseConditional.vb
Web Access
Project: src\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.
 
' //
' //============ Methods related to conditional compilation. ==============
' //
 
' // Parse a line containing a conditional compilation directive.
Imports System.Globalization
Imports Microsoft.CodeAnalysis.Syntax.InternalSyntax
Imports InternalSyntaxFactory = Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.SyntaxFactory
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax
 
    Partial Friend Class Parser
 
        ' File: Parser.cpp
        ' Lines: 18978 - 18978
        ' .Parser::ParseConditionalCompilationStatement( [ bool SkippingMethodBody ] )
 
        Friend Function ParseConditionalCompilationStatement() As DirectiveTriviaSyntax
            ' # may be actually scanned as a date literal. This is an error.
            If CurrentToken.Kind = SyntaxKind.DateLiteralToken OrElse
                CurrentToken.Kind = SyntaxKind.BadToken Then
 
                Dim missingHash = InternalSyntaxFactory.MissingPunctuation(SyntaxKind.HashToken)
                missingHash = missingHash.AddLeadingSyntax(New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of SyntaxToken)(CurrentToken))
                GetNextToken()
                Return (ParseBadDirective(missingHash))
            End If
 
            Debug.Assert(CurrentToken.Kind = SyntaxKind.HashToken, "Conditional compilation lines start with '#'.")
 
            ' // First parse the conditional line, then interpret it.
 
            Dim statement As DirectiveTriviaSyntax = Nothing
            Dim hashToken = DirectCast(CurrentToken, PunctuationSyntax)
            GetNextToken()
 
            Select Case CurrentToken.Kind
 
                Case SyntaxKind.ElseKeyword
 
                    statement = ParseElseDirective(hashToken)
 
                Case SyntaxKind.IfKeyword
 
                    statement = ParseIfDirective(hashToken, Nothing)
 
                Case SyntaxKind.ElseIfKeyword
 
                    statement = ParseElseIfDirective(hashToken)
 
                Case SyntaxKind.EndKeyword
 
                    statement = ParseEndDirective(hashToken)
 
                Case SyntaxKind.EndIfKeyword
 
                    statement = ParseAnachronisticEndIfDirective(hashToken)
 
                Case SyntaxKind.ConstKeyword
 
                    statement = ParseConstDirective(hashToken)
 
                Case SyntaxKind.IdentifierToken
                    Select Case DirectCast(CurrentToken, IdentifierTokenSyntax).PossibleKeywordKind
 
                        Case SyntaxKind.ExternalSourceKeyword
                            statement = ParseExternalSourceDirective(hashToken)
 
                        Case SyntaxKind.ExternalChecksumKeyword
                            statement = ParseExternalChecksumDirective(hashToken)
 
                        Case SyntaxKind.RegionKeyword
                            statement = ParseRegionDirective(hashToken)
 
                        Case SyntaxKind.EnableKeyword, SyntaxKind.DisableKeyword
                            statement = ParseWarningDirective(hashToken)
 
                        Case SyntaxKind.ReferenceKeyword
                            statement = ParseReferenceDirective(hashToken)
 
                        Case Else
                            statement = ParseBadDirective(hashToken)
                    End Select
 
                Case Else
                    statement = ParseBadDirective(hashToken)
 
            End Select
 
            Return statement
        End Function
 
        ' File: Parser.cpp
        ' Lines: 19739 - 19739
        ' Expression* .Parser::ParseConditionalCompilationExpression( [ _Inout_ bool& ErrorInConstruct ] )
 
        Friend Function ParseConditionalCompilationExpression() As ExpressionSyntax
            Dim PrevEvaluatingConditionCompilationExpressions As Boolean = _evaluatingConditionCompilationExpression
            _evaluatingConditionCompilationExpression = True
 
            Dim Expr = ParseExpressionCore()
 
            _evaluatingConditionCompilationExpression = PrevEvaluatingConditionCompilationExpressions
 
            Return Expr
        End Function
 
        Private Function ParseElseDirective(hashToken As PunctuationSyntax) As DirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.ElseKeyword)
 
            Dim elseKeyword = DirectCast(CurrentToken, KeywordSyntax)
            GetNextToken()
 
            If CurrentToken.Kind <> SyntaxKind.IfKeyword Then
 
                Dim statement = SyntaxFactory.ElseDirectiveTrivia(hashToken, elseKeyword)
 
                Return statement
 
            Else
                ' // Accept Else If as a synonym for ElseIf.
 
                Return ParseIfDirective(hashToken, elseKeyword)
            End If
 
        End Function
 
        Private Function ParseElseIfDirective(hashToken As PunctuationSyntax) As DirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.ElseIfKeyword)
 
            Return ParseIfDirective(hashToken, Nothing)
 
        End Function
 
        Private Function ParseIfDirective(hashToken As PunctuationSyntax, elseKeyword As KeywordSyntax) As IfDirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.IfKeyword OrElse CurrentToken.Kind = SyntaxKind.ElseIfKeyword)
 
            Dim ifKeyword As KeywordSyntax = DirectCast(CurrentToken, KeywordSyntax)
            GetNextToken()
 
            Dim expression = ParseConditionalCompilationExpression()
 
            If expression.ContainsDiagnostics Then
                expression = ResyncAt(expression)
            End If
 
            Dim thenKeyword As KeywordSyntax = Nothing
            If CurrentToken.Kind = SyntaxKind.ThenKeyword Then
                thenKeyword = DirectCast(CurrentToken, KeywordSyntax)
                GetNextToken()
            End If
 
            Dim statement As IfDirectiveTriviaSyntax
 
            If ifKeyword.Kind = SyntaxKind.IfKeyword AndAlso elseKeyword Is Nothing Then
                statement = SyntaxFactory.IfDirectiveTrivia(hashToken, elseKeyword, ifKeyword, expression, thenKeyword)
            Else
                statement = SyntaxFactory.ElseIfDirectiveTrivia(hashToken, elseKeyword, ifKeyword, expression, thenKeyword)
            End If
 
            Return statement
        End Function
 
        Private Function ParseEndDirective(hashToken As PunctuationSyntax) As DirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.EndKeyword)
 
            Dim endKeyword = DirectCast(CurrentToken, KeywordSyntax)
            GetNextToken()
 
            Dim statement As DirectiveTriviaSyntax = Nothing
 
            If CurrentToken.Kind = SyntaxKind.IfKeyword Then
 
                Dim ifKeyword = DirectCast(CurrentToken, KeywordSyntax)
                GetNextToken()
 
                statement = SyntaxFactory.EndIfDirectiveTrivia(hashToken, endKeyword, ifKeyword)
 
            ElseIf CurrentToken.Kind = SyntaxKind.IdentifierToken Then
 
                Dim identifier = DirectCast(CurrentToken, IdentifierTokenSyntax)
 
                If identifier.PossibleKeywordKind = SyntaxKind.RegionKeyword Then
                    Dim regionKeyword = _scanner.MakeKeyword(DirectCast(CurrentToken, IdentifierTokenSyntax))
                    GetNextToken()
 
                    statement = SyntaxFactory.EndRegionDirectiveTrivia(hashToken, endKeyword, regionKeyword)
 
                ElseIf identifier.PossibleKeywordKind = SyntaxKind.ExternalSourceKeyword Then
                    Dim externalSourceKeyword = _scanner.MakeKeyword(DirectCast(CurrentToken, IdentifierTokenSyntax))
                    GetNextToken()
 
                    statement = SyntaxFactory.EndExternalSourceDirectiveTrivia(hashToken, endKeyword, externalSourceKeyword)
                End If
 
            End If
 
            If statement Is Nothing Then
                hashToken = hashToken.AddTrailingSyntax(endKeyword, ERRID.ERR_Syntax)
                statement = SyntaxFactory.BadDirectiveTrivia(hashToken)
            End If
 
            Return statement
        End Function
 
        Private Function ParseAnachronisticEndIfDirective(hashToken As PunctuationSyntax) As DirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.EndIfKeyword)
 
            Dim endIfKeyword = DirectCast(CurrentToken, KeywordSyntax)
            GetNextToken()
 
            Dim endKeyword = InternalSyntaxFactory.MissingKeyword(SyntaxKind.EndKeyword)
            endKeyword = endKeyword.AddLeadingSyntax(endIfKeyword, ERRID.ERR_ObsoleteEndIf)
 
            Dim statement = SyntaxFactory.EndIfDirectiveTrivia(hashToken, endKeyword, InternalSyntaxFactory.MissingKeyword(SyntaxKind.IfKeyword))
 
            Return statement
 
        End Function
 
        Private Function ParseConstDirective(hashToken As PunctuationSyntax) As ConstDirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.ConstKeyword)
 
            Dim constKeyword = DirectCast(CurrentToken, KeywordSyntax)
            GetNextToken()
 
            Dim Name = ParseIdentifier()
 
            Dim unexpected As CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of SyntaxToken) = Nothing
            If Name.ContainsDiagnostics Then
                unexpected = ResyncAt({SyntaxKind.EqualsToken})
            End If
 
            Dim equalsToken As PunctuationSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.EqualsToken, equalsToken)
            If unexpected.Node IsNot Nothing Then
                equalsToken = equalsToken.AddLeadingSyntax(unexpected, ERRID.ERR_Syntax)
            End If
 
            Dim expression = ParseConditionalCompilationExpression()
 
            If expression.ContainsDiagnostics Then
                expression = ResyncAt(expression)
            End If
 
            Dim statement = SyntaxFactory.ConstDirectiveTrivia(hashToken, constKeyword, Name, equalsToken, expression)
 
            Return statement
        End Function
 
        Private Function ParseRegionDirective(hashToken As PunctuationSyntax) As RegionDirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.IdentifierToken AndAlso DirectCast(CurrentToken, IdentifierTokenSyntax).PossibleKeywordKind = SyntaxKind.RegionKeyword,
                         NameOf(ParseRegionDirective) & " called with wrong token.")
 
            Dim identifier = DirectCast(CurrentToken, IdentifierTokenSyntax)
            GetNextToken()
            Dim regionKeyword = _scanner.MakeKeyword(identifier)
            Dim title As StringLiteralTokenSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.StringLiteralToken, title)
 
            Dim statement = SyntaxFactory.RegionDirectiveTrivia(hashToken, regionKeyword, title)
            Return statement
        End Function
 
        Private Function ParseExternalSourceDirective(hashToken As PunctuationSyntax) As ExternalSourceDirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.IdentifierToken AndAlso DirectCast(CurrentToken, IdentifierTokenSyntax).PossibleKeywordKind = SyntaxKind.ExternalSourceKeyword,
                         NameOf(ParseExternalSourceDirective) & " called with wrong token")
 
            Dim identifier = DirectCast(CurrentToken, IdentifierTokenSyntax)
            Dim externalSourceKeyword = _scanner.MakeKeyword(identifier)
 
            GetNextToken()
 
            Dim openParen As PunctuationSyntax = Nothing
            Dim closeParen As PunctuationSyntax = Nothing
 
            VerifyExpectedToken(SyntaxKind.OpenParenToken, openParen)
 
            Dim externalSource As StringLiteralTokenSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.StringLiteralToken, externalSource)
 
            Dim comma As PunctuationSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.CommaToken, comma)
 
            Dim externalSourceFileStartLine As IntegerLiteralTokenSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.IntegerLiteralToken, externalSourceFileStartLine)
 
            VerifyExpectedToken(SyntaxKind.CloseParenToken, closeParen)
 
            'TODO - Need to keep track of external source nesting and report and error
 
            'If m_CurrentExternalSourceDirective IsNot Nothing Then
 
            '    ReportSyntaxError(ERRID.ERR_NestedExternalSource, Start, m_CurrentToken, ErrorInConstruct)
            '    hasErrors = True
 
            'ElseIf m_ExternalSourceDirectiveTarget IsNot Nothing Then
 
            'End If
 
            Dim statement = SyntaxFactory.ExternalSourceDirectiveTrivia(hashToken,
                                                  externalSourceKeyword,
                                                  openParen,
                                                  externalSource,
                                                  comma,
                                                  externalSourceFileStartLine,
                                                  closeParen)
 
            Return statement
        End Function
 
        Private Function ParseExternalChecksumDirective(hashToken As PunctuationSyntax) As ExternalChecksumDirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.IdentifierToken AndAlso DirectCast(CurrentToken, IdentifierTokenSyntax).PossibleKeywordKind = SyntaxKind.ExternalChecksumKeyword,
                          NameOf(ParseExternalChecksumDirective) & " called with wrong token")
 
            Dim identifier = DirectCast(CurrentToken, IdentifierTokenSyntax)
            Dim externalChecksumKeyword = _scanner.MakeKeyword(identifier)
 
            GetNextToken()
 
            Dim openParen As PunctuationSyntax = Nothing
            Dim closeParen As PunctuationSyntax = Nothing
 
            VerifyExpectedToken(SyntaxKind.OpenParenToken, openParen)
 
            Dim externalSource As StringLiteralTokenSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.StringLiteralToken, externalSource)
 
            Dim firstComma As PunctuationSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.CommaToken, firstComma)
 
            Dim guid As StringLiteralTokenSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.StringLiteralToken, guid)
 
            If Not guid.IsMissing Then
                Dim tmp As System.Guid
                If Not System.Guid.TryParse(guid.ValueText, tmp) Then
                    guid = guid.WithDiagnostics(ErrorFactory.ErrorInfo(ERRID.WRN_BadGUIDFormatExtChecksum))
                End If
            End If
 
            Dim secondComma As PunctuationSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.CommaToken, secondComma)
 
            Dim checksum As StringLiteralTokenSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.StringLiteralToken, checksum)
 
            If Not checksum.IsMissing Then
                Dim checksumText = checksum.ValueText
 
                If checksumText.Length Mod 2 <> 0 Then
                    checksum = checksum.WithDiagnostics(ErrorFactory.ErrorInfo(ERRID.WRN_BadChecksumValExtChecksum))
 
                Else
                    For Each ch In checksumText
                        If Not SyntaxFacts.IsHexDigit(ch) Then
                            checksum = checksum.WithDiagnostics(ErrorFactory.ErrorInfo(ERRID.WRN_BadChecksumValExtChecksum))
                            Exit For
                        End If
                    Next
                End If
            End If
 
            VerifyExpectedToken(SyntaxKind.CloseParenToken, closeParen)
 
            Dim statement = SyntaxFactory.ExternalChecksumDirectiveTrivia(hashToken,
                                                    externalChecksumKeyword,
                                                    openParen,
                                                    externalSource,
                                                    firstComma,
                                                    guid,
                                                    secondComma,
                                                    checksum,
                                                    closeParen)
 
            Return statement
 
        End Function
 
        Private Function ParseWarningDirective(hashToken As PunctuationSyntax) As DirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.IdentifierToken,
                         NameOf(ParseWarningDirective) & " called with token that is not an " & NameOf(SyntaxKind.IdentifierToken))
            Dim identifier = DirectCast(CurrentToken, IdentifierTokenSyntax)
 
            Debug.Assert((identifier.PossibleKeywordKind = SyntaxKind.EnableKeyword) OrElse
                         (identifier.PossibleKeywordKind = SyntaxKind.DisableKeyword),
                         NameOf(ParseWarningDirective) & " called with token that is neither " & NameOf(SyntaxKind.EnableKeyword) & " nor " & NameOf(SyntaxKind.DisableKeyword))
            Dim enableOrDisableKeyword = _scanner.MakeKeyword(identifier)
 
            GetNextToken()
 
            Dim warningKeyword As KeywordSyntax = Nothing
            TryGetContextualKeyword(SyntaxKind.WarningKeyword, warningKeyword, createIfMissing:=True)
            If warningKeyword.ContainsDiagnostics Then
                warningKeyword = ResyncAt(warningKeyword)
            End If
 
            Dim errorCodes = Me._pool.AllocateSeparated(Of IdentifierNameSyntax)()
            If Not SyntaxFacts.IsTerminator(CurrentToken.Kind) Then
                Do
                    Dim errorCode = SyntaxFactory.IdentifierName(ParseIdentifier())
                    If errorCode.ContainsDiagnostics Then
                        errorCode = ResyncAt(errorCode, SyntaxKind.CommaToken)
                    ElseIf errorCode.Identifier.TypeCharacter <> TypeCharacter.None Then
                        ' Disallow type characters at the end of diagnostic ids.
                        errorCode = ReportSyntaxError(errorCode, ERRID.ERR_TypecharNotallowed)
                    End If
                    errorCodes.Add(errorCode)
 
                    Dim comma As PunctuationSyntax = Nothing
                    If Not TryGetToken(SyntaxKind.CommaToken, comma) Then
                        If SyntaxFacts.IsTerminator(CurrentToken.Kind) Then
                            Exit Do
                        Else
                            comma = InternalSyntaxFactory.MissingPunctuation(SyntaxKind.CommaToken)
                            comma = ReportSyntaxError(comma, ERRID.ERR_ExpectedComma)
                        End If
                    End If
 
                    If comma.ContainsDiagnostics Then
                        comma = ResyncAt(comma)
                    End If
                    errorCodes.AddSeparator(comma)
                Loop
            End If
 
            Dim statement As DirectiveTriviaSyntax = Nothing
            If enableOrDisableKeyword.Kind = SyntaxKind.EnableKeyword Then
                statement = SyntaxFactory.EnableWarningDirectiveTrivia(
                    hashToken, enableOrDisableKeyword, warningKeyword, errorCodes.ToList)
            ElseIf enableOrDisableKeyword.Kind = SyntaxKind.DisableKeyword Then
                statement = SyntaxFactory.DisableWarningDirectiveTrivia(
                    hashToken, enableOrDisableKeyword, warningKeyword, errorCodes.ToList)
            End If
 
            If statement IsNot Nothing Then
                statement = CheckFeatureAvailability(Feature.WarningDirectives, statement)
            End If
 
            Me._pool.Free(errorCodes)
            Return statement
        End Function
 
        Private Function ParseReferenceDirective(hashToken As PunctuationSyntax) As DirectiveTriviaSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.IdentifierToken AndAlso DirectCast(CurrentToken, IdentifierTokenSyntax).PossibleKeywordKind = SyntaxKind.ReferenceKeyword,
                         NameOf(ParseReferenceDirective) & " called with wrong token.")
 
            Dim identifier = DirectCast(CurrentToken, IdentifierTokenSyntax)
            GetNextToken()
            Dim referenceKeyword = _scanner.MakeKeyword(identifier)
 
            If Not IsScript Then
                referenceKeyword = AddError(referenceKeyword, ERRID.ERR_ReferenceDirectiveOnlyAllowedInScripts)
            End If
 
            Dim file As StringLiteralTokenSyntax = Nothing
            VerifyExpectedToken(SyntaxKind.StringLiteralToken, file)
 
            Return SyntaxFactory.ReferenceDirectiveTrivia(hashToken, referenceKeyword, file)
        End Function
 
        Private Shared Function ParseBadDirective(hashToken As PunctuationSyntax) As BadDirectiveTriviaSyntax
            Dim badDirective = InternalSyntaxFactory.BadDirectiveTrivia(hashToken)
 
            If Not badDirective.ContainsDiagnostics Then
                badDirective = ReportSyntaxError(badDirective, ERRID.ERR_ExpectedConditionalDirective)
            End If
 
            Return badDirective
        End Function
 
    End Class
 
End Namespace