private ExpressionSyntax ParseInterpolatedStringToken()
        {
            // We don't want to make the scanner stateful (between tokens) if we can possibly avoid it.
            // The approach implemented here is
            //
            // (1) Scan the whole interpolated string literal as a single token. Now the statefulness of
            // the scanner (to match { }'s) is limited to its behavior while scanning a single token.
            //
            // (2) When the parser gets such a token, here, it spins up another scanner / parser on each of
            // the holes and builds a tree for the whole thing (resulting in an InterpolatedStringExpressionSyntax).
            //
            // (3) The parser discards the original token and replaces it with this tree. (In other words,
            // it replaces one token with a different set of tokens that have already been parsed)
            //
            // (4) On an incremental change, we widen the invalidated region to include any enclosing interpolated
            // string nonterminal so that we never reuse tokens inside a changed interpolated string.
            //
            // This has the secondary advantage that it can reasonably be specified.
            //
            // The substitution will end up being invisible to external APIs and clients such as the IDE, as
            // they have no way to ask for the stream of tokens before parsing.
            //

            var originalToken = this.EatToken();
            var originalText  = originalToken.ValueText; // this is actually the source text

            Debug.Assert(originalText[0] == '$' || originalText[0] == '@');

            var isAltInterpolatedVerbatim = originalText.Length > 2 && originalText[0] == '@'; // @$
            var isVerbatim = isAltInterpolatedVerbatim || (originalText.Length > 2 && originalText[1] == '@');

            Debug.Assert(originalToken.Kind == SyntaxKind.InterpolatedStringToken);
            var interpolations = ArrayBuilder <Lexer.Interpolation> .GetInstance();

            SyntaxDiagnosticInfo error = null;
            bool closeQuoteMissing;

            using (var tempLexer = new Lexer(Text.SourceText.From(originalText), this.Options, allowPreprocessorDirectives: false))
            {
                // compute the positions of the interpolations in the original string literal, and also compute/preserve
                // lexical errors
                var info = default(Lexer.TokenInfo);
                tempLexer.ScanInterpolatedStringLiteralTop(interpolations, isVerbatim, ref info, ref error, out closeQuoteMissing);
            }

            // Make a token for the open quote $" or $@" or @$"
            var openQuoteIndex = isVerbatim ? 2 : 1;

            Debug.Assert(originalText[openQuoteIndex] == '"');

            var openQuoteKind = isVerbatim
                    ? SyntaxKind.InterpolatedVerbatimStringStartToken // $@ or @$
                    : SyntaxKind.InterpolatedStringStartToken;        // $

            var openQuoteText = isAltInterpolatedVerbatim
                ? "@$\""
                : isVerbatim
                    ? "$@\""
                    : "$\"";
            var openQuote = SyntaxFactory.Token(originalToken.GetLeadingTrivia(), openQuoteKind, openQuoteText, openQuoteText, trailing: null);

            if (isAltInterpolatedVerbatim)
            {
                openQuote = CheckFeatureAvailability(openQuote, MessageID.IDS_FeatureAltInterpolatedVerbatimStrings);
            }

            // Make a token for the close quote " (even if it was missing)
            var closeQuoteIndex = closeQuoteMissing ? originalText.Length : originalText.Length - 1;

            Debug.Assert(closeQuoteMissing || originalText[closeQuoteIndex] == '"');
            var closeQuote = closeQuoteMissing
                ? SyntaxFactory.MissingToken(SyntaxKind.InterpolatedStringEndToken).TokenWithTrailingTrivia(originalToken.GetTrailingTrivia())
                : SyntaxFactory.Token(null, SyntaxKind.InterpolatedStringEndToken, originalToken.GetTrailingTrivia());
            var builder = _pool.Allocate <InterpolatedStringContentSyntax>();

            if (interpolations.Count == 0)
            {
                // In the special case when there are no interpolations, we just construct a format string
                // with no inserts. We must still use String.Format to get its handling of escapes such as {{,
                // so we still treat it as a composite format string.
                var text = Substring(originalText, openQuoteIndex + 1, closeQuoteIndex - 1);
                if (text.Length > 0)
                {
                    var token = MakeStringToken(text, text, isVerbatim, SyntaxKind.InterpolatedStringTextToken);
                    builder.Add(SyntaxFactory.InterpolatedStringText(token));
                }
            }
            else
            {
                for (int i = 0; i < interpolations.Count; i++)
                {
                    var interpolation = interpolations[i];

                    // Add a token for text preceding the interpolation
                    var text = Substring(originalText, (i == 0) ? (openQuoteIndex + 1) : (interpolations[i - 1].CloseBracePosition + 1), interpolation.OpenBracePosition - 1);
                    if (text.Length > 0)
                    {
                        var token = MakeStringToken(text, text, isVerbatim, SyntaxKind.InterpolatedStringTextToken);
                        builder.Add(SyntaxFactory.InterpolatedStringText(token));
                    }

                    // Add an interpolation
                    var interp = ParseInterpolation(originalText, interpolation, isVerbatim);
                    builder.Add(interp);
                }

                // Add a token for text following the last interpolation
                var lastText = Substring(originalText, interpolations[interpolations.Count - 1].CloseBracePosition + 1, closeQuoteIndex - 1);
                if (lastText.Length > 0)
                {
                    var token = MakeStringToken(lastText, lastText, isVerbatim, SyntaxKind.InterpolatedStringTextToken);
                    builder.Add(SyntaxFactory.InterpolatedStringText(token));
                }
            }

            interpolations.Free();
            var result = SyntaxFactory.InterpolatedStringExpression(openQuote, builder, closeQuote);

            _pool.Free(builder);
            if (error != null)
            {
                result = result.WithDiagnosticsGreen(new[] { error });
            }

            Debug.Assert(originalToken.ToFullString() == result.ToFullString()); // yield from text equals yield from node
            return(CheckFeatureAvailability(result, MessageID.IDS_FeatureInterpolatedStrings));
        }
예제 #2
0
        private ExpressionSyntax ParseInterpolatedStringToken()
        {
            // We don't want to make the scanner stateful (between tokens) if we can possibly avoid it.
            // The approach implemented here is
            //
            // (1) Scan the whole interpolated string literal as a single token. Now the statefulness of
            // the scanner (to match { }'s) is limited to its behavior while scanning a single token.
            //
            // (2) When the parser gets such a token, here, it spins up another scanner / parser on each of
            // the holes and builds a tree for the whole thing (resulting in an InterpolatedStringExpressionSyntax).
            //
            // (3) The parser discards the original token and replaces it with this tree. (In other words,
            // it replaces one token with a different set of tokens that have already been parsed)
            //
            // (4) On an incremental change, we widen the invalidated region to include any enclosing interpolated
            // string nonterminal so that we never reuse tokens inside a changed interpolated string.
            //
            // This has the secondary advantage that it can reasonably be specified.
            //
            // The substitution will end up being invisible to external APIs and clients such as the IDE, as
            // they have no way to ask for the stream of tokens before parsing.

            Debug.Assert(this.CurrentToken.Kind == SyntaxKind.InterpolatedStringToken);
            var originalToken = this.EatToken();

            var originalText = originalToken.ValueText; // this is actually the source text

            Debug.Assert(originalText[0] == '$' || originalText[0] == '@');

            // compute the positions of the interpolations in the original string literal, if there was an error or not,
            // and where the close quote can be found.
            var interpolations = ArrayBuilder <Lexer.Interpolation> .GetInstance();

            rescanInterpolation(out var kind, out var error, out var openQuoteRange, interpolations, out var closeQuoteRange);

            var result = SyntaxFactory.InterpolatedStringExpression(
                getOpenQuote(openQuoteRange), getContent(interpolations), getCloseQuote(closeQuoteRange));

            interpolations.Free();
            if (error != null)
            {
                result = result.WithDiagnosticsGreen(new[] { error });
            }

            Debug.Assert(originalToken.ToFullString() == result.ToFullString()); // yield from text equals yield from node
            return(result);

            void rescanInterpolation(out Lexer.InterpolatedStringKind kind, out SyntaxDiagnosticInfo error, out Range openQuoteRange, ArrayBuilder <Lexer.Interpolation> interpolations, out Range closeQuoteRange)
            {
                using var tempLexer = new Lexer(SourceText.From(originalText), this.Options, allowPreprocessorDirectives: false);
                var info = default(Lexer.TokenInfo);

                tempLexer.ScanInterpolatedStringLiteralTop(ref info, out error, out kind, out openQuoteRange, interpolations, out closeQuoteRange);
            }

            SyntaxToken getOpenQuote(Range openQuoteRange)
            {
                var openQuoteText = originalText[openQuoteRange];

                return(SyntaxFactory.Token(
                           originalToken.GetLeadingTrivia(),
                           kind is Lexer.InterpolatedStringKind.Verbatim ? SyntaxKind.InterpolatedVerbatimStringStartToken : SyntaxKind.InterpolatedStringStartToken,
                           openQuoteText, openQuoteText, trailing: null));
            }

            CodeAnalysis.Syntax.InternalSyntax.SyntaxList <InterpolatedStringContentSyntax> getContent(ArrayBuilder <Lexer.Interpolation> interpolations)
            {
                var builder = _pool.Allocate <InterpolatedStringContentSyntax>();

                if (interpolations.Count == 0)
                {
                    // In the special case when there are no interpolations, we just construct a format string
                    // with no inserts. We must still use String.Format to get its handling of escapes such as {{,
                    // so we still treat it as a composite format string.
                    var text = originalText[new Range(openQuoteRange.End, closeQuoteRange.Start)];
                    if (text.Length > 0)
                    {
                        builder.Add(SyntaxFactory.InterpolatedStringText(MakeInterpolatedStringTextToken(text, kind)));
                    }
                }
                else
                {
                    for (int i = 0; i < interpolations.Count; i++)
                    {
                        var interpolation = interpolations[i];

                        // Add a token for text preceding the interpolation
                        var text = originalText[new Range(
                                                    i == 0 ? openQuoteRange.End : interpolations[i - 1].CloseBraceRange.End,
                                                    interpolation.OpenBraceRange.Start)];
                        if (text.Length > 0)
                        {
                            builder.Add(SyntaxFactory.InterpolatedStringText(MakeInterpolatedStringTextToken(text, kind)));
                        }

                        builder.Add(ParseInterpolation(this.Options, originalText, interpolation, kind));
                    }

                    // Add a token for text following the last interpolation
                    var lastText = originalText[new Range(interpolations[^ 1].CloseBraceRange.End, closeQuoteRange.Start)];