File: Parser\ParseInterpolatedString.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.
 
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.Syntax.InternalSyntax
Imports InternalSyntaxFactory = Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax.SyntaxFactory
 
'
'============ Methods for parsing portions of executable statements ==
'
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax
 
    Partial Friend Class Parser
 
        Private Function ParseInterpolatedStringExpression() As InterpolatedStringExpressionSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.DollarSignDoubleQuoteToken, "ParseInterpolatedStringExpression called on the wrong token.")
 
            ResetCurrentToken(ScannerState.InterpolatedStringPunctuation)
 
            Debug.Assert(CurrentToken.Kind = SyntaxKind.DollarSignDoubleQuoteToken, "Rescanning $"" failed.")
 
            Dim dollarSignDoubleQuoteToken = DirectCast(CurrentToken, PunctuationSyntax)
            GetNextToken(ScannerState.InterpolatedStringContent)
 
            Dim contentBuilder = _pool.Allocate(Of InterpolatedStringContentSyntax)
 
            Dim doubleQuoteToken As PunctuationSyntax
            Dim skipped As SyntaxListBuilder(Of SyntaxToken) = Nothing
            Do
                Dim content As InterpolatedStringContentSyntax
 
                Select Case CurrentToken.Kind
                    Case SyntaxKind.InterpolatedStringTextToken
 
                        Dim textToken = DirectCast(CurrentToken, InterpolatedStringTextTokenSyntax)
 
                        ' At this point we're either right before an {, an } (error), or a "".
                        GetNextToken(ScannerState.InterpolatedStringPunctuation)
 
                        Debug.Assert(CurrentToken.Kind <> SyntaxKind.InterpolatedStringTextToken,
                                     "Two interpolated string text literal tokens encountered back-to-back. " &
                                     "Scanner should have scanned these as a single token.")
 
                        content = SyntaxFactory.InterpolatedStringText(textToken)
 
                    Case SyntaxKind.OpenBraceToken
 
                        content = ParseInterpolatedStringInterpolation()
 
                    Case SyntaxKind.CloseBraceToken
 
                        If skipped.IsNull Then
                            skipped = _pool.Allocate(Of SyntaxToken)
                        End If
 
                        skipped.Add(CurrentToken)
                        GetNextToken(ScannerState.InterpolatedStringContent)
                        Continue Do
 
                    Case SyntaxKind.DoubleQuoteToken
 
                        doubleQuoteToken = DirectCast(CurrentToken, PunctuationSyntax)
                        GetNextToken()
                        Exit Do
 
                    Case SyntaxKind.EndOfInterpolatedStringToken
 
                        doubleQuoteToken = InternalSyntaxFactory.MissingPunctuation(SyntaxKind.DoubleQuoteToken)
                        GetNextToken(ScannerState.VB)
                        Exit Do
 
                    Case Else
 
                        doubleQuoteToken = InternalSyntaxFactory.MissingPunctuation(SyntaxKind.DoubleQuoteToken)
                        Exit Do
 
                End Select
 
                If Not skipped.IsNull Then
                    content = AddLeadingSyntax(content, _pool.ToListAndFree(skipped), ERRID.ERR_Syntax)
                    skipped = Nothing
                End If
 
                contentBuilder.Add(content)
            Loop
 
            If Not skipped.IsNull Then
                doubleQuoteToken = AddLeadingSyntax(doubleQuoteToken, _pool.ToListAndFree(skipped), ERRID.ERR_Syntax)
                skipped = Nothing
            End If
 
            Dim node = SyntaxFactory.InterpolatedStringExpression(dollarSignDoubleQuoteToken,
                                                              _pool.ToListAndFree(contentBuilder),
                                                              doubleQuoteToken)
            Return CheckFeatureAvailability(Feature.InterpolatedStrings, node)
        End Function
 
        Private Function ParseInterpolatedStringInterpolation() As InterpolationSyntax
            Debug.Assert(CurrentToken.Kind = SyntaxKind.OpenBraceToken, "ParseInterpolatedStringEmbeddedExpression called on the wrong token.")
 
            Dim colonToken As PunctuationSyntax = Nothing
            Dim excessText As String = Nothing
 
            Dim openBraceToken = DirectCast(CurrentToken, PunctuationSyntax)
            GetNextToken(ScannerState.VB)
 
            Dim expression As ExpressionSyntax
 
            If CurrentToken.Kind = SyntaxKind.ColonToken Then
 
                openBraceToken = DirectCast(RemoveTrailingColonTriviaAndConvertToColonToken(openBraceToken, colonToken, excessText), PunctuationSyntax)
                expression = ReportSyntaxError(InternalSyntaxFactory.MissingExpression(), ERRID.ERR_ExpectedExpression)
 
            Else
                expression = ParseExpressionCore()
 
                ' Scanned this as a terminator. Fix it.
                If CurrentToken.Kind = SyntaxKind.ColonToken Then
                    expression = DirectCast(RemoveTrailingColonTriviaAndConvertToColonToken(expression, colonToken, excessText), ExpressionSyntax)
                End If
            End If
 
            Dim alignmentClauseOpt As InterpolationAlignmentClauseSyntax
 
            If CurrentToken.Kind = SyntaxKind.CommaToken Then
                Debug.Assert(colonToken Is Nothing)
 
                Dim commaToken = DirectCast(CurrentToken, PunctuationSyntax)
                GetNextToken(ScannerState.VB)
 
                If CurrentToken.Kind = SyntaxKind.ColonToken Then
                    commaToken = DirectCast(RemoveTrailingColonTriviaAndConvertToColonToken(commaToken, colonToken, excessText), PunctuationSyntax)
                End If
 
                Dim signTokenOpt As PunctuationSyntax
 
                If CurrentToken.Kind = SyntaxKind.MinusToken OrElse
                   CurrentToken.Kind = SyntaxKind.PlusToken Then
 
                    signTokenOpt = DirectCast(CurrentToken, PunctuationSyntax)
                    GetNextToken(ScannerState.VB)
 
                    If CurrentToken.Kind = SyntaxKind.ColonToken Then
                        signTokenOpt = DirectCast(RemoveTrailingColonTriviaAndConvertToColonToken(signTokenOpt, colonToken, excessText), PunctuationSyntax)
                    End If
                Else
                    signTokenOpt = Nothing
                End If
 
                Dim widthToken As IntegerLiteralTokenSyntax
 
                If CurrentToken.Kind = SyntaxKind.IntegerLiteralToken Then
 
                    widthToken = DirectCast(CurrentToken, IntegerLiteralTokenSyntax)
                    GetNextToken(ScannerState.VB)
 
                    If CurrentToken.Kind = SyntaxKind.ColonToken Then
                        widthToken = DirectCast(RemoveTrailingColonTriviaAndConvertToColonToken(widthToken, colonToken, excessText), IntegerLiteralTokenSyntax)
                    End If
                Else
                    widthToken = ReportSyntaxError(InternalSyntaxFactory.MissingIntegerLiteralToken(), ERRID.ERR_ExpectedIntLiteral)
                End If
 
                Dim valueExpression As ExpressionSyntax = SyntaxFactory.NumericLiteralExpression(widthToken)
 
                If signTokenOpt IsNot Nothing Then
                    valueExpression = SyntaxFactory.UnaryExpression(
                                                        If(signTokenOpt.Kind = SyntaxKind.PlusToken, SyntaxKind.UnaryPlusExpression, SyntaxKind.UnaryMinusExpression),
                                                        signTokenOpt,
                                                        valueExpression)
                End If
 
                alignmentClauseOpt = SyntaxFactory.InterpolationAlignmentClause(commaToken, valueExpression)
            Else
                alignmentClauseOpt = Nothing
            End If
 
            Dim formatStringClauseOpt As InterpolationFormatClauseSyntax
 
            If CurrentToken.Kind = SyntaxKind.ColonToken AndAlso colonToken IsNot Nothing Then
                ' If colonToken IsNot Nothing we were able to recovery.
 
                GetNextToken(ScannerState.InterpolatedStringFormatString)
 
                Dim formatStringToken As InterpolatedStringTextTokenSyntax
 
                If CurrentToken.Kind = SyntaxKind.InterpolatedStringTextToken Then
                    formatStringToken = DirectCast(CurrentToken, InterpolatedStringTextTokenSyntax)
                    GetNextToken(ScannerState.InterpolatedStringPunctuation)
 
                    If excessText IsNot Nothing Then
                        formatStringToken = InternalSyntaxFactory.InterpolatedStringTextToken(excessText & formatStringToken.Text,
                                                                                              excessText & formatStringToken.Value,
                                                                                              formatStringToken.GetLeadingTrivia(),
                                                                                              formatStringToken.GetTrailingTrivia())
                    End If
                Else
                    If excessText IsNot Nothing Then
                        formatStringToken = InternalSyntaxFactory.InterpolatedStringTextToken(excessText,
                                                                                              excessText,
                                                                                              Nothing,
                                                                                              Nothing)
                    Else
                        formatStringToken = Nothing
                    End If
                End If
 
                If formatStringToken Is Nothing Then
                    formatStringToken = DirectCast(InternalSyntaxFactory.MissingToken(SyntaxKind.InterpolatedStringTextToken), InterpolatedStringTextTokenSyntax)
                    formatStringToken = ReportSyntaxError(formatStringToken, ERRID.ERR_Syntax)
 
                ElseIf formatStringToken.GetTrailingTrivia() IsNot Nothing Then
                    formatStringToken = ReportSyntaxError(formatStringToken, ERRID.ERR_InterpolationFormatWhitespace)
 
                End If
 
                formatStringClauseOpt = SyntaxFactory.InterpolationFormatClause(colonToken, formatStringToken)
 
            Else
                formatStringClauseOpt = Nothing
 
                If CurrentToken.Kind = SyntaxKind.ColonToken Then
                    ' But if colonToken is null we weren't able to gracefully recover.
                    GetNextToken(ScannerState.InterpolatedStringFormatString)
                End If
            End If
 
            Dim closeBraceToken As PunctuationSyntax
 
            If CurrentToken.Kind = SyntaxKind.CloseBraceToken Then
                ' Must rescan this with interpolated string rules for trailing trivia.
                ' Specifically, any trailing trivia attached to the closing brace is actually interpolated string content.
                ResetCurrentToken(ScannerState.InterpolatedStringPunctuation)
 
                closeBraceToken = DirectCast(CurrentToken, PunctuationSyntax)
                GetNextToken(ScannerState.InterpolatedStringContent)
 
            ElseIf CurrentToken.Kind = SyntaxKind.EndOfInterpolatedStringToken Then
                GetNextToken(ScannerState.VB)
 
                closeBraceToken = DirectCast(HandleUnexpectedToken(SyntaxKind.CloseBraceToken), PunctuationSyntax)
            Else
                ' Content rules will either resync at a } or at the closing ".
                If Not IsValidStatementTerminator(CurrentToken) Then
                    ResetCurrentToken(ScannerState.InterpolatedStringFormatString)
                End If
 
                Debug.Assert(CurrentToken.Kind <> SyntaxKind.CloseBraceToken)
                closeBraceToken = DirectCast(HandleUnexpectedToken(SyntaxKind.CloseBraceToken), PunctuationSyntax)
 
                If CurrentToken.Kind = SyntaxKind.InterpolatedStringTextToken Then
                    ResetCurrentToken(ScannerState.InterpolatedStringContent)
                    GetNextToken(ScannerState.InterpolatedStringContent)
                End If
            End If
 
            Return SyntaxFactory.Interpolation(openBraceToken, expression, alignmentClauseOpt, formatStringClauseOpt, closeBraceToken)
 
        End Function
 
        Private Shared Function RemoveTrailingColonTriviaAndConvertToColonToken(
                             token As SyntaxToken,
                             <Out> ByRef colonToken As PunctuationSyntax,
                             <Out> ByRef excessText As String
                         ) As SyntaxToken
 
            If Not token.HasTrailingTrivia Then
                colonToken = Nothing
                excessText = Nothing
                Return token
            End If
 
            Dim triviaList As New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of VisualBasicSyntaxNode)(token.GetTrailingTrivia())
 
            Dim indexOfFirstColon As Integer = -1
            Dim newTrailingTrivia As GreenNode
 
            If triviaList.Count = 1 Then
                indexOfFirstColon = 0
                newTrailingTrivia = Nothing
                excessText = Nothing
            ElseIf triviaList(0).Kind = SyntaxKind.ColonTrivia
                indexOfFirstColon = 0
                newTrailingTrivia = Nothing
                excessText = triviaList.GetEndOfTrivia(1).Node.ToFullString()
            Else
                For i = 0 To triviaList.Count - 1
                    If triviaList(i).Kind = SyntaxKind.ColonTrivia Then
                        indexOfFirstColon = i
                        Exit For
                    End If
                Next
 
                newTrailingTrivia = triviaList.GetStartOfTrivia(indexOfFirstColon).Node
 
                If indexOfFirstColon = triviaList.Count - 1 Then
                    excessText = Nothing
                Else
                    excessText = triviaList.GetEndOfTrivia(indexOfFirstColon + 1).Node.ToFullString()
                    Debug.Assert(triviaList.GetEndOfTrivia(indexOfFirstColon + 1).AnyAndOnly(SyntaxKind.ColonTrivia, SyntaxKind.WhitespaceTrivia))
                End If
            End If
 
            Dim firstColonTrivia = DirectCast(triviaList(indexOfFirstColon), SyntaxTrivia)
 
            colonToken = New PunctuationSyntax(SyntaxKind.ColonToken, firstColonTrivia.Text, Nothing, Nothing)
 
            Return DirectCast(token.WithTrailingTrivia(newTrailingTrivia), SyntaxToken)
 
        End Function
 
        Private Function RemoveTrailingColonTriviaAndConvertToColonToken(
                             node As VisualBasicSyntaxNode,
                             <Out> ByRef colonToken As PunctuationSyntax,
                             <Out> ByRef excessText As String
                         ) As VisualBasicSyntaxNode
 
            Dim lastNonMissing = DirectCast(node.GetLastToken(), SyntaxToken)
 
            Dim newLastNonMissing = RemoveTrailingColonTriviaAndConvertToColonToken(lastNonMissing, colonToken, excessText)
 
            Dim newNode = LastTokenReplacer.Replace(node, Function(t) If(t Is lastNonMissing, newLastNonMissing, t))
 
            ' If no token was replaced we have failed to recover; let high contexts deal with it.
            If newNode Is node Then
                colonToken = Nothing
                excessText = Nothing
            End If
 
            Return newNode
        End Function
 
    End Class
 
End Namespace