text:
// 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.

#nullable disable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis.EmbeddedLanguages.Common;
using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.EmbeddedLanguages.RegularExpressions
{
    using static EmbeddedSyntaxHelpers;
    using static RegexHelpers;

    using RegexNodeOrToken = EmbeddedSyntaxNodeOrToken<RegexKind, RegexNode>;
    using RegexToken = EmbeddedSyntaxToken<RegexKind>;
    using RegexTrivia = EmbeddedSyntaxTrivia<RegexKind>;

    /// <summary>
    /// Produces a <see cref="RegexTree"/> from a sequence of <see cref="VirtualChar"/> characters.
    ///
    /// Importantly, this parser attempts to replicate diagnostics with almost the exact same text
    /// as the native .NET regex parser.  This is important so that users get an understandable
    /// experience where it appears to them that this is all one cohesive system and that the IDE
    /// will let them discover and fix the same issues they would encounter when previously trying
    /// to just compile and execute these regexes.
    /// </summary>
    /// <remarks>
    /// Invariants we try to maintain (and should consider a bug if we do not): l 1. If the .NET
    /// regex parser does not report an error for a given pattern, we should not either. it would be
    /// very bad if we told the user there was something wrong with there pattern when there really
    /// wasn't.
    ///
    /// 2. If the .NET regex parser does report an error for a given pattern, we should either not
    /// report an error (not recommended) or report the same error at an appropriate location in the
    /// pattern.  Not reporting the error can be confusing as the user will think their pattern is
    /// ok, when it really is not.  However, it can be acceptable to do this as it's not telling
    /// them that something is actually wrong, and it may be too difficult to find and report the
    /// same error.  Note: there is only one time we do this in this parser (see the deviation
    /// documented in <see cref="ParsePossibleEcmascriptBackreferenceEscape"/>).
    ///
    /// Note1: "report the same error" means that we will attempt to report the error using the same
    /// text the .NET regex parser uses for its error messages.  This is so that the user is not
    /// confused when they use the IDE vs running the regex by getting different messages for the
    /// same issue.
    ///
    /// Note2: the above invariants make life difficult at times.  This happens due to the fact that
    /// the .NET parser is multi-pass.  Meaning it does a first scan (which may report errors), then
    /// does the full parse.  This means that it might report an error in a later location during
    /// the initial scan than it would during the parse.  We replicate that behavior to follow the
    /// second invariant.
    ///
    /// Note3: It would be nice if we could check these invariants at runtime, so we could control
    /// our behavior by the behavior of the real .NET regex engine.  For example, if the .NET regex
    /// engine did not report any issues, we could suppress any diagnostics we generated and we
    /// could log an NFW to record which pattern we deviated on so we could fix the issue for a
    /// future release.  However, we cannot do this as the .NET regex engine has no guarantees about
    /// its performance characteristics.  For example, certain regex patterns might end up causing
    /// that engine to consume unbounded amounts of CPU and memory.  This is because the .NET regex
    /// engine is not just a parser, but something that builds an actual recognizer using techniques
    /// that are not necessarily bounded.  As such, while we test ourselves around it during our
    /// tests, we cannot do the same at runtime as part of the IDE.
    ///
    /// This parser was based off the corefx RegexParser based at:
    /// https://github.com/dotnet/corefx/blob/f759243d724f462da0bcef54e86588f8a55352c6/src/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs#L1
    ///
    /// Note4: The .NET parser itself changes over time (for example to fix behavior that even it
    /// thinks is buggy).  When this happens, we have to make a choice as to which behavior to
    /// follow. In general, the overall principle is that we should follow the more lenient
    /// behavior.  If we end up taking the more strict interpretation we risk giving people an error
    /// during design time that they would not get at runtime.  It's far worse to have that than to
    /// not report an error, even though one might happen later.
    /// </remarks>
    internal partial struct RegexParser
    {
        private readonly ImmutableDictionary<string, TextSpan> _captureNamesToSpan;
        private readonly ImmutableDictionary<int, TextSpan> _captureNumbersToSpan;

        private RegexLexer _lexer;
        private RegexOptions _options;
        private RegexToken _currentToken;
        private int _recursionDepth;

        private RegexParser(
            VirtualCharSequence text, RegexOptions options,
            ImmutableDictionary<string, TextSpan> captureNamesToSpan,
            ImmutableDictionary<int, TextSpan> captureNumbersToSpan) : this()
        {
            _lexer = new RegexLexer(text);
            _options = options;

            _captureNamesToSpan = captureNamesToSpan;
            _captureNumbersToSpan = captureNumbersToSpan;

            // Get the first token.  It is allowed to have trivia on it.
            ConsumeCurrentToken(allowTrivia: true);
        }

        /// <summary>
        /// Returns the latest token the lexer has produced, and then asks the lexer to
        /// produce the next token after that.
        /// </summary>
        /// <param name="allowTrivia">Whether or not trivia is allowed on the next token
        /// produced.  In the .NET parser trivia is only allowed on a few constructs,
        /// and our parser mimics that behavior.  Note that even if trivia is allowed,
        /// the type of trivia that can be scanned depends on the current RegexOptions.
        /// For example, if <see cref="RegexOptions.IgnorePatternWhitespace"/> is currently
        /// enabled, then '#...' comments are allowed.  Otherwise, only '(?#...)' comments
        /// are allowed.</param>
        private RegexToken ConsumeCurrentToken(bool allowTrivia)
        {
            var previous = _currentToken;
            _currentToken = _lexer.ScanNextToken(allowTrivia, _options);
            return previous;
        }

        /// <summary>
        /// Given an input text, and set of options, parses out a fully representative syntax tree
        /// and list of diagnostics.  Parsing should always succeed, except in the case of the stack
        /// overflowing.
        /// </summary>
        public static RegexTree TryParse(VirtualCharSequence text, RegexOptions options)
        {
            if (text.IsDefault)
            {
                return null;
            }

            try
            {
                // Parse the tree once, to figure out the capture groups.  These are needed
                // to then parse the tree again, as the captures will affect how we interpret
                // certain things (i.e. escape references) and what errors will be reported.
                //
                // This is necessary as .NET regexes allow references to *future* captures.
                // As such, we don't know when we're seeing a reference if it's to something
                // that exists or not.
                var tree1 = new RegexParser(text, options,
                    ImmutableDictionary<string, TextSpan>.Empty,
                    ImmutableDictionary<int, TextSpan>.Empty).ParseTree();

                var (captureNames, captureNumbers) = CaptureInfoAnalyzer.Analyze(text, tree1.Root, options);

                var tree2 = new RegexParser(
                    text, options, captureNames, captureNumbers).ParseTree();
                return tree2;
            }
            catch (InsufficientExecutionStackException)
            {
                return null;
            }
        }

        private RegexTree ParseTree()
        {
            // Most callers to ParseAlternatingSequences are from group constructs.  As those
            // constructs will have already consumed the open paren, they don't want this sub-call
            // to consume through close-paren tokens as they want that token for themselves.
            // However, we're the topmost call and have not consumed an open paren.  And, we want
            // this call to consume all the way to the end, eating up excess close-paren tokens that
            // are encountered.
            var expression = this.ParseAlternatingSequences(consumeCloseParen: true);
            Debug.Assert(_lexer.Position == _lexer.Text.Length);
            Debug.Assert(_currentToken.Kind == RegexKind.EndOfFile);

            var root = new RegexCompilationUnit(expression, _currentToken);

            var seenDiagnostics = new HashSet<EmbeddedDiagnostic>();
            using var _ = ArrayBuilder<EmbeddedDiagnostic>.GetInstance(out var diagnostics);
            CollectDiagnostics(root, seenDiagnostics, diagnostics);

            return new RegexTree(
                _lexer.Text, root, diagnostics.ToImmutable(),
                _captureNamesToSpan, _captureNumbersToSpan);
        }

        private static void CollectDiagnostics(
            RegexNode node, HashSet<EmbeddedDiagnostic> seenDiagnostics, ArrayBuilder<EmbeddedDiagnostic> diagnostics)
        {
            foreach (var child in node)
            {
                if (child.IsNode)
                {
                    CollectDiagnostics(child.Node, seenDiagnostics, diagnostics);
                }
                else
                {
                    var token = child.Token;
                    foreach (var trivia in token.LeadingTrivia)
                    {
                        AddUniqueDiagnostics(seenDiagnostics, trivia.Diagnostics, diagnostics);
                    }

                    // We never place trailing trivia on regex tokens.
                    Debug.Assert(token.TrailingTrivia.IsEmpty);
                    AddUniqueDiagnostics(seenDiagnostics, token.Diagnostics, diagnostics);
                }
            }
        }

        /// <summary>
        /// It's very common to have duplicated diagnostics.  For example, consider "((". This will
        /// have two 'missing )' diagnostics, both at the end.  Reporting both isn't helpful, so we
        /// filter duplicates out here.
        /// </summary>
        private static void AddUniqueDiagnostics(
            HashSet<EmbeddedDiagnostic> seenDiagnostics, ImmutableArray<EmbeddedDiagnostic> from, ArrayBuilder<EmbeddedDiagnostic> to)
        {
            foreach (var diagnostic in from)
            {
                if (seenDiagnostics.Add(diagnostic))
                {
                    to.Add(diagnostic);
                }
            }
        }

        private RegexExpressionNode ParseAlternatingSequences(bool consumeCloseParen)
        {
            try
            {
                _recursionDepth++;
                StackGuard.EnsureSufficientExecutionStack(_recursionDepth);
                return ParseAlternatingSequencesWorker(consumeCloseParen);
            }
            finally
            {
                _recursionDepth--;
            }
        }

        /// <summary>
        /// Parses out code of the form: ...|...|...
        /// This is the type of code you have at the top level of a regex, or inside any grouping
        /// contruct.  Note that sequences can be empty in .NET regex.  i.e. the following is legal:
        ///
        ///     ...||...
        ///
        /// An empty sequence just means "match at every position in the test string".
        /// </summary>
        private RegexExpressionNode ParseAlternatingSequencesWorker(bool consumeCloseParen)
        {
            RegexExpressionNode current = ParseSequence(consumeCloseParen);

            while (_currentToken.Kind == RegexKind.BarToken)
            {
                // Trivia allowed between the | and the next token.
                current = new RegexAlternationNode(
                    current, ConsumeCurrentToken(allowTrivia: true), ParseSequence(consumeCloseParen));
            }

            return current;
        }

        private RegexSequenceNode ParseSequence(bool consumeCloseParen)
        {
            using var _1 = ArrayBuilder<RegexExpressionNode>.GetInstance(out var builder);
            while (ShouldConsumeSequenceElement(consumeCloseParen))
            {
                var last = builder.Count == 0 ? null : builder.Last();
                builder.Add(ParsePrimaryExpressionAndQuantifiers(last));
            }

            // We wil commonly get tons of text nodes in a row.  For example, the
            // regex `abc` will be three text nodes in a row.  To help save on memory
            // try to merge that into one single text node.
            using var _2 = ArrayBuilder<RegexExpressionNode>.GetInstance(out var sequence);
            MergeTextNodes(builder, sequence);

            return new RegexSequenceNode(sequence.ToImmutable());
        }

        private static void MergeTextNodes(ArrayBuilder<RegexExpressionNode> list, ArrayBuilder<RegexExpressionNode> final)
        {
            // Iterate all the nodes in the sequence we have, adding them directly to
            // `final` if they are not text nodes.  If they are text nodes, we attempt
            // to keep merging them with any following text nodes as long as well.
            for (var index = 0; index < list.Count;)
            {
                var current = list[index];
                if (current.Kind != RegexKind.Text)
                {
                    // Not a text node.  Just add as-is, and move to the next node.
                    index++;
                    final.Add(current);
                    continue;
                }

                // Got a text node.  Try to combine it with all following nodes.
                index = MergeAndAddAdjacentTextNodes(list, final, index);
            }

            return;

            // local functions

            static int MergeAndAddAdjacentTextNodes(
                ArrayBuilder<RegexExpressionNode> list,
                ArrayBuilder<RegexExpressionNode> final,
                int index)
            {
                var startIndex = index;
                var startTextNode = (RegexTextNode)list[startIndex];

                // Keep walking forward as long as we hit text nodes and we can
                // merge that text node with the previous text node.
                index++;
                var lastTextNode = startTextNode;
                for (; index < list.Count; index++)
                {
                    var currentNode = list[index];
                    if (!CanMerge(lastTextNode, currentNode))
                    {
                        // Hit something we couldn't merge with our last text node
                        // Break out and merge what we have so far.  'index' will
                        // be pointing at the right node for our caller.
                        break;
                    }

                    lastTextNode = (RegexTextNode)currentNode;
                }

                // If didn't have multiple text nodes in a row.  Just return the
                // starting node.  Otherwise, create one text node that has a token
                // that spans from the start of the first node to the end of the last node.
                final.Add(startTextNode == lastTextNode
                    ? startTextNode
                    : new RegexTextNode(CreateToken(
                        RegexKind.TextToken, startTextNode.TextToken.LeadingTrivia,
                        VirtualCharSequence.FromBounds(
                            startTextNode.TextToken.VirtualChars,
                            lastTextNode.TextToken.VirtualChars))));

                return index;
            }

            // Local functions
            static bool CanMerge(RegexTextNode lastNode, RegexExpressionNode next)
            {
                if (next.Kind == RegexKind.Text)
                {
                    var lastTextToken = lastNode.TextToken;
                    var nextTextToken = ((RegexTextNode)next).TextToken;

                    // Can't merge if the next text node has leading trivia. Also, conservatively
                    // don't allow merging if there are diagnostics or values for these tokens.
                    // We might be able to support that, but it's easier to not do anything that
                    // might break an expectation someone might have downstream.                    /
                    if (lastTextToken.Diagnostics.Length == 0 &&
                        nextTextToken.Diagnostics.Length == 0 &&
                        lastTextToken.Value == null &&
                        nextTextToken.Value == null &&
                        nextTextToken.LeadingTrivia.Length == 0)
                    {
                        lastTextToken.VirtualChars.AssertAdjacentTo(nextTextToken.VirtualChars);
                        return true;
                    }
                }

                return false;
            }
        }

        private bool ShouldConsumeSequenceElement(bool consumeCloseParen)
        {
            if (_currentToken.Kind == RegexKind.EndOfFile)
            {
                return false;
            }

            if (_currentToken.Kind == RegexKind.BarToken)
            {
                return false;
            }

            if (_currentToken.Kind == RegexKind.CloseParenToken)
            {
                return consumeCloseParen;
            }

            return true;
        }

        private RegexExpressionNode ParsePrimaryExpressionAndQuantifiers(RegexExpressionNode lastExpression)
        {
            var current = ParsePrimaryExpression(lastExpression);
            if (current.Kind == RegexKind.SimpleOptionsGrouping)
            {
                // Simple options (i.e. "(?i-x)" can't have quantifiers attached to them).
                return current;
            }

            return _currentToken.Kind switch
            {
                RegexKind.AsteriskToken => ParseZeroOrMoreQuantifier(current),
                RegexKind.PlusToken => ParseOneOrMoreQuantifier(current),
                RegexKind.QuestionToken => ParseZeroOrOneQuantifier(current),
                RegexKind.OpenBraceToken => TryParseNumericQuantifier(current, _currentToken),
                _ => current,
            };
        }

        private RegexExpressionNode TryParseLazyQuantifier(RegexQuantifierNode quantifier)
        {
            if (_currentToken.Kind != RegexKind.QuestionToken)
            {
                return quantifier;
            }

            // Whitespace allowed after the question and the next sequence element.
            return new RegexLazyQuantifierNode(quantifier,
                ConsumeCurrentToken(allowTrivia: true));
        }

        private RegexExpressionNode ParseZeroOrMoreQuantifier(RegexPrimaryExpressionNode current)
        {
            // Whitespace allowed between the quantifier and the possible following ? or next sequence item.
            return TryParseLazyQuantifier(new RegexZeroOrMoreQuantifierNode(current, ConsumeCurrentToken(allowTrivia: true)));
        }

        private RegexExpressionNode ParseOneOrMoreQuantifier(RegexPrimaryExpressionNode current)
        {
            // Whitespace allowed between the quantifier and the possible following ? or next sequence item.
            return TryParseLazyQuantifier(new RegexOneOrMoreQuantifierNode(current, ConsumeCurrentToken(allowTrivia: true)));
        }

        private RegexExpressionNode ParseZeroOrOneQuantifier(RegexPrimaryExpressionNode current)
        {
            // Whitespace allowed between the quantifier and the possible following ? or next sequence item.
            return TryParseLazyQuantifier(new RegexZeroOrOneQuantifierNode(current, ConsumeCurrentToken(allowTrivia: true)));
        }

        private RegexExpressionNode TryParseNumericQuantifier(
            RegexPrimaryExpressionNode expression, RegexToken openBraceToken)
        {
            var start = _lexer.Position;

            if (!TryParseNumericQuantifierParts(
                    out var firstNumberToken,
                    out var commaToken,
                    out var secondNumberToken,
                    out var closeBraceToken))
            {
                _currentToken = openBraceToken;
                _lexer.Position = start;
                return expression;
            }

            var quantifier = CreateQuantifier(
                expression, openBraceToken, firstNumberToken, commaToken,
                secondNumberToken, closeBraceToken);

            return TryParseLazyQuantifier(quantifier);
        }

        private static RegexQuantifierNode CreateQuantifier(
            RegexPrimaryExpressionNode expression,
            RegexToken openBraceToken, RegexToken firstNumberToken, RegexToken? commaToken,
            RegexToken? secondNumberToken, RegexToken closeBraceToken)
        {
            if (commaToken != null)
            {
                return secondNumberToken != null
                    ? new RegexClosedNumericRangeQuantifierNode(expression, openBraceToken, firstNumberToken, commaToken.Value, secondNumberToken.Value, closeBraceToken)
                    : (RegexQuantifierNode)new RegexOpenNumericRangeQuantifierNode(expression, openBraceToken, firstNumberToken, commaToken.Value, closeBraceToken);
            }

            return new RegexExactNumericQuantifierNode(expression, openBraceToken, firstNumberToken, closeBraceToken);
        }

        private bool TryParseNumericQuantifierParts(
            out RegexToken firstNumberToken, out RegexToken? commaToken,
            out RegexToken? secondNumberToken, out RegexToken closeBraceToken)
        {
            firstNumberToken = default;
            commaToken = null;
            secondNumberToken = null;
            closeBraceToken = default;

            var firstNumber = _lexer.TryScanNumber();
            if (firstNumber == null)
            {
                return false;
            }

            firstNumberToken = firstNumber.Value;

            // Nothing allowed between {x,n}
            ConsumeCurrentToken(allowTrivia: false);

            if (_currentToken.Kind == RegexKind.CommaToken)
            {
                commaToken = _currentToken;

                var start = _lexer.Position;
                secondNumberToken = _lexer.TryScanNumber();

                if (secondNumberToken == null)
                {
                    // Nothing allowed between {x,n}
                    ResetToPositionAndConsumeCurrentToken(start, allowTrivia: false);
                }
                else
                {
                    var secondNumberTokenLocal = secondNumberToken.Value;

                    // Nothing allowed between {x,n}
                    ConsumeCurrentToken(allowTrivia: false);

                    var val1 = (int)firstNumberToken.Value;
                    var val2 = (int)secondNumberTokenLocal.Value;

                    if (val2 < val1)
                    {
                        secondNumberTokenLocal = secondNumberTokenLocal.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                            FeaturesResources.Illegal_x_y_with_x_less_than_y,
                            secondNumberTokenLocal.GetSpan()));
                        secondNumberToken = secondNumberTokenLocal;
                    }
                }
            }

            if (_currentToken.Kind != RegexKind.CloseBraceToken)
            {
                return false;
            }

            // Whitespace allowed between the quantifier and the possible following ? or next sequence item.
            closeBraceToken = ConsumeCurrentToken(allowTrivia: true);
            return true;
        }

        private void ResetToPositionAndConsumeCurrentToken(int position, bool allowTrivia)
        {
            _lexer.Position = position;
            ConsumeCurrentToken(allowTrivia);
        }

        private RegexPrimaryExpressionNode ParsePrimaryExpression(RegexExpressionNode lastExpression)
        {
            switch (_currentToken.Kind)
            {
                case RegexKind.DotToken:
                    return ParseWildcard();
                case RegexKind.CaretToken:
                    return ParseStartAnchor();
                case RegexKind.DollarToken:
                    return ParseEndAnchor();
                case RegexKind.BackslashToken:
                    return ParseEscape(_currentToken, allowTriviaAfterEnd: true);
                case RegexKind.OpenBracketToken:
                    return ParseCharacterClass();
                case RegexKind.OpenParenToken:
                    return ParseGrouping();
                case RegexKind.CloseParenToken:
                    return ParseUnexpectedCloseParenToken();
                case RegexKind.OpenBraceToken:
                    return ParsePossibleUnexpectedNumericQuantifier(lastExpression);
                case RegexKind.AsteriskToken:
                case RegexKind.PlusToken:
                case RegexKind.QuestionToken:
                    return ParseUnexpectedQuantifier(lastExpression);
                default:
                    return ParseText();
            }
        }

        private RegexPrimaryExpressionNode ParsePossibleUnexpectedNumericQuantifier(RegexExpressionNode lastExpression)
        {
            // Native parser looks for something like {0,1} in a top level sequence and reports
            // an explicit error that that's not allowed.  However, something like {0, 1} is fine
            // and is treated as six textual tokens.
            var openBraceToken = _currentToken.With(kind: RegexKind.TextToken);
            var start = _lexer.Position;

            if (TryParseNumericQuantifierParts(
                    out _, out _, out _, out _))
            {
                // Report that a numeric quantifier isn't allowed here.
                CheckQuantifierExpression(lastExpression, ref openBraceToken);
            }

            // Started with { but wasn't a numeric quantifier.  This is totally legal and is just
            // a textual sequence.  Restart, scanning this token as a normal sequence element.
            ResetToPositionAndConsumeCurrentToken(start, allowTrivia: true);
            return new RegexTextNode(openBraceToken);
        }

        private RegexPrimaryExpressionNode ParseUnexpectedCloseParenToken()
        {
            var token = _currentToken.With(kind: RegexKind.TextToken).AddDiagnosticIfNone(
                new EmbeddedDiagnostic(FeaturesResources.Too_many_close_parens, _currentToken.GetSpan()));

            // Technically, since an error occurred, we can do whatever we want here.  However,
            // the spirit of the native parser is that top level sequence elements are allowed
            // to have trivia.  So that's the behavior we mimic.
            ConsumeCurrentToken(allowTrivia: true);
            return new RegexTextNode(token);
        }

        private RegexPrimaryExpressionNode ParseText()
        {
            var token = ConsumeCurrentToken(allowTrivia: true);
            Debug.Assert(token.Value == null);

            // Allow trivia between this piece of text and the next sequence element
            return new RegexTextNode(token.With(kind: RegexKind.TextToken));
        }

        private RegexPrimaryExpressionNode ParseEndAnchor()
        {
            // Allow trivia between this anchor and the next sequence element
            return new RegexAnchorNode(RegexKind.EndAnchor, ConsumeCurrentToken(allowTrivia: true));
        }

        private RegexPrimaryExpressionNode ParseStartAnchor()
        {
            // Allow trivia between this anchor and the next sequence element
            return new RegexAnchorNode(RegexKind.StartAnchor, ConsumeCurrentToken(allowTrivia: true));
        }

        private RegexPrimaryExpressionNode ParseWildcard()
        {
            // Allow trivia between the . and the next sequence element
            return new RegexWildcardNode(ConsumeCurrentToken(allowTrivia: true));
        }

        private RegexGroupingNode ParseGrouping()
        {
            var start = _lexer.Position;

            // Check what immediately follows the (.  If we have (? it is processed specially.
            // However, we do not treat (? the same as ( ?
            var openParenToken = ConsumeCurrentToken(allowTrivia: false);

            switch (_currentToken.Kind)
            {
                case RegexKind.QuestionToken:
                    return ParseGroupQuestion(openParenToken, _currentToken);

                default:
                    // Wasn't (? just parse this as a normal group.
                    _lexer.Position = start;
                    return ParseSimpleGroup(openParenToken);
            }
        }

        private RegexToken ParseGroupingCloseParen()
        {
            switch (_currentToken.Kind)
            {
                case RegexKind.CloseParenToken:
                    // Grouping completed normally.  Allow trivia between it and the next sequence element.
                    return ConsumeCurrentToken(allowTrivia: true);

                default:
                    return CreateMissingToken(RegexKind.CloseParenToken).AddDiagnosticIfNone(
                        new EmbeddedDiagnostic(FeaturesResources.Not_enough_close_parens, GetTokenStartPositionSpan(_currentToken)));
            }
        }

        private RegexSimpleGroupingNode ParseSimpleGroup(RegexToken openParenToken)
            => new(
                openParenToken, ParseGroupingEmbeddedExpression(_options), ParseGroupingCloseParen());

        private RegexExpressionNode ParseGroupingEmbeddedExpression(RegexOptions embeddedOptions)
        {
            // Save and restore options when we go into, and pop out of a group node.
            var currentOptions = _options;
            _options = embeddedOptions;

            // We're parsing the embedded sequence inside the current group.  As this is a sequence
            // we want to allow trivia between the current token we're on, and the first token
            // of the embedded sequence.
            ConsumeCurrentToken(allowTrivia: true);

            // When parsing out the sequence don't grab the close paren, that will be for our caller
            // to get.
            var expression = this.ParseAlternatingSequences(consumeCloseParen: false);
            _options = currentOptions;
            return expression;
        }

        private TextSpan GetTokenSpanIncludingEOF(RegexToken token)
            => token.Kind == RegexKind.EndOfFile
                ? GetTokenStartPositionSpan(token)
                : token.GetSpan();

        private TextSpan GetTokenStartPositionSpan(RegexToken token)
        {
            return token.Kind == RegexKind.EndOfFile
                ? new TextSpan(_lexer.Text.Last().Span.End, 0)
                : new TextSpan(token.VirtualChars[0].Span.Start, 0);
        }

        private RegexGroupingNode ParseGroupQuestion(RegexToken openParenToken, RegexToken questionToken)
        {
            var optionsToken = _lexer.TryScanOptions();
            if (optionsToken != null)
            {
                return ParseOptionsGroupingNode(openParenToken, questionToken, optionsToken.Value);
            }

            var afterQuestionPos = _lexer.Position;

            // Lots of possible options when we see (?.  Look at the immediately following character
            // (without any allowed spaces) to decide what to parse out next.
            ConsumeCurrentToken(allowTrivia: false);
            switch (_currentToken.Kind)
            {
                case RegexKind.LessThanToken:
                    // (?<=...) or (?<!...) or (?<...>...) or (?<...-...>...)
                    return ParseLookbehindOrNamedCaptureOrBalancingGrouping(openParenToken, questionToken);

                case RegexKind.SingleQuoteToken:
                    //  (?'...'...) or (?'...-...'...)
                    return ParseNamedCaptureOrBalancingGrouping(
                        openParenToken, questionToken, _currentToken);

                case RegexKind.OpenParenToken:
                    // alternation construct (?(...) | )
                    return ParseConditionalGrouping(openParenToken, questionToken);

                case RegexKind.ColonToken:
                    return ParseNonCapturingGroupingNode(openParenToken, questionToken);

                case RegexKind.EqualsToken:
                    return ParsePositiveLookaheadGrouping(openParenToken, questionToken);

                case RegexKind.ExclamationToken:
                    return ParseNegativeLookaheadGrouping(openParenToken, questionToken);

                case RegexKind.GreaterThanToken:
                    return ParseAtomicGrouping(openParenToken, questionToken);

                default:
                    if (_currentToken.Kind != RegexKind.CloseParenToken)
                    {
                        // Native parser reports "Unrecognized grouping construct", *except* for (?)
                        openParenToken = openParenToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                            FeaturesResources.Unrecognized_grouping_construct,
                            openParenToken.GetSpan()));
                    }

                    break;
            }

            // (?)
            // Parse this as a normal group. The question will immediately error as it's a
            // quantifier not following anything.
            _lexer.Position = afterQuestionPos - 1;
            return ParseSimpleGroup(openParenToken);
        }

        private RegexConditionalGroupingNode ParseConditionalGrouping(RegexToken openParenToken, RegexToken questionToken)
        {
            var innerOpenParenToken = _currentToken;
            var afterInnerOpenParen = _lexer.Position;

            var captureToken = _lexer.TryScanNumberOrCaptureName();
            if (captureToken == null)
            {
                return ParseConditionalExpressionGrouping(openParenToken, questionToken);
            }

            var capture = captureToken.Value;

            RegexToken innerCloseParenToken;
            if (capture.Kind == RegexKind.NumberToken)
            {
                // If it's a numeric group, it has to be immediately followed by a ) and the
                // numeric reference has to exist.
                //
                // That means that (?(4 ) is not treated as an embedded expression but as an
                // error.  This is different from (?(a ) which will be treated as an embedded
                // expression, and different from (?(a) will be treated as an embedded
                // expression or capture group depending on if 'a' is a existing capture name.

                ConsumeCurrentToken(allowTrivia: false);
                if (_currentToken.Kind == RegexKind.CloseParenToken)
                {
                    innerCloseParenToken = _currentToken;
                    if (!HasCapture((int)capture.Value))
                    {
                        capture = capture.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                            FeaturesResources.Reference_to_undefined_group,
                            capture.GetSpan()));
                    }
                }
                else
                {
                    innerCloseParenToken = CreateMissingToken(RegexKind.CloseParenToken);
                    capture = capture.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                        FeaturesResources.Malformed,
                        capture.GetSpan()));
                    MoveBackBeforePreviousScan();
                }
            }
            else
            {
                // If it's a capture name, it's ok if that capture doesn't exist.  In that case we
                // will just treat this as an conditional expression.
                if (!HasCapture((string)capture.Value))
                {
                    _lexer.Position = afterInnerOpenParen;
                    return ParseConditionalExpressionGrouping(openParenToken, questionToken);
                }

                // Capture name existed.  For this to be a capture grouping it exactly has to
                // match (?(a)   anything other than a close paren after the ) will make this
                // into a conditional expression.
                ConsumeCurrentToken(allowTrivia: false);
                if (_currentToken.Kind != RegexKind.CloseParenToken)
                {
                    _lexer.Position = afterInnerOpenParen;
                    return ParseConditionalExpressionGrouping(openParenToken, questionToken);
                }

                innerCloseParenToken = _currentToken;
            }

            // Was (?(name) or (?(num)  and name/num was a legal capture name.  Parse
            // this out as a conditional grouping.  Because we're going to be parsing out
            // an embedded sequence, allow trivia before the first element.
            ConsumeCurrentToken(allowTrivia: true);
            var result = ParseConditionalGroupingResult();

            return new RegexConditionalCaptureGroupingNode(
                openParenToken, questionToken,
                innerOpenParenToken, capture, innerCloseParenToken,
                result, ParseGroupingCloseParen());
        }

        private bool HasCapture(int value)
            => _captureNumbersToSpan.ContainsKey(value);

        private bool HasCapture(string value)
            => _captureNamesToSpan.ContainsKey(value);

        private void MoveBackBeforePreviousScan()
        {
            if (_currentToken.Kind != RegexKind.EndOfFile)
            {
                // Move back to un-consume whatever we just consumed.
                _lexer.Position--;
            }
        }

        private RegexConditionalGroupingNode ParseConditionalExpressionGrouping(
            RegexToken openParenToken, RegexToken questionToken)
        {
            // Reproduce very specific errors the .NET regex parser looks for.  Technically,
            // we would error out in these cases no matter what.  However, it means we can
            // stringently enforce that our parser produces the same errors as the native one.
            //
            // Move back before the (
            _lexer.Position--;
            if (_lexer.IsAt("(?#"))
            {
                var pos = _lexer.Position;
                var comment = _lexer.ScanComment(options: default);
                _lexer.Position = pos;

                if (comment.Value.Diagnostics.Length > 0)
                {
                    openParenToken = openParenToken.AddDiagnosticIfNone(comment.Value.Diagnostics[0]);
                }
                else
                {
                    openParenToken = openParenToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                        FeaturesResources.Alternation_conditions_cannot_be_comments,
                        openParenToken.GetSpan()));
                }
            }
            else if (_lexer.IsAt("(?'"))
            {
                openParenToken = openParenToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Alternation_conditions_do_not_capture_and_cannot_be_named,
                    openParenToken.GetSpan()));
            }
            else if (_lexer.IsAt("(?<"))
            {
                if (!_lexer.IsAt("(?<!") &&
                    !_lexer.IsAt("(?<="))
                {
                    openParenToken = openParenToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                        FeaturesResources.Alternation_conditions_do_not_capture_and_cannot_be_named,
                        openParenToken.GetSpan()));
                }
            }

            // Consume the ( once more.
            ConsumeCurrentToken(allowTrivia: false);
            Debug.Assert(_currentToken.Kind == RegexKind.OpenParenToken);

            // Parse out the grouping that starts with the second open paren in (?(
            // this will get us to (?(...)
            var grouping = ParseGrouping();

            // Now parse out the embedded expression that follows that.  this will get us to
            // (?(...)...
            var result = ParseConditionalGroupingResult();

            // Finally, grab the close paren and produce (?(...)...)
            return new RegexConditionalExpressionGroupingNode(
                openParenToken, questionToken,
                grouping, result, ParseGroupingCloseParen());
        }

        private RegexExpressionNode ParseConditionalGroupingResult()
        {
            var currentOptions = _options;
            var result = this.ParseAlternatingSequences(consumeCloseParen: false);
            _options = currentOptions;

            result = CheckConditionalAlternation(result);
            return result;
        }

        private static RegexExpressionNode CheckConditionalAlternation(RegexExpressionNode result)
        {
            if (result is RegexAlternationNode topAlternation &&
                topAlternation.Left is RegexAlternationNode)
            {
                return new RegexAlternationNode(
                    topAlternation.Left,
                    topAlternation.BarToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                        FeaturesResources.Too_many_bars_in_conditional_grouping,
                        topAlternation.BarToken.GetSpan())),
                    topAlternation.Right);
            }

            return result;
        }

        private RegexGroupingNode ParseLookbehindOrNamedCaptureOrBalancingGrouping(
            RegexToken openParenToken, RegexToken questionToken)
        {
            var start = _lexer.Position;

            // We have  (?<  Look for  (?<=  or  (?<!
            var lessThanToken = ConsumeCurrentToken(allowTrivia: false);

            switch (_currentToken.Kind)
            {
                case RegexKind.EqualsToken:
                    return new RegexPositiveLookbehindGroupingNode(
                        openParenToken, questionToken, lessThanToken, _currentToken,
                        ParseGroupingEmbeddedExpression(_options | RegexOptions.RightToLeft), ParseGroupingCloseParen());

                case RegexKind.ExclamationToken:
                    return new RegexNegativeLookbehindGroupingNode(
                        openParenToken, questionToken, lessThanToken, _currentToken,
                        ParseGroupingEmbeddedExpression(_options | RegexOptions.RightToLeft), ParseGroupingCloseParen());

                default:
                    // Didn't have a lookbehind group.  Parse out as  (?<...>  or  (?<...-...>
                    _lexer.Position = start;
                    return ParseNamedCaptureOrBalancingGrouping(openParenToken, questionToken, lessThanToken);
            }
        }

        private RegexGroupingNode ParseNamedCaptureOrBalancingGrouping(
            RegexToken openParenToken, RegexToken questionToken, RegexToken openToken)
        {
            if (_lexer.Position == _lexer.Text.Length)
            {
                openParenToken = openParenToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Unrecognized_grouping_construct,
                    GetSpan(openParenToken, openToken)));
            }

            // (?<...>...) or (?<...-...>...)
            // (?'...'...) or (?'...-...'...)
            var captureToken = _lexer.TryScanNumberOrCaptureName();
            if (captureToken == null)
            {
                // Can't have any trivia between the elements in this grouping header.
                ConsumeCurrentToken(allowTrivia: false);
                captureToken = CreateMissingToken(RegexKind.CaptureNameToken);

                if (_currentToken.Kind == RegexKind.MinusToken)
                {
                    return ParseBalancingGrouping(
                        openParenToken, questionToken, openToken, captureToken.Value);
                }
                else
                {
                    openParenToken = openParenToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                        FeaturesResources.Invalid_group_name_Group_names_must_begin_with_a_word_character,
                        GetTokenSpanIncludingEOF(_currentToken)));

                    // If we weren't at the end of the text, go back to before whatever character
                    // we just consumed.
                    MoveBackBeforePreviousScan();
                }
            }

            var capture = captureToken.Value;
            if (capture.Kind == RegexKind.NumberToken && (int)capture.Value == 0)
            {
                capture = capture.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Capture_number_cannot_be_zero,
                    capture.GetSpan()));
            }

            // Can't have any trivia between the elements in this grouping header.
            ConsumeCurrentToken(allowTrivia: false);

            if (_currentToken.Kind == RegexKind.MinusToken)
            {
                // Have  (?<...-  parse out the balancing group form.
                return ParseBalancingGrouping(
                    openParenToken, questionToken,
                    openToken, capture);
            }

            var closeToken = ParseCaptureGroupingCloseToken(ref openParenToken, openToken);

            return new RegexCaptureGroupingNode(
                openParenToken, questionToken,
                openToken, capture, closeToken,
                ParseGroupingEmbeddedExpression(_options), ParseGroupingCloseParen());
        }

        private RegexToken ParseCaptureGroupingCloseToken(ref RegexToken openParenToken, RegexToken openToken)
        {
            if ((openToken.Kind == RegexKind.LessThanToken && _currentToken.Kind == RegexKind.GreaterThanToken) ||
                (openToken.Kind == RegexKind.SingleQuoteToken && _currentToken.Kind == RegexKind.SingleQuoteToken))
            {
                return _currentToken;
            }

            if (_currentToken.Kind == RegexKind.EndOfFile)
            {
                openParenToken = openParenToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Unrecognized_grouping_construct,
                    GetSpan(openParenToken, openToken)));
            }
            else
            {
                openParenToken = openParenToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Invalid_group_name_Group_names_must_begin_with_a_word_character,
                    _currentToken.GetSpan()));

                // Rewind to where we were before seeing this bogus character.
                _lexer.Position--;
            }

            return CreateMissingToken(
                openToken.Kind == RegexKind.LessThanToken
                    ? RegexKind.GreaterThanToken : RegexKind.SingleQuoteToken);
        }

        private RegexBalancingGroupingNode ParseBalancingGrouping(
            RegexToken openParenToken, RegexToken questionToken,
            RegexToken openToken, RegexToken firstCapture)
        {
            var minusToken = _currentToken;
            var secondCapture = _lexer.TryScanNumberOrCaptureName();
            if (secondCapture == null)
            {
                // Invalid group name: Group names must begin with a word character
                ConsumeCurrentToken(allowTrivia: false);

                openParenToken = openParenToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Invalid_group_name_Group_names_must_begin_with_a_word_character,
                    GetTokenSpanIncludingEOF(_currentToken)));

                // If we weren't at the end of the text, go back to before whatever character
                // we just consumed.
                MoveBackBeforePreviousScan();
                secondCapture = CreateMissingToken(RegexKind.CaptureNameToken);
            }

            var second = secondCapture.Value;
            CheckCapture(ref second);

            // Can't have any trivia between the elements in this grouping header.
            ConsumeCurrentToken(allowTrivia: false);
            var closeToken = ParseCaptureGroupingCloseToken(ref openParenToken, openToken);

            return new RegexBalancingGroupingNode(
                openParenToken, questionToken,
                openToken, firstCapture, minusToken, second, closeToken,
                ParseGroupingEmbeddedExpression(_options), ParseGroupingCloseParen());
        }

        private void CheckCapture(ref RegexToken captureToken)
        {
            if (captureToken.IsMissing)
            {
                // Don't need to check for a synthesized error capture token.
                return;
            }

            if (captureToken.Kind == RegexKind.NumberToken)
            {
                var val = (int)captureToken.Value;
                if (!HasCapture(val))
                {
                    captureToken = captureToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                        string.Format(FeaturesResources.Reference_to_undefined_group_number_0, val),
                        captureToken.GetSpan()));
                }
            }
            else
            {
                var val = (string)captureToken.Value;
                if (!HasCapture(val))
                {
                    captureToken = captureToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                        string.Format(FeaturesResources.Reference_to_undefined_group_name_0, val),
                        captureToken.GetSpan()));
                }
            }
        }

        private RegexNonCapturingGroupingNode ParseNonCapturingGroupingNode(RegexToken openParenToken, RegexToken questionToken)
            => new(
                openParenToken, questionToken, _currentToken,
                ParseGroupingEmbeddedExpression(_options), ParseGroupingCloseParen());

        private RegexPositiveLookaheadGroupingNode ParsePositiveLookaheadGrouping(RegexToken openParenToken, RegexToken questionToken)
            => new(
                openParenToken, questionToken, _currentToken,
                ParseGroupingEmbeddedExpression(_options & ~RegexOptions.RightToLeft), ParseGroupingCloseParen());

        private RegexNegativeLookaheadGroupingNode ParseNegativeLookaheadGrouping(RegexToken openParenToken, RegexToken questionToken)
            => new(
                openParenToken, questionToken, _currentToken,
                ParseGroupingEmbeddedExpression(_options & ~RegexOptions.RightToLeft), ParseGroupingCloseParen());

        private RegexAtomicGroupingNode ParseAtomicGrouping(RegexToken openParenToken, RegexToken questionToken)
            => new(
                openParenToken, questionToken, _currentToken,
                ParseGroupingEmbeddedExpression(_options), ParseGroupingCloseParen());

        private RegexGroupingNode ParseOptionsGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken optionsToken)
        {
            // Only (?opts:...) or (?opts) are allowed.  After the opts must be a : or )
            ConsumeCurrentToken(allowTrivia: false);
            switch (_currentToken.Kind)
            {
                case RegexKind.CloseParenToken:
                    // Allow trivia after the options and the next element in the sequence.
                    _options = GetNewOptionsFromToken(_options, optionsToken);
                    return new RegexSimpleOptionsGroupingNode(
                        openParenToken, questionToken, optionsToken,
                        ConsumeCurrentToken(allowTrivia: true));

                case RegexKind.ColonToken:
                    return ParseNestedOptionsGroupingNode(openParenToken, questionToken, optionsToken);

                default:
                    return new RegexSimpleOptionsGroupingNode(
                        openParenToken, questionToken, optionsToken,
                        CreateMissingToken(RegexKind.CloseParenToken).AddDiagnosticIfNone(
                            new EmbeddedDiagnostic(FeaturesResources.Unrecognized_grouping_construct, openParenToken.GetSpan())));
            }
        }

        private RegexNestedOptionsGroupingNode ParseNestedOptionsGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken optionsToken)
            => new(
                openParenToken, questionToken, optionsToken, _currentToken,
                ParseGroupingEmbeddedExpression(GetNewOptionsFromToken(_options, optionsToken)), ParseGroupingCloseParen());

        private static bool IsTextChar(RegexToken currentToken, char ch)
            => currentToken.Kind == RegexKind.TextToken && currentToken.VirtualChars.Length == 1 && currentToken.VirtualChars[0].Value == ch;

        private static RegexOptions GetNewOptionsFromToken(RegexOptions currentOptions, RegexToken optionsToken)
        {
            var copy = currentOptions;
            var on = true;
            foreach (var ch in optionsToken.VirtualChars)
            {
                switch (ch.Value)
                {
                    case '-': on = false; break;
                    case '+': on = true; break;
                    default:
                        var newOption = OptionFromCode(ch);
                        if (on)
                        {
                            copy |= newOption;
                        }
                        else
                        {
                            copy &= ~newOption;
                        }

                        break;
                }
            }

            return copy;
        }

        private static RegexOptions OptionFromCode(VirtualChar ch)
        {
            switch (ch.Value)
            {
                case 'i': case 'I': return RegexOptions.IgnoreCase;
                case 'm': case 'M': return RegexOptions.Multiline;
                case 'n': case 'N': return RegexOptions.ExplicitCapture;
                case 's': case 'S': return RegexOptions.Singleline;
                case 'x': case 'X': return RegexOptions.IgnorePatternWhitespace;
                default:
                    throw new InvalidOperationException();
            }
        }

        private RegexBaseCharacterClassNode ParseCharacterClass()
        {
            var openBracketToken = _currentToken;
            Debug.Assert(openBracketToken.Kind == RegexKind.OpenBracketToken);
            var caretToken = CreateMissingToken(RegexKind.CaretToken);
            var closeBracketToken = CreateMissingToken(RegexKind.CloseBracketToken);

            // trivia is not allowed anywhere in a character class
            ConsumeCurrentToken(allowTrivia: false);
            if (_currentToken.Kind == RegexKind.CaretToken)
            {
                caretToken = _currentToken;
            }
            else
            {
                MoveBackBeforePreviousScan();
            }

            // trivia is not allowed anywhere in a character class
            ConsumeCurrentToken(allowTrivia: false);

            using var _1 = ArrayBuilder<RegexExpressionNode>.GetInstance(out var builder);
            while (_currentToken.Kind != RegexKind.EndOfFile)
            {
                Debug.Assert(_currentToken.VirtualChars.Length == 1);

                if (_currentToken.Kind == RegexKind.CloseBracketToken && builder.Count > 0)
                {
                    // Allow trivia after the character class, and whatever is next in the sequence.
                    closeBracketToken = ConsumeCurrentToken(allowTrivia: true);
                    break;
                }

                ParseCharacterClassComponents(builder);
            }

            // We wil commonly get tons of text nodes in a row.  For example, the
            // regex `[abc]` will be three text nodes in a row.  To help save on memory
            // try to merge that into one single text node.
            using var _2 = ArrayBuilder<RegexExpressionNode>.GetInstance(out var contents);
            MergeTextNodes(builder, contents);

            if (closeBracketToken.IsMissing)
            {
                closeBracketToken = closeBracketToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Unterminated_character_class_set,
                    GetTokenStartPositionSpan(_currentToken)));
            }

            var components = new RegexSequenceNode(contents.ToImmutable());
            return caretToken.IsMissing
                ? (RegexBaseCharacterClassNode)new RegexCharacterClassNode(openBracketToken, components, closeBracketToken)
                : new RegexNegatedCharacterClassNode(openBracketToken, caretToken, components, closeBracketToken);
        }

        private void ParseCharacterClassComponents(ArrayBuilder<RegexExpressionNode> components)
        {
            var left = ParseSingleCharacterClassComponent(isFirst: components.Count == 0, afterRangeMinus: false);
            if (left.Kind == RegexKind.CharacterClassEscape ||
                left.Kind == RegexKind.CategoryEscape ||
                IsEscapedMinus(left))
            {
                // \s or \p{Lu} or \- on the left of a minus doesn't start a range. If there is a following
                // minus, it's just treated textually.
                components.Add(left);
                return;
            }

            if (_currentToken.Kind == RegexKind.MinusToken && !_lexer.IsAt("]"))
            {
                // trivia is not allowed anywhere in a character class
                var minusToken = ConsumeCurrentToken(allowTrivia: false);

                if (_currentToken.Kind == RegexKind.OpenBracketToken)
                {
                    components.Add(left);
                    components.Add(ParseCharacterClassSubtractionNode(minusToken));
                }
                else
                {
                    // Note that behavior of parsing here changed in .net.  See issue:
                    // https://github.com/dotnet/corefx/issues/31786
                    //
                    // We follow the latest behavior in .net which parses things correctly.
                    var right = ParseSingleCharacterClassComponent(isFirst: false, afterRangeMinus: true);

                    if (TryGetRangeComponentValue(left, out var leftCh) &&
                        TryGetRangeComponentValue(right, out var rightCh) &&
                        leftCh > rightCh)
                    {
                        minusToken = minusToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                            FeaturesResources.x_y_range_in_reverse_order,
                            minusToken.GetSpan()));
                    }

                    components.Add(new RegexCharacterClassRangeNode(left, minusToken, right));
                }
            }
            else
            {
                components.Add(left);
            }
        }

        private static bool IsEscapedMinus(RegexNode node)
            => node is RegexSimpleEscapeNode simple && IsTextChar(simple.TypeToken, '-');

        private bool TryGetRangeComponentValue(RegexExpressionNode component, out int ch)
        {
            // Don't bother examining the component if it has any errors already.  This also means
            // we don't have to worry about running into invalid escape sequences and the like.
            if (!HasProblem(component))
            {
                return TryGetRangeComponentValueWorker(component, out ch);
            }

            ch = default;
            return false;
        }

        private bool TryGetRangeComponentValueWorker(RegexNode component, out int ch)
        {
            switch (component.Kind)
            {
                case RegexKind.SimpleEscape:
                    var escapeNode = (RegexSimpleEscapeNode)component;
                    ch = MapEscapeChar(escapeNode.TypeToken.VirtualChars[0]).Value;
                    return true;

                case RegexKind.ControlEscape:
                    var controlEscape = (RegexControlEscapeNode)component;
                    var controlCh = controlEscape.ControlToken.VirtualChars[0].Value;

                    // \ca interpreted as \cA
                    if (controlCh >= 'a' && controlCh <= 'z')
                    {
                        controlCh -= (char)('a' - 'A');
                    }

                    // The control characters have values mapping from the A-Z range to numeric
                    // values 1-26.  So, to map that, we subtract 'A' from the value (which would
                    // give us 0-25) and then add '1' back to it.
                    ch = controlCh - 'A' + 1;
                    return true;

                case RegexKind.OctalEscape:
                    ch = GetCharValue(((RegexOctalEscapeNode)component).OctalText, withBase: 8);
                    return true;

                case RegexKind.HexEscape:
                    ch = GetCharValue(((RegexHexEscapeNode)component).HexText, withBase: 16);
                    return true;

                case RegexKind.UnicodeEscape:
                    ch = GetCharValue(((RegexUnicodeEscapeNode)component).HexText, withBase: 16);
                    return true;

                case RegexKind.PosixProperty:
                    // When the native parser sees [:...:] it treats this as if it just saw '[' and skipped the
                    // rest.
                    ch = '[';
                    return true;

                case RegexKind.Text:
                    ch = ((RegexTextNode)component).TextToken.VirtualChars[0].Value;
                    return true;

                case RegexKind.Sequence:
                    var sequence = (RegexSequenceNode)component;
#if DEBUG
                    Debug.Assert(sequence.ChildCount > 0);
                    for (int i = 0, n = sequence.ChildCount - 1; i < n; i++)
                    {
                        Debug.Assert(IsEscapedMinus(sequence.ChildAt(i).Node));
                    }
#endif

                    var last = sequence.ChildAt(sequence.ChildCount - 1).Node;
                    if (IsEscapedMinus(last))
                    {
                        break;
                    }

                    return TryGetRangeComponentValueWorker(last, out ch);
            }

            ch = default;
            return false;
        }

        private static int GetCharValue(RegexToken hexText, int withBase)
        {
            unchecked
            {
                var total = 0;
                foreach (var vc in hexText.VirtualChars)
                {
                    total *= withBase;
                    total += HexValue(vc);
                }

                return total;
            }
        }

        private static int HexValue(VirtualChar ch)
        {
            Debug.Assert(RegexLexer.IsHexChar(ch));
            unchecked
            {
                var temp = ch.Value - '0';
                if (temp >= 0 && temp <= 9)
                    return temp;

                temp = ch.Value - 'a';
                if (temp >= 0 && temp <= 5)
                    return temp + 10;

                temp = ch.Value - 'A';
                if (temp >= 0 && temp <= 5)
                    return temp + 10;
            }

            throw new InvalidOperationException();
        }

        private bool HasProblem(RegexNodeOrToken component)
        {
            if (component.IsNode)
            {
                foreach (var child in component.Node)
                {
                    if (HasProblem(child))
                    {
                        return true;
                    }
                }
            }
            else
            {
                var token = component.Token;
                if (token.IsMissing ||
                    token.Diagnostics.Length > 0)
                {
                    return true;
                }

                foreach (var trivia in token.LeadingTrivia)
                {
                    if (trivia.Diagnostics.Length > 0)
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private RegexPrimaryExpressionNode ParseSingleCharacterClassComponent(bool isFirst, bool afterRangeMinus)
        {
            if (_currentToken.Kind == RegexKind.BackslashToken && _lexer.Position < _lexer.Text.Length)
            {
                var backslashToken = _currentToken;

                // trivia is not allowed anywhere in a character class, and definitely not between
                // a \ and the following character.
                ConsumeCurrentToken(allowTrivia: false);
                Debug.Assert(_currentToken.VirtualChars.Length == 1);

                var nextChar = _currentToken.VirtualChars[0];
                switch (nextChar.Value)
                {
                    case 'D':
                    case 'd':
                    case 'S':
                    case 's':
                    case 'W':
                    case 'w':
                    case 'p':
                    case 'P':
                        if (afterRangeMinus)
                        {
                            backslashToken = backslashToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                                string.Format(FeaturesResources.Cannot_include_class_0_in_character_range, nextChar),
                                GetSpan(backslashToken, _currentToken)));
                        }

                        // move back before the character we just scanned.
                        // trivia is not allowed anywhere in a character class.

                        // The above list are character class and category escapes.  ParseEscape can
                        // handle both of those, so we just defer to it.
                        _lexer.Position--;
                        return ParseEscape(backslashToken, allowTriviaAfterEnd: false);

                    case '-':
                        // trivia is not allowed anywhere in a character class.

                        // We just let the basic consumption code pull out a token for us, we then
                        // convert that to text since we treat all characters after the - as text no
                        // matter what.
                        return new RegexSimpleEscapeNode(
                            backslashToken, ConsumeCurrentToken(allowTrivia: false).With(kind: RegexKind.TextToken));

                    default:
                        // trivia is not allowed anywhere in a character class.

                        // Note: it is very intentional that we're calling ParseCharEscape and not
                        // ParseEscape.  Normal escapes are not interpreted the same way inside a
                        // character class.  For example \b is not an anchor in a character class.
                        // And things like \k'...' are not k-captures, etc. etc.
                        _lexer.Position--;
                        return ParseCharEscape(backslashToken, allowTriviaAfterEnd: false);
                }
            }

            if (!afterRangeMinus &&
                !isFirst &&
                _currentToken.Kind == RegexKind.MinusToken &&
                _lexer.IsAt("["))
            {
                // have a trailing subtraction.
                // trivia is not allowed anywhere in a character class
                return ParseCharacterClassSubtractionNode(
                    ConsumeCurrentToken(allowTrivia: false));
            }

            // From the .NET regex code:
            // This is code for Posix style properties - [:Ll:] or [:IsTibetan:].
            // It currently doesn't do anything other than skip the whole thing!
            if (!afterRangeMinus && _currentToken.Kind == RegexKind.OpenBracketToken && _lexer.IsAt(":"))
            {
                var beforeBracketPos = _lexer.Position - 1;
                // trivia is not allowed anywhere in a character class
                ConsumeCurrentToken(allowTrivia: false);

                var captureName = _lexer.TryScanCaptureName();
                if (captureName.HasValue && _lexer.IsAt(":]"))
                {
                    _lexer.Position += 2;
                    var textChars = _lexer.GetSubPattern(beforeBracketPos, _lexer.Position);
                    var token = CreateToken(RegexKind.TextToken, ImmutableArray<RegexTrivia>.Empty, textChars);

                    // trivia is not allowed anywhere in a character class
                    ConsumeCurrentToken(allowTrivia: false);
                    return new RegexPosixPropertyNode(token);
                }
                else
                {
                    // Reset to back where we were.
                    // trivia is not allowed anywhere in a character class
                    _lexer.Position = beforeBracketPos;
                    ConsumeCurrentToken(allowTrivia: false);
                    Debug.Assert(_currentToken.Kind == RegexKind.OpenBracketToken);
                }
            }

            // trivia is not allowed anywhere in a character class
            return new RegexTextNode(
                ConsumeCurrentToken(allowTrivia: false).With(kind: RegexKind.TextToken));
        }

        private RegexPrimaryExpressionNode ParseCharacterClassSubtractionNode(RegexToken minusToken)
        {
            var charClass = ParseCharacterClass();

            if (_currentToken.Kind != RegexKind.CloseBracketToken && _currentToken.Kind != RegexKind.EndOfFile)
            {
                minusToken = minusToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.A_subtraction_must_be_the_last_element_in_a_character_class,
                    GetTokenStartPositionSpan(minusToken)));
            }

            return new RegexCharacterClassSubtractionNode(minusToken, charClass);
        }

        /// <summary>
        /// Parses out an escape sequence.  Escape sequences are allowed in top level sequences
        /// and in character classes.  In a top level sequence trivia will be allowed afterwards,
        /// but in a character class trivia is not allowed afterwards.
        /// </summary>
        private RegexEscapeNode ParseEscape(RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            Debug.Assert(_lexer.Text[_lexer.Position - 1] == '\\');

            // No spaces between \ and next char.
            ConsumeCurrentToken(allowTrivia: false);

            if (_currentToken.Kind == RegexKind.EndOfFile)
            {
                backslashToken = backslashToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Illegal_backslash_at_end_of_pattern,
                    backslashToken.GetSpan()));
                return new RegexSimpleEscapeNode(backslashToken, CreateMissingToken(RegexKind.TextToken));
            }

            Debug.Assert(_currentToken.VirtualChars.Length == 1);
            switch (_currentToken.VirtualChars[0].Value)
            {
                case 'b':
                case 'B':
                case 'A':
                case 'G':
                case 'Z':
                case 'z':
                    return new RegexAnchorEscapeNode(
                        backslashToken, ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd));

                case 'w':
                case 'W':
                case 's':
                case 'S':
                case 'd':
                case 'D':
                    return new RegexCharacterClassEscapeNode(
                        backslashToken, ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd));

                case 'p':
                case 'P':
                    return ParseCategoryEscape(backslashToken, allowTriviaAfterEnd);
            }

            // Move back to after the backslash
            _lexer.Position--;
            return ParseBasicBackslash(backslashToken, allowTriviaAfterEnd);
        }

        private RegexEscapeNode ParseBasicBackslash(RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            Debug.Assert(_lexer.Text[_lexer.Position - 1] == '\\');

            // No spaces between \ and next char.
            ConsumeCurrentToken(allowTrivia: false);

            if (_currentToken.Kind == RegexKind.EndOfFile)
            {
                backslashToken = backslashToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Illegal_backslash_at_end_of_pattern,
                    backslashToken.GetSpan()));
                return new RegexSimpleEscapeNode(backslashToken, CreateMissingToken(RegexKind.TextToken));
            }

            Debug.Assert(_currentToken.VirtualChars.Length == 1);
            var ch = _currentToken.VirtualChars[0];
            if (ch == 'k')
            {
                return ParsePossibleKCaptureEscape(backslashToken, allowTriviaAfterEnd);
            }

            if (ch == '<' || ch == '\'')
            {
                _lexer.Position--;
                return ParsePossibleCaptureEscape(backslashToken, allowTriviaAfterEnd);
            }

            if (ch >= '1' && ch <= '9')
            {
                _lexer.Position--;
                return ParsePossibleBackreferenceEscape(backslashToken, allowTriviaAfterEnd);
            }

            _lexer.Position--;
            return ParseCharEscape(backslashToken, allowTriviaAfterEnd);
        }

        private RegexEscapeNode ParsePossibleBackreferenceEscape(RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            Debug.Assert(_lexer.Text[_lexer.Position - 1] == '\\');
            return HasOption(_options, RegexOptions.ECMAScript)
                ? ParsePossibleEcmascriptBackreferenceEscape(backslashToken, allowTriviaAfterEnd)
                : ParsePossibleRegularBackreferenceEscape(backslashToken, allowTriviaAfterEnd);
        }

        private RegexEscapeNode ParsePossibleEcmascriptBackreferenceEscape(
            RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            // Small deviation: Ecmascript allows references only to captures that precede
            // this position (unlike .NET which allows references in any direction).  However,
            // because we don't track position, we just consume the entire back-reference.
            //
            // This is addressable if we add position tracking when we locate all the captures.

            Debug.Assert(_lexer.Text[_lexer.Position - 1] == '\\');
            var start = _lexer.Position;

            var bestPosition = -1;
            var capVal = 0;
            while (_lexer.Position < _lexer.Text.Length &&
                   _lexer.Text[_lexer.Position] is var ch &&
                   (ch >= '0' && ch <= '9'))
            {
                unchecked
                {
                    capVal *= 10;
                    capVal += (ch.Value - '0');
                }

                _lexer.Position++;

                if (HasCapture(capVal))
                {
                    bestPosition = _lexer.Position;
                }
            }

            if (bestPosition != -1)
            {
                var numberToken = CreateToken(
                    RegexKind.NumberToken, ImmutableArray<RegexTrivia>.Empty,
                    _lexer.GetSubPattern(start, bestPosition)).With(value: capVal);
                ResetToPositionAndConsumeCurrentToken(bestPosition, allowTrivia: allowTriviaAfterEnd);
                return new RegexBackreferenceEscapeNode(backslashToken, numberToken);
            }

            _lexer.Position = start;
            return ParseCharEscape(backslashToken, allowTriviaAfterEnd);
        }

        private RegexEscapeNode ParsePossibleRegularBackreferenceEscape(
            RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            Debug.Assert(_lexer.Text[_lexer.Position - 1] == '\\');
            var start = _lexer.Position;

            var numberToken = _lexer.TryScanNumber().Value;
            var capVal = (int)numberToken.Value;
            if (HasCapture(capVal) ||
                capVal <= 9)
            {
                CheckCapture(ref numberToken);

                ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd);
                return new RegexBackreferenceEscapeNode(backslashToken, numberToken);
            }

            _lexer.Position = start;
            return ParseCharEscape(backslashToken, allowTriviaAfterEnd);
        }

        private RegexEscapeNode ParsePossibleCaptureEscape(RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            Debug.Assert(_lexer.Text[_lexer.Position - 1] == '\\');
            Debug.Assert(_lexer.Text[_lexer.Position] == '<' ||
                         _lexer.Text[_lexer.Position] == '\'');

            var afterBackslashPosition = _lexer.Position;
            ScanCaptureParts(allowTriviaAfterEnd, out var openToken, out var capture, out var closeToken);

            if (openToken.IsMissing || capture.IsMissing || closeToken.IsMissing)
            {
                _lexer.Position = afterBackslashPosition;
                return ParseCharEscape(backslashToken, allowTriviaAfterEnd);
            }

            return new RegexCaptureEscapeNode(
                backslashToken, openToken, capture, closeToken);
        }

        private RegexEscapeNode ParsePossibleKCaptureEscape(RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            var typeToken = _currentToken;
            var afterBackslashPosition = _lexer.Position - @"k".Length;

            ScanCaptureParts(allowTriviaAfterEnd, out var openToken, out var capture, out var closeToken);
            if (openToken.IsMissing)
            {
                backslashToken = backslashToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Malformed_named_back_reference,
                    GetSpan(backslashToken, typeToken)));
                return new RegexSimpleEscapeNode(backslashToken, typeToken.With(kind: RegexKind.TextToken));
            }

            if (capture.IsMissing || closeToken.IsMissing)
            {
                // Native parser falls back to normal escape scanning, if it doesn't see a capture,
                // or close brace.  For normal .NET regexes, this will then fail later (as \k is not
                // a legal escape), but will succeed for ecmascript regexes.
                _lexer.Position = afterBackslashPosition;
                return ParseCharEscape(backslashToken, allowTriviaAfterEnd);
            }

            return new RegexKCaptureEscapeNode(
                backslashToken, typeToken, openToken, capture, closeToken);
        }

        private void ScanCaptureParts(
            bool allowTriviaAfterEnd, out RegexToken openToken, out RegexToken capture, out RegexToken closeToken)
        {
            openToken = CreateMissingToken(RegexKind.LessThanToken);
            capture = CreateMissingToken(RegexKind.CaptureNameToken);
            closeToken = CreateMissingToken(RegexKind.GreaterThanToken);

            // No trivia allowed in <cap> or 'cap'
            ConsumeCurrentToken(allowTrivia: false);

            if (_lexer.Position < _lexer.Text.Length &&
                (_currentToken.Kind == RegexKind.LessThanToken || _currentToken.Kind == RegexKind.SingleQuoteToken))
            {
                openToken = _currentToken;
            }
            else
            {
                return;
            }

            var captureToken = _lexer.TryScanNumberOrCaptureName();
            capture = captureToken == null
                ? CreateMissingToken(RegexKind.CaptureNameToken)
                : captureToken.Value;

            // No trivia allowed in <cap> or 'cap'
            ConsumeCurrentToken(allowTrivia: false);
            closeToken = CreateMissingToken(RegexKind.GreaterThanToken);

            if (!capture.IsMissing &&
                ((openToken.Kind == RegexKind.LessThanToken && _currentToken.Kind == RegexKind.GreaterThanToken) ||
                 (openToken.Kind == RegexKind.SingleQuoteToken && _currentToken.Kind == RegexKind.SingleQuoteToken)))
            {
                CheckCapture(ref capture);
                closeToken = ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd);
            }
        }

        private RegexEscapeNode ParseCharEscape(RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            Debug.Assert(_lexer.Text[_lexer.Position - 1] == '\\');

            // no trivia between \ and the next char
            ConsumeCurrentToken(allowTrivia: false);
            Debug.Assert(_currentToken.VirtualChars.Length == 1);

            var ch = _currentToken.VirtualChars[0];
            if (ch >= '0' && ch <= '7')
            {
                _lexer.Position--;
                var octalDigits = _lexer.ScanOctalCharacters(_options);
                Debug.Assert(octalDigits.VirtualChars.Length > 0);

                ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd);
                return new RegexOctalEscapeNode(backslashToken, octalDigits);
            }

            switch (ch.Value)
            {
                case 'a':
                case 'b':
                case 'e':
                case 'f':
                case 'n':
                case 'r':
                case 't':
                case 'v':
                    return new RegexSimpleEscapeNode(
                        backslashToken, ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd));
                case 'x':
                    return ParseHexEscape(backslashToken, allowTriviaAfterEnd);
                case 'u':
                    return ParseUnicodeEscape(backslashToken, allowTriviaAfterEnd);
                case 'c':
                    return ParseControlEscape(backslashToken, allowTriviaAfterEnd);
                default:
                    var typeToken = ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd).With(kind: RegexKind.TextToken);

                    if (!HasOption(_options, RegexOptions.ECMAScript) && RegexCharClass.IsWordChar(ch))
                    {
                        typeToken = typeToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                            string.Format(FeaturesResources.Unrecognized_escape_sequence_0, ch),
                            typeToken.GetSpan()));
                    }

                    return new RegexSimpleEscapeNode(backslashToken, typeToken);
            }
        }

        private RegexEscapeNode ParseUnicodeEscape(RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            var typeToken = _currentToken;
            var hexChars = _lexer.ScanHexCharacters(4);
            ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd);
            return new RegexUnicodeEscapeNode(backslashToken, typeToken, hexChars);
        }

        private RegexEscapeNode ParseHexEscape(RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            var typeToken = _currentToken;
            var hexChars = _lexer.ScanHexCharacters(2);
            ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd);
            return new RegexHexEscapeNode(backslashToken, typeToken, hexChars);
        }

        private RegexControlEscapeNode ParseControlEscape(RegexToken backslashToken, bool allowTriviaAfterEnd)
        {
            // Nothing allowed between \c and the next char
            var typeToken = ConsumeCurrentToken(allowTrivia: false);

            if (_currentToken.Kind == RegexKind.EndOfFile)
            {
                typeToken = typeToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Missing_control_character,
                    typeToken.GetSpan()));
                return new RegexControlEscapeNode(backslashToken, typeToken, CreateMissingToken(RegexKind.TextToken));
            }

            Debug.Assert(_currentToken.VirtualChars.Length == 1);

            var ch = _currentToken.VirtualChars[0].Value;

            unchecked
            {
                // From: https://github.com/dotnet/corefx/blob/80e220fc7009de0f0611ee6b52d4d5ffd25eb6c7/src/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexParser.cs#L1450

                // Note: Roslyn accepts a control escape that current .NET parser does not.
                // Specifically: \c[
                //
                // It is a bug that the .NET parser does not support this construct.  The bug was
                // reported at: https://github.com/dotnet/corefx/issues/26501 and was fixed for
                // CoreFx with https://github.com/dotnet/corefx/commit/80e220fc7009de0f0611ee6b52d4d5ffd25eb6c7
                //
                // Because it was a bug, we follow the correct behavior.  That means we will not
                // report a diagnostic for a Regex that someone might run on a previous version of
                // .NET that ends up throwing at runtime.  That's acceptable.  Our goal is to match
                // the latest .NET 'correct' behavior.  Not intermediary points with bugs that have
                // since been fixed.

                // \ca interpreted as \cA
                if (ch >= 'a' && ch <= 'z')
                {
                    ch -= (char)('a' - 'A');
                }

                if (ch >= '@' && ch <= '_')
                {
                    var controlToken = ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd).With(kind: RegexKind.TextToken);
                    return new RegexControlEscapeNode(backslashToken, typeToken, controlToken);
                }
                else
                {
                    typeToken = typeToken.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                        FeaturesResources.Unrecognized_control_character,
                        _currentToken.GetSpan()));

                    // Don't consume the bogus control character.
                    return new RegexControlEscapeNode(backslashToken, typeToken, CreateMissingToken(RegexKind.TextToken));
                }
            }
        }

        private RegexEscapeNode ParseCategoryEscape(RegexToken backslash, bool allowTriviaAfterEnd)
        {
            Debug.Assert(_lexer.Text[_lexer.Position - 1] is var ch && (ch == 'P' || ch == 'p'));
            var typeToken = _currentToken;

            var start = _lexer.Position;

            if (!TryGetCategoryEscapeParts(
                    allowTriviaAfterEnd,
                    out var openBraceToken,
                    out var categoryToken,
                    out var closeBraceToken,
                    out var message))
            {
                ResetToPositionAndConsumeCurrentToken(start, allowTrivia: allowTriviaAfterEnd);
                typeToken = typeToken.With(kind: RegexKind.TextToken).AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    message, GetSpan(backslash, typeToken)));
                return new RegexSimpleEscapeNode(backslash, typeToken);
            }

            return new RegexCategoryEscapeNode(backslash, typeToken, openBraceToken, categoryToken, closeBraceToken);
        }

        private bool TryGetCategoryEscapeParts(
            bool allowTriviaAfterEnd,
            out RegexToken openBraceToken,
            out RegexToken categoryToken,
            out RegexToken closeBraceToken,
            out string message)
        {
            openBraceToken = default;
            categoryToken = default;
            closeBraceToken = default;
            message = null;

            if (_lexer.Text.Length - _lexer.Position < "{x}".Length)
            {
                message = FeaturesResources.Incomplete_character_escape;
                return false;
            }

            // no whitespace in \p{x}
            ConsumeCurrentToken(allowTrivia: false);

            if (_currentToken.Kind != RegexKind.OpenBraceToken)
            {
                message = FeaturesResources.Malformed_character_escape;
                return false;
            }

            openBraceToken = _currentToken;
            var category = _lexer.TryScanEscapeCategory();

            // no whitespace in \p{x}
            ConsumeCurrentToken(allowTrivia: false);
            if (_currentToken.Kind != RegexKind.CloseBraceToken)
            {
                message = FeaturesResources.Incomplete_character_escape;
                return false;
            }

            if (category == null)
            {
                message = FeaturesResources.Unknown_property;
                return false;
            }

            categoryToken = category.Value;
            closeBraceToken = ConsumeCurrentToken(allowTrivia: allowTriviaAfterEnd);
            return true;
        }

        private RegexTextNode ParseUnexpectedQuantifier(RegexExpressionNode lastExpression)
        {
            // This is just a bogus element in the higher level sequence.  Allow trivia
            // after this to abide by the spirit of the native parser.
            var token = ConsumeCurrentToken(allowTrivia: true);
            CheckQuantifierExpression(lastExpression, ref token);
            return new RegexTextNode(token.With(kind: RegexKind.TextToken));
        }

        private static void CheckQuantifierExpression(RegexExpressionNode current, ref RegexToken token)
        {
            if (current == null ||
                current.Kind == RegexKind.SimpleOptionsGrouping)
            {
                token = token.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    FeaturesResources.Quantifier_x_y_following_nothing, token.GetSpan()));
            }
            else if (current is RegexQuantifierNode ||
                     current is RegexLazyQuantifierNode)
            {
                token = token.AddDiagnosticIfNone(new EmbeddedDiagnostic(
                    string.Format(FeaturesResources.Nested_quantifier_0, token.VirtualChars.First()), token.GetSpan()));
            }
        }
    }
}


text:
106 House Republicans sign onto Texas's disgraceful election lawsuit because that's what being a good Republican means now
AllahpunditPosted at 6:23 pm on December 10, 2020
I'm reading headlines today at liberal websites like "The GOP Abandons Democracy" and "More Than Half of House Republicans Sign On to Trump's Election Coup" and feeling the old familiar urge to bristle with indignation. Surely the left is once again demagoging some legitimate Republican project in hyperbolic scaremongering terms. Liberal bias, plain as day.
It isn't. Those headlines are accurate. The Republican Party, at least a substantial chunk of it, is now the sort of sinister caricature of itself that a leftist might draw. In an age of hyperpartisanship it's almost impossible to be as wretched and corrupt as your political enemies claim you are, but somehow they've done it. More than half the House caucus has joined a lawsuit that would disenfranchise millions, cause an irreparable schism between U.S. states, install a disordered unelected demagogue in office for another four years, and obliterate whatever remaining institutional credibility the Supreme Court has.
These people would end the United States as we know it if they could. The contempt expressed for them in the left's coverage isn't half as acidic as it should be.
Still, I'm glad they made their position plain. They've created a handy list going forward of people who should never hold elected office in this country again. If I'm ever given the opportunity to vote for one, I'll be sure to vote for their opponent.
https://twitter.com/johnkruzel/status/1337135543373733889
Some of the names are predictable MAGA goons like Matt Gaetz but there are bona fide disappointments like Steve Scalise, Cathy McMorris-Rodgers, Elise Stefanik, and war hero turned Trump stooge Dan Crenshaw. All are morally disqualified from holding positions of public trust. It's no comfort either that most, even all, know that the Texas suit is anti-democratic garbage that would make the country ungovernable and are supporting it in full confidence that it's doomed to fail — that is, that this is an empty loyalty gesture aimed at Trump and his base.
That's reason enough to never support them again. Burning the American flag is a gesture too but a politician who did it would be unelectable in most parts of this country. These cretins want to burn a lot more than that. Un-American behavior has no place in American government.
"Enemies of the people" one might call them, to borrow one of the president's favorite phrases. As a Twitter pal says, anyone who's willing to overturn an election on the flimsiest pretenses of fraud is willing to go much further. They're a menace to the country, nothing less.
Anyone inclined to excuse them on grounds that the GOP is an autocracy now and they're merely going along to get along in that system needs to explain why other congressional Republicans, including Ted Cruz's former chief of staff, have kept their civic bearings here:
Today, a number of my colleagues will be filing an amicus brief in support of the lawsuit filed by the State of Texas regarding the election results of several other states. Respectfully, I will not join because I believe the case itself represents a dangerous violation… (1/5)
— Chip Roy (@chiproytx) December 10, 2020
…states increased the casting of illegal votes and undermine confidence in our elections. The American people are raising legitimate questions & they deserve answers. I strongly support the continued pursuit of litigation where most likely to succeed – such as Georgia… (3/5)
…sovereignty from the meddling of other states. Our remedy must be, from this day forward, to decline to allow the usurpation of our authority as people – through our states – to govern ourselves in all respects. #StandUpForAmerica #Federalism (5/5)
Even Roy has to hedge his dissent with nonsense about Georgia's outcome being in some doubt despite a full recount and a hand recount there. But he at least grasps that letting Texas sue to invalidate another state's election procedures would open the door to blue states and red ones suing each other endlessly to try to compel them to follow the laws, customs, and procedures followed by their own states. For that reason alone, SCOTUS will flush this idiotic complaint. It's not going to singlehandedly shift the United States from a federalist system to one where certain states get to impose their governing preferences on others with the Court's blessing.
And if it did, Erick Erickson's right about what would happen. "Wait for Gavin Newsom and Andrew Cuomo to give this precedent a whirl. Wait for progressive states to start suing conservative states over religious liberty, transgender rights, police brutality, tax policies that 'steal' residents of progressive states, etc." That's one obvious reason why the House members who've signed on to the Texas suit aren't taking it seriously — the discord it would unleash is apparent even to non-lawyers. Another is the fact that every one of these 106 was on the same ballot on November 3 as Trump and Biden. If their suit prevails, at a minimum every member of the House elected last month in Georgia, Michigan, Pennsylvania, and Wisconsin can't properly be seated in the next Congress because there's no way to have confidence in the integrity of the ballots that handed them their victory.
The worst part of this, though, and the part for which they'll be ultimately remembered is that they chose to egg Trump on at a moment when he inevitably began hinting that people should get hurt if he doesn't get his way:
….The fact that our Country is being stolen. A coup is taking place in front of our eyes, and the public can't take this anymore." A Trump fan at Georgia Rally on @OANN Bad!
"This is going to escalate dramatically. This is a very dangerous moment in our history." "The public can't take this anymore." He's still dialing up state legislators to try to convince them to void their elections and execute a coup on his behalf in case his court challenges fail, which they will. Anyone who pretends to care about the Founders and the Constitution, as all 106 of these frauds do, should grasp that if separation of powers means anything it means that when one branch turns to demagoguery and mayhem to assert its power, the other branches have a duty to check it by asserting their own. The Supreme Court will assert itself, possibly before the day is out. The 106 House members who signed on here, to their lasting disgrace, decided to abet the mayhem and demagoguery instead.
Enemies of the people. A stain on the Constitution of the United States.
Today's roll call of shame might not be the last one. New York magazine makes a good point about the Trumpist plan in the works to object to Congress's official recording of the electoral college vote on January 6. If one member of the House and one member of the Senate object to the awarding of electors from a state, both houses of Congress then have to vote on how that state's electors should be awarded. (If they can't agree then the results on election night hold, which means Biden's sure to win anyway thanks to the Democratic majority in the House.) In other words, every Republican in the House and Senate may be forced to go on record in a floor vote and say whether they believe the votes in Wisconsin or Pennsylvania or Georgia or Arizona — or all of them — should go to Trump or Biden. That's McConnell's worst nightmare, as it would force the swing-staters in his caucus into a high-stakes no-win choice.
The smart move would be for no one to object so that the floor vote doesn't happen. But this party is nothing more than a loyalty cult now and Trump will demand a show of loyalty by insisting that the vote happen. He'll want to know who the "traitors" are, and his base will want to know too. That's what congressional Republicans deserve.
The only way this perverse fascist episode can end is with Ken Paxton, Texas's AG, receiving a corrupt pardon from Trump as thanks for his role in bringing this lawsuit. We're well on our way. I'll leave you with this.
NEW: FBI agents delivered at least one federal subpoena to the Texas Attorney General's office Wednesday for information in an ongoing investigation involving AG Ken Paxton, three sources confirm, indicating the seriousness with which they are taking allegations against Paxton. pic.twitter.com/hn77HfcDwj
— Tony Plohetski (@tplohetski) December 10, 2020
Tags: electoral house Ken Paxton lawsuit Republicans Supreme Court Texas trump

redpajama_set_name: RedPajamaCommonCrawl