File: EmbeddedLanguages\RegularExpressions\RegexNodes.cs
Web Access
Project: ..\..\..\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.EmbeddedLanguages.Common;
using Microsoft.CodeAnalysis.Serialization;
 
namespace Microsoft.CodeAnalysis.EmbeddedLanguages.RegularExpressions
{
    using RegexNodeOrToken = EmbeddedSyntaxNodeOrToken<RegexKind, RegexNode>;
    using RegexToken = EmbeddedSyntaxToken<RegexKind>;
    using RegexAlternatingSequenceList = EmbeddedSeparatedSyntaxNodeList<RegexKind, RegexNode, RegexSequenceNode>;
 
    internal sealed class RegexCompilationUnit : RegexNode
    {
        public RegexCompilationUnit(RegexExpressionNode expression, RegexToken endOfFileToken)
            : base(RegexKind.CompilationUnit)
        {
            Debug.Assert(expression != null);
            Debug.Assert(endOfFileToken.Kind == RegexKind.EndOfFile);
            Expression = expression;
            EndOfFileToken = endOfFileToken;
        }
 
        public RegexExpressionNode Expression { get; }
        public RegexToken EndOfFileToken { get; }
 
        internal override int ChildCount => 2;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => Expression,
                1 => EndOfFileToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Represents a possibly-empty sequence of regex expressions.  For example, the regex ""
    /// will produce an empty RegexSequence nodes, and "a|" will produce an alternation with an
    /// empty sequence on the right side.  Having a node represent the empty sequence is actually
    /// appropriate as these are legal regexes and the empty sequence represents 'a pattern
    /// that will match any position'.  Not having a node for this would actually end up 
    /// complicating things in terms of dealing with nulls in the tree.
    /// 
    /// This does not deviate from Roslyn principles.  While nodes for empty text are rare, they
    /// are allowed (for example, OmittedTypeArgument in C#).
    /// </summary>
    internal sealed class RegexSequenceNode : RegexExpressionNode
    {
        public ImmutableArray<RegexExpressionNode> Children { get; }
 
        internal override int ChildCount => Children.Length;
 
        public RegexSequenceNode(ImmutableArray<RegexExpressionNode> children)
            : base(RegexKind.Sequence)
        {
            this.Children = children;
        }
 
        internal override RegexNodeOrToken ChildAt(int index)
            => Children[index];
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Represents a chunk of text (usually just a single char) from the original pattern.
    /// </summary>
    internal sealed class RegexTextNode : RegexPrimaryExpressionNode
    {
        public RegexTextNode(RegexToken textToken)
            : base(RegexKind.Text)
        {
            Debug.Assert(textToken.Kind == RegexKind.TextToken);
            TextToken = textToken;
        }
 
        public RegexToken TextToken { get; }
 
        internal override int ChildCount => 1;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => TextToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Base type for [...] and [^...] character classes.
    /// </summary>
    internal abstract class RegexBaseCharacterClassNode : RegexPrimaryExpressionNode
    {
        protected RegexBaseCharacterClassNode(
            RegexKind kind, RegexToken openBracketToken, RegexSequenceNode components, RegexToken closeBracketToken)
            : base(kind)
        {
            Debug.Assert(openBracketToken.Kind == RegexKind.OpenBracketToken);
            Debug.Assert(components != null);
            Debug.Assert(closeBracketToken.Kind == RegexKind.CloseBracketToken);
            OpenBracketToken = openBracketToken;
            Components = components;
            CloseBracketToken = closeBracketToken;
        }
 
        public RegexToken OpenBracketToken { get; }
        public RegexSequenceNode Components { get; }
        public RegexToken CloseBracketToken { get; }
    }
 
    /// <summary>
    /// [...] node.
    /// </summary>
    internal sealed class RegexCharacterClassNode : RegexBaseCharacterClassNode
    {
        public RegexCharacterClassNode(
            RegexToken openBracketToken, RegexSequenceNode components, RegexToken closeBracketToken)
            : base(RegexKind.CharacterClass, openBracketToken, components, closeBracketToken)
        {
        }
 
        internal override int ChildCount => 3;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenBracketToken,
                1 => Components,
                2 => CloseBracketToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// [^...] node
    /// </summary>
    internal sealed class RegexNegatedCharacterClassNode : RegexBaseCharacterClassNode
    {
        public RegexNegatedCharacterClassNode(
            RegexToken openBracketToken, RegexToken caretToken, RegexSequenceNode components, RegexToken closeBracketToken)
            : base(RegexKind.NegatedCharacterClass, openBracketToken, components, closeBracketToken)
        {
            Debug.Assert(caretToken.Kind == RegexKind.CaretToken);
            CaretToken = caretToken;
        }
 
        public RegexToken CaretToken { get; }
 
        internal override int ChildCount => 4;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenBracketToken,
                1 => CaretToken,
                2 => Components,
                3 => CloseBracketToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```a-z``` node in a character class.
    /// </summary>
    internal sealed class RegexCharacterClassRangeNode : RegexPrimaryExpressionNode
    {
        public RegexCharacterClassRangeNode(
            RegexExpressionNode left, RegexToken minusToken, RegexExpressionNode right)
            : base(RegexKind.CharacterClassRange)
        {
            Debug.Assert(left != null);
            Debug.Assert(minusToken.Kind == RegexKind.MinusToken);
            Debug.Assert(right != null);
            Left = left;
            MinusToken = minusToken;
            Right = right;
        }
 
        public RegexExpressionNode Left { get; }
        public RegexToken MinusToken { get; }
        public RegexExpressionNode Right { get; }
 
        internal override int ChildCount => 3;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => Left,
                1 => MinusToken,
                2 => Right,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```-[f-m]``` in a pattern like ```[a-z-[f-m]]```.  A subtraction must come last in a 
    /// character class, and removes some range of chars from the character class built up
    /// so far.
    /// </summary>
    internal sealed class RegexCharacterClassSubtractionNode : RegexPrimaryExpressionNode
    {
        public RegexCharacterClassSubtractionNode(
            RegexToken minusToken, RegexBaseCharacterClassNode characterClass)
            : base(RegexKind.CharacterClassSubtraction)
        {
            Debug.Assert(minusToken.Kind == RegexKind.MinusToken);
            Debug.Assert(characterClass != null);
            MinusToken = minusToken;
            CharacterClass = characterClass;
        }
 
        public RegexToken MinusToken { get; }
        public RegexBaseCharacterClassNode CharacterClass { get; }
 
        internal override int ChildCount => 2;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => MinusToken,
                1 => CharacterClass,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Represents a ```[:...:]``` node in a character class.  Note: the .NET regex parser
    /// simply treats this as the character ```[``` and ignores the rest of the ```:...:]```.
    /// They latter part has no impact on the actual match engine that is produced.
    /// </summary>
    internal sealed class RegexPosixPropertyNode : RegexPrimaryExpressionNode
    {
        public RegexPosixPropertyNode(RegexToken textToken)
            : base(RegexKind.PosixProperty)
        {
            Debug.Assert(textToken.Kind == RegexKind.TextToken);
            TextToken = textToken;
        }
 
        public RegexToken TextToken { get; }
 
        internal override int ChildCount => 1;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => TextToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Root of all expression nodes.
    /// </summary>
    internal abstract class RegexExpressionNode : RegexNode
    {
        protected RegexExpressionNode(RegexKind kind)
            : base(kind)
        {
        }
    }
 
    /// <summary>
    /// Root of all the primary nodes (similar to unary nodes in C#).
    /// </summary>
    internal abstract class RegexPrimaryExpressionNode : RegexExpressionNode
    {
        protected RegexPrimaryExpressionNode(RegexKind kind)
            : base(kind)
        {
        }
    }
 
    /// <summary>
    /// A ```.``` expression.
    /// </summary>
    internal sealed class RegexWildcardNode : RegexPrimaryExpressionNode
    {
        public RegexWildcardNode(RegexToken dotToken)
            : base(RegexKind.Wildcard)
        {
            Debug.Assert(dotToken.Kind == RegexKind.DotToken);
            DotToken = dotToken;
        }
 
        public RegexToken DotToken { get; }
 
        internal override int ChildCount => 1;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => DotToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Root of all quantifier nodes: ```?```, ```*``` etc.
    /// </summary>
    internal abstract class RegexQuantifierNode : RegexExpressionNode
    {
        protected RegexQuantifierNode(RegexKind kind)
            : base(kind)
        {
        }
    }
 
    /// <summary>
    /// ```expr*```
    /// </summary>
    internal sealed class RegexZeroOrMoreQuantifierNode : RegexQuantifierNode
    {
        public RegexZeroOrMoreQuantifierNode(
            RegexExpressionNode expression, RegexToken asteriskToken)
            : base(RegexKind.ZeroOrMoreQuantifier)
        {
            Debug.Assert(expression != null);
            Debug.Assert(asteriskToken.Kind == RegexKind.AsteriskToken);
            Expression = expression;
            AsteriskToken = asteriskToken;
        }
 
        public RegexExpressionNode Expression { get; }
        public RegexToken AsteriskToken { get; }
 
        internal override int ChildCount => 2;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => this.Expression,
                1 => this.AsteriskToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```expr+```
    /// </summary>
    internal sealed class RegexOneOrMoreQuantifierNode : RegexQuantifierNode
    {
        public RegexOneOrMoreQuantifierNode(
            RegexExpressionNode expression, RegexToken plusToken)
            : base(RegexKind.OneOrMoreQuantifier)
        {
            Debug.Assert(expression != null);
            Debug.Assert(plusToken.Kind == RegexKind.PlusToken);
            Expression = expression;
            PlusToken = plusToken;
        }
 
        public RegexExpressionNode Expression { get; }
        public RegexToken PlusToken { get; }
 
        internal override int ChildCount => 2;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => this.Expression,
                1 => this.PlusToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```expr?```
    /// </summary>
    internal sealed class RegexZeroOrOneQuantifierNode : RegexQuantifierNode
    {
        public RegexZeroOrOneQuantifierNode(
            RegexExpressionNode expression, RegexToken questionToken)
            : base(RegexKind.ZeroOrOneQuantifier)
        {
            Debug.Assert(expression != null);
            Debug.Assert(questionToken.Kind == RegexKind.QuestionToken);
            Expression = expression;
            QuestionToken = questionToken;
        }
 
        public RegexExpressionNode Expression { get; }
        public RegexToken QuestionToken { get; }
 
        internal override int ChildCount => 2;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => this.Expression,
                1 => this.QuestionToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Quantifiers can be optionally followed by a ? to make them lazy.  i.e. ```a*?``` or ```a+?```.
    /// You can even have ```a??```  (zero or one 'a', lazy).  However, only one lazy modifier is allowed
    /// ```a*??``` or ```a???``` is not allowed.
    /// </summary>
    internal sealed class RegexLazyQuantifierNode : RegexExpressionNode
    {
        public RegexLazyQuantifierNode(
            RegexQuantifierNode quantifier, RegexToken questionToken)
            : base(RegexKind.LazyQuantifier)
        {
            Debug.Assert(quantifier != null);
            Debug.Assert(quantifier.Kind != RegexKind.LazyQuantifier);
            Debug.Assert(questionToken.Kind == RegexKind.QuestionToken);
            Quantifier = quantifier;
            QuestionToken = questionToken;
        }
 
        public RegexQuantifierNode Quantifier { get; }
        public RegexToken QuestionToken { get; }
 
        internal override int ChildCount => 2;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => this.Quantifier,
                1 => this.QuestionToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Base type of all regex numeric quantifier nodes.  i.e.  
    /// ```a{5}```,  ```a{5,}``` and ```a{5,10}```
    /// </summary>
    internal abstract class RegexNumericQuantifierNode : RegexQuantifierNode
    {
        protected RegexNumericQuantifierNode(
            RegexKind kind, RegexPrimaryExpressionNode expression, RegexToken openBraceToken, RegexToken firstNumberToken, RegexToken closeBraceToken)
            : base(kind)
        {
            Debug.Assert(expression != null);
            Debug.Assert(openBraceToken.Kind == RegexKind.OpenBraceToken);
            Debug.Assert(firstNumberToken.Kind == RegexKind.NumberToken);
            Debug.Assert(closeBraceToken.Kind == RegexKind.CloseBraceToken);
            Expression = expression;
            OpenBraceToken = openBraceToken;
            FirstNumberToken = firstNumberToken;
            CloseBraceToken = closeBraceToken;
        }
 
        public RegexExpressionNode Expression { get; }
        public RegexToken OpenBraceToken { get; }
        public RegexToken FirstNumberToken { get; }
        public RegexToken CloseBraceToken { get; }
    }
 
    /// <summary>
    /// ```a{5}```
    /// </summary>
    internal sealed class RegexExactNumericQuantifierNode : RegexNumericQuantifierNode
    {
        public RegexExactNumericQuantifierNode(
            RegexPrimaryExpressionNode expression, RegexToken openBraceToken, RegexToken numberToken, RegexToken closeBraceToken)
            : base(RegexKind.ExactNumericQuantifier, expression, openBraceToken, numberToken, closeBraceToken)
        {
        }
 
        internal override int ChildCount => 4;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => Expression,
                1 => OpenBraceToken,
                2 => FirstNumberToken,
                3 => CloseBraceToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```a{5,}```
    /// </summary>
    internal sealed class RegexOpenNumericRangeQuantifierNode : RegexNumericQuantifierNode
    {
        public RegexOpenNumericRangeQuantifierNode(
            RegexPrimaryExpressionNode expression,
            RegexToken openBraceToken, RegexToken firstNumberToken,
            RegexToken commaToken, RegexToken closeBraceToken)
            : base(RegexKind.OpenRangeNumericQuantifier, expression, openBraceToken, firstNumberToken, closeBraceToken)
        {
            Debug.Assert(commaToken.Kind == RegexKind.CommaToken);
            CommaToken = commaToken;
        }
 
        public RegexToken CommaToken { get; }
 
        internal override int ChildCount => 5;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => Expression,
                1 => OpenBraceToken,
                2 => FirstNumberToken,
                3 => CommaToken,
                4 => CloseBraceToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```a{5,10}```
    /// </summary>
    internal sealed class RegexClosedNumericRangeQuantifierNode : RegexNumericQuantifierNode
    {
        public RegexClosedNumericRangeQuantifierNode(
            RegexPrimaryExpressionNode expression,
            RegexToken openBraceToken, RegexToken firstNumberToken,
            RegexToken commaToken, RegexToken secondNumberToken, RegexToken closeBraceToken)
            : base(RegexKind.ClosedRangeNumericQuantifier, expression, openBraceToken, firstNumberToken, closeBraceToken)
        {
            Debug.Assert(commaToken.Kind == RegexKind.CommaToken);
            Debug.Assert(secondNumberToken.Kind == RegexKind.NumberToken);
            CommaToken = commaToken;
            SecondNumberToken = secondNumberToken;
        }
 
        public RegexToken CommaToken { get; }
        public RegexToken SecondNumberToken { get; }
 
        internal override int ChildCount => 6;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => Expression,
                1 => OpenBraceToken,
                2 => FirstNumberToken,
                3 => CommaToken,
                4 => SecondNumberToken,
                5 => CloseBraceToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```$``` or ```^```.
    /// </summary>
    internal sealed class RegexAnchorNode : RegexPrimaryExpressionNode
    {
        public RegexAnchorNode(RegexKind kind, RegexToken anchorToken)
            : base(kind)
        {
            Debug.Assert(anchorToken.Kind is RegexKind.DollarToken or RegexKind.CaretToken);
            AnchorToken = anchorToken;
        }
 
        public RegexToken AnchorToken { get; }
 
        internal override int ChildCount => 1;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => AnchorToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```expr1|expr2``` node.
    /// </summary>
    internal sealed class RegexAlternationNode : RegexExpressionNode
    {
        public RegexAlternationNode(RegexAlternatingSequenceList sequenceList)
            : base(RegexKind.Alternation)
        {
            Debug.Assert(sequenceList.NodesAndTokens.Length > 0);
            for (var i = 1; i < sequenceList.NodesAndTokens.Length; i += 2)
                Debug.Assert(sequenceList.NodesAndTokens[i].Kind == RegexKind.BarToken);
 
            SequenceList = sequenceList;
        }
 
        public RegexAlternatingSequenceList SequenceList { get; }
 
        internal override int ChildCount => SequenceList.NodesAndTokens.Length;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => SequenceList.NodesAndTokens[index];
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Base type of all non-trivia ```(...)``` nodes
    /// </summary>
    internal abstract class RegexGroupingNode : RegexPrimaryExpressionNode
    {
        protected RegexGroupingNode(RegexKind kind, RegexToken openParenToken, RegexToken closeParenToken)
            : base(kind)
        {
            Debug.Assert(openParenToken.Kind == RegexKind.OpenParenToken);
            Debug.Assert(closeParenToken.Kind == RegexKind.CloseParenToken);
            OpenParenToken = openParenToken;
            CloseParenToken = closeParenToken;
        }
 
        public RegexToken OpenParenToken { get; }
        public RegexToken CloseParenToken { get; }
    }
 
    /// <summary>
    /// The ```(...)``` node you get when the group does not start with ```(?```
    /// </summary>
    internal sealed class RegexSimpleGroupingNode : RegexGroupingNode
    {
        public RegexSimpleGroupingNode(RegexToken openParenToken, RegexExpressionNode expression, RegexToken closeParenToken)
            : base(RegexKind.SimpleGrouping, openParenToken, closeParenToken)
        {
            Debug.Assert(expression != null);
            Expression = expression;
        }
 
        public RegexExpressionNode Expression { get; }
 
        internal override int ChildCount => 3;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => Expression,
                2 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Base type of all ```(?...)``` groupings.
    /// </summary>
    internal abstract class RegexQuestionGroupingNode : RegexGroupingNode
    {
        protected RegexQuestionGroupingNode(RegexKind kind, RegexToken openParenToken, RegexToken questionToken, RegexToken closeParenToken)
            : base(kind, openParenToken, closeParenToken)
        {
            Debug.Assert(questionToken.Kind == RegexKind.QuestionToken);
            QuestionToken = questionToken;
        }
 
        public RegexToken QuestionToken { get; }
    }
 
    /// <summary>
    /// Base type of ```(?inmsx)``` or ```(?inmsx:...)``` nodes.
    /// </summary>
    internal abstract class RegexOptionsGroupingNode : RegexQuestionGroupingNode
    {
        protected RegexOptionsGroupingNode(RegexKind kind, RegexToken openParenToken, RegexToken questionToken, RegexToken optionsToken, RegexToken closeParenToken)
            : base(kind, openParenToken, questionToken, closeParenToken)
        {
            OptionsToken = optionsToken;
        }
 
        public RegexToken OptionsToken { get; }
    }
 
    /// <summary>
    /// ```(?inmsx)``` node.  Changes options in a sequence for all subsequence nodes.
    /// </summary>
    internal sealed class RegexSimpleOptionsGroupingNode : RegexOptionsGroupingNode
    {
        public RegexSimpleOptionsGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken optionsToken, RegexToken closeParenToken)
            : base(RegexKind.SimpleOptionsGrouping, openParenToken, questionToken, optionsToken, closeParenToken)
        {
        }
 
        internal override int ChildCount => 4;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => OptionsToken,
                3 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```(?inmsx:expr)``` node.  Changes options for the parsing of 'expr'.
    /// </summary>
    internal sealed class RegexNestedOptionsGroupingNode : RegexOptionsGroupingNode
    {
        public RegexNestedOptionsGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken optionsToken,
            RegexToken colonToken, RegexExpressionNode expression, RegexToken closeParenToken)
            : base(RegexKind.NestedOptionsGrouping, openParenToken, questionToken, optionsToken, closeParenToken)
        {
            Debug.Assert(colonToken.Kind == RegexKind.ColonToken);
            Debug.Assert(expression != null);
            ColonToken = colonToken;
            Expression = expression;
        }
 
        public RegexToken ColonToken { get; }
        public RegexExpressionNode Expression { get; }
 
        internal override int ChildCount => 6;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => OptionsToken,
                3 => ColonToken,
                4 => Expression,
                5 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```(?:expr)``` node.
    /// </summary>
    internal sealed class RegexNonCapturingGroupingNode : RegexQuestionGroupingNode
    {
        public RegexNonCapturingGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken colonToken,
            RegexExpressionNode expression, RegexToken closeParenToken)
            : base(RegexKind.NonCapturingGrouping, openParenToken, questionToken, closeParenToken)
        {
            Debug.Assert(colonToken.Kind == RegexKind.ColonToken);
            Debug.Assert(expression != null);
            ColonToken = colonToken;
            Expression = expression;
        }
 
        public RegexToken ColonToken { get; }
        public RegexExpressionNode Expression { get; }
 
        internal override int ChildCount => 5;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => ColonToken,
                3 => Expression,
                4 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```(?=expr)``` node.
    /// </summary>
    internal sealed class RegexPositiveLookaheadGroupingNode : RegexQuestionGroupingNode
    {
        public RegexPositiveLookaheadGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken equalsToken,
            RegexExpressionNode expression, RegexToken closeParenToken)
            : base(RegexKind.PositiveLookaheadGrouping, openParenToken, questionToken, closeParenToken)
        {
            Debug.Assert(equalsToken.Kind == RegexKind.EqualsToken);
            Debug.Assert(expression != null);
            EqualsToken = equalsToken;
            Expression = expression;
        }
 
        public RegexToken EqualsToken { get; }
        public RegexExpressionNode Expression { get; }
 
        internal override int ChildCount => 5;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => EqualsToken,
                3 => Expression,
                4 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```(?!expr)``` node.
    /// </summary>
    internal sealed class RegexNegativeLookaheadGroupingNode : RegexQuestionGroupingNode
    {
        public RegexNegativeLookaheadGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken exclamationToken,
            RegexExpressionNode expression, RegexToken closeParenToken)
            : base(RegexKind.NegativeLookaheadGrouping, openParenToken, questionToken, closeParenToken)
        {
            Debug.Assert(exclamationToken.Kind == RegexKind.ExclamationToken);
            Debug.Assert(expression != null);
            ExclamationToken = exclamationToken;
            Expression = expression;
        }
 
        public RegexToken ExclamationToken { get; }
        public RegexExpressionNode Expression { get; }
 
        internal override int ChildCount => 5;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => ExclamationToken,
                3 => Expression,
                4 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    internal abstract class RegexLookbehindGroupingNode : RegexQuestionGroupingNode
    {
        protected RegexLookbehindGroupingNode(
            RegexKind kind, RegexToken openParenToken, RegexToken questionToken,
            RegexToken lessThanToken, RegexToken closeParenToken)
            : base(kind, openParenToken, questionToken, closeParenToken)
        {
            Debug.Assert(lessThanToken.Kind == RegexKind.LessThanToken);
            LessThanToken = lessThanToken;
        }
 
        public RegexToken LessThanToken { get; }
    }
 
    /// <summary>
    /// ```(?&lt;=expr)``` node.
    /// </summary>
    internal sealed class RegexPositiveLookbehindGroupingNode : RegexLookbehindGroupingNode
    {
        public RegexPositiveLookbehindGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken lessThanToken,
            RegexToken equalsToken, RegexExpressionNode expression, RegexToken closeParenToken)
            : base(RegexKind.PositiveLookbehindGrouping, openParenToken, questionToken, lessThanToken, closeParenToken)
        {
            Debug.Assert(equalsToken.Kind == RegexKind.EqualsToken);
            Debug.Assert(expression != null);
            EqualsToken = equalsToken;
            Expression = expression;
        }
 
        public RegexToken EqualsToken { get; }
        public RegexExpressionNode Expression { get; }
 
        internal override int ChildCount => 6;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => LessThanToken,
                3 => EqualsToken,
                4 => Expression,
                5 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```(?&lt;!expr)``` node.
    /// </summary>
    internal sealed class RegexNegativeLookbehindGroupingNode : RegexLookbehindGroupingNode
    {
        public RegexNegativeLookbehindGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken lessThanToken,
            RegexToken exclamationToken, RegexExpressionNode expression, RegexToken closeParenToken)
            : base(RegexKind.NegativeLookbehindGrouping, openParenToken, questionToken, lessThanToken, closeParenToken)
        {
            Debug.Assert(exclamationToken.Kind == RegexKind.ExclamationToken);
            Debug.Assert(expression != null);
            ExclamationToken = exclamationToken;
            Expression = expression;
        }
 
        public RegexToken ExclamationToken { get; }
        public RegexExpressionNode Expression { get; }
 
        internal override int ChildCount => 6;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => LessThanToken,
                3 => ExclamationToken,
                4 => Expression,
                5 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```(?&gt;expr)``` node.
    /// </summary>
    internal sealed class RegexAtomicGroupingNode : RegexQuestionGroupingNode
    {
        public RegexAtomicGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken greaterThanToken,
            RegexExpressionNode expression, RegexToken closeParenToken)
            : base(RegexKind.AtomicGrouping, openParenToken, questionToken, closeParenToken)
        {
            Debug.Assert(greaterThanToken.Kind == RegexKind.GreaterThanToken);
            Debug.Assert(expression != null);
            GreaterThanToken = greaterThanToken;
            Expression = expression;
        }
 
        public RegexToken GreaterThanToken { get; }
        public RegexExpressionNode Expression { get; }
 
        internal override int ChildCount => 5;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => GreaterThanToken,
                3 => Expression,
                4 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```(?'name'expr)``` or ```(?&lt;name&gt;expr)``` node.
    /// </summary>
    internal sealed class RegexCaptureGroupingNode : RegexQuestionGroupingNode
    {
        public RegexCaptureGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken openToken,
            RegexToken captureToken, RegexToken closeToken,
            RegexExpressionNode expression, RegexToken closeParenToken)
            : base(RegexKind.CaptureGrouping, openParenToken, questionToken, closeParenToken)
        {
            Debug.Assert(expression != null);
            OpenToken = openToken;
            CaptureToken = captureToken;
            CloseToken = closeToken;
            Expression = expression;
        }
 
        public RegexToken OpenToken { get; }
        public RegexToken CaptureToken { get; }
        public RegexToken CloseToken { get; }
        public RegexExpressionNode Expression { get; }
 
        internal override int ChildCount => 7;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => OpenToken,
                3 => CaptureToken,
                4 => CloseToken,
                5 => Expression,
                6 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```(?'name1-name2'expr)``` or ```(?&lt;name1-name2&gt;expr)``` node.
    /// </summary>
    internal sealed class RegexBalancingGroupingNode : RegexQuestionGroupingNode
    {
        public RegexBalancingGroupingNode(
            RegexToken openParenToken, RegexToken questionToken, RegexToken openToken,
            RegexToken firstCaptureToken, RegexToken minusToken, RegexToken secondCaptureToken,
            RegexToken closeToken, RegexExpressionNode expression, RegexToken closeParenToken)
            : base(RegexKind.BalancingGrouping, openParenToken, questionToken, closeParenToken)
        {
            Debug.Assert(minusToken.Kind == RegexKind.MinusToken);
            Debug.Assert(expression != null);
            OpenToken = openToken;
            FirstCaptureToken = firstCaptureToken;
            MinusToken = minusToken;
            SecondCaptureToken = secondCaptureToken;
            CloseToken = closeToken;
            Expression = expression;
        }
 
        public RegexToken OpenToken { get; }
        public RegexToken FirstCaptureToken { get; }
        public RegexToken MinusToken { get; }
        public RegexToken SecondCaptureToken { get; }
        public RegexToken CloseToken { get; }
        public RegexExpressionNode Expression { get; }
 
        internal override int ChildCount => 9;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => OpenToken,
                3 => FirstCaptureToken,
                4 => MinusToken,
                5 => SecondCaptureToken,
                6 => CloseToken,
                7 => Expression,
                8 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    internal abstract class RegexConditionalGroupingNode : RegexQuestionGroupingNode
    {
        protected RegexConditionalGroupingNode(
            RegexKind kind, RegexToken openParenToken, RegexToken questionToken,
            RegexExpressionNode result, RegexToken closeParenToken)
            : base(kind, openParenToken, questionToken, closeParenToken)
        {
            Debug.Assert(result != null);
            Result = result;
        }
 
        public RegexExpressionNode Result { get; }
    }
 
    /// <summary>
    /// ```(?(capture_name)result)```
    /// </summary>
    internal sealed class RegexConditionalCaptureGroupingNode : RegexConditionalGroupingNode
    {
        public RegexConditionalCaptureGroupingNode(
            RegexToken openParenToken, RegexToken questionToken,
            RegexToken innerOpenParenToken, RegexToken captureToken, RegexToken innerCloseParenToken,
            RegexExpressionNode result, RegexToken closeParenToken)
            : base(RegexKind.ConditionalCaptureGrouping, openParenToken, questionToken, result, closeParenToken)
        {
            Debug.Assert(innerOpenParenToken.Kind == RegexKind.OpenParenToken);
            Debug.Assert(innerCloseParenToken.Kind == RegexKind.CloseParenToken);
            InnerOpenParenToken = innerOpenParenToken;
            CaptureToken = captureToken;
            InnerCloseParenToken = innerCloseParenToken;
        }
 
        public RegexToken InnerOpenParenToken { get; }
        public RegexToken CaptureToken { get; }
        public RegexToken InnerCloseParenToken { get; }
 
        internal override int ChildCount => 7;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => InnerOpenParenToken,
                3 => CaptureToken,
                4 => InnerCloseParenToken,
                5 => Result,
                6 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```(?(group)result)```
    /// </summary>
    internal sealed class RegexConditionalExpressionGroupingNode : RegexConditionalGroupingNode
    {
        public RegexConditionalExpressionGroupingNode(
            RegexToken openParenToken, RegexToken questionToken,
            RegexGroupingNode grouping,
            RegexExpressionNode result, RegexToken closeParenToken)
            : base(RegexKind.ConditionalExpressionGrouping, openParenToken, questionToken, result, closeParenToken)
        {
            Debug.Assert(grouping != null);
            Grouping = grouping;
        }
 
        internal override int ChildCount => 5;
 
        public RegexGroupingNode Grouping { get; }
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => OpenParenToken,
                1 => QuestionToken,
                2 => Grouping,
                3 => Result,
                4 => CloseParenToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// Base type of all regex primitives that start with \
    /// </summary>
    internal abstract class RegexEscapeNode : RegexPrimaryExpressionNode
    {
        protected RegexEscapeNode(RegexKind kind, RegexToken backslashToken) : base(kind)
        {
            Debug.Assert(backslashToken.Kind == RegexKind.BackslashToken);
            BackslashToken = backslashToken;
        }
 
        public RegexToken BackslashToken { get; }
    }
 
    /// <summary>
    /// Base type of all regex escapes that start with \ and some informative character (like \v \t \c etc.).
    /// </summary>
    internal abstract class RegexTypeEscapeNode : RegexEscapeNode
    {
        protected RegexTypeEscapeNode(RegexKind kind, RegexToken backslashToken, RegexToken typeToken)
            : base(kind, backslashToken)
        {
            TypeToken = typeToken;
        }
 
        public RegexToken TypeToken { get; }
    }
 
    /// <summary>
    /// A basic escape that just has \ and one additional character and needs no further information.
    /// </summary>
    internal sealed class RegexSimpleEscapeNode : RegexTypeEscapeNode
    {
        public RegexSimpleEscapeNode(RegexToken backslashToken, RegexToken typeToken)
            : base(RegexKind.SimpleEscape, backslashToken, typeToken)
        {
            Debug.Assert(typeToken.Kind == RegexKind.TextToken);
        }
 
        internal override int ChildCount => 2;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => BackslashToken,
                1 => TypeToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// One of \b \B \A \G \z \Z
    /// </summary>
    internal sealed class RegexAnchorEscapeNode : RegexTypeEscapeNode
    {
        public RegexAnchorEscapeNode(RegexToken backslashToken, RegexToken typeToken)
            : base(RegexKind.AnchorEscape, backslashToken, typeToken)
        {
        }
 
        internal override int ChildCount => 2;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => BackslashToken,
                1 => TypeToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// One of \s \S \d \D \w \W
    /// </summary>
    internal sealed class RegexCharacterClassEscapeNode : RegexTypeEscapeNode
    {
        public RegexCharacterClassEscapeNode(RegexToken backslashToken, RegexToken typeToken)
            : base(RegexKind.CharacterClassEscape, backslashToken, typeToken)
        {
        }
 
        internal override int ChildCount => 2;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => BackslashToken,
                1 => TypeToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```\cX``` escape
    /// </summary>
    internal sealed class RegexControlEscapeNode : RegexTypeEscapeNode
    {
        public RegexControlEscapeNode(RegexToken backslashToken, RegexToken typeToken, RegexToken controlToken)
            : base(RegexKind.ControlEscape, backslashToken, typeToken)
        {
            ControlToken = controlToken;
        }
 
        internal override int ChildCount => 3;
 
        public RegexToken ControlToken { get; }
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => BackslashToken,
                1 => TypeToken,
                2 => ControlToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```\xFF``` escape.
    /// </summary>
    internal sealed class RegexHexEscapeNode : RegexTypeEscapeNode
    {
        public RegexHexEscapeNode(RegexToken backslashToken, RegexToken typeToken, RegexToken hexText)
            : base(RegexKind.HexEscape, backslashToken, typeToken)
        {
            HexText = hexText;
        }
 
        internal override int ChildCount => 3;
 
        public RegexToken HexText { get; }
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => BackslashToken,
                1 => TypeToken,
                2 => HexText,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```\uFFFF``` escape.
    /// </summary>
    internal sealed class RegexUnicodeEscapeNode : RegexTypeEscapeNode
    {
        public RegexUnicodeEscapeNode(RegexToken backslashToken, RegexToken typeToken, RegexToken hexText)
            : base(RegexKind.UnicodeEscape, backslashToken, typeToken)
        {
            HexText = hexText;
        }
 
        internal override int ChildCount => 3;
 
        public RegexToken HexText { get; }
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => BackslashToken,
                1 => TypeToken,
                2 => HexText,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```\'name'``` or ```\&lt;name&gt;``` escape.
    /// </summary>
    internal sealed class RegexCaptureEscapeNode : RegexEscapeNode
    {
        public RegexCaptureEscapeNode(
            RegexToken backslashToken, RegexToken openToken, RegexToken captureToken, RegexToken closeToken)
            : base(RegexKind.CaptureEscape, backslashToken)
        {
            OpenToken = openToken;
            CaptureToken = captureToken;
            CloseToken = closeToken;
        }
 
        internal override int ChildCount => 4;
 
        public RegexToken OpenToken { get; }
        public RegexToken CaptureToken { get; }
        public RegexToken CloseToken { get; }
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => BackslashToken,
                1 => OpenToken,
                2 => CaptureToken,
                3 => CloseToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```\k'name'``` or ```\k&lt;name&gt;``` escape.
    /// </summary>
    internal sealed class RegexKCaptureEscapeNode : RegexTypeEscapeNode
    {
        public RegexKCaptureEscapeNode(
            RegexToken backslashToken, RegexToken typeToken,
            RegexToken openToken, RegexToken captureToken, RegexToken closeToken)
            : base(RegexKind.KCaptureEscape, backslashToken, typeToken)
        {
            OpenToken = openToken;
            CaptureToken = captureToken;
            CloseToken = closeToken;
        }
 
        internal override int ChildCount => 5;
 
        public RegexToken OpenToken { get; }
        public RegexToken CaptureToken { get; }
        public RegexToken CloseToken { get; }
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => BackslashToken,
                1 => TypeToken,
                2 => OpenToken,
                3 => CaptureToken,
                4 => CloseToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```\1``` escape. In contexts where back-references are not allowed.
    /// </summary>
    internal sealed class RegexOctalEscapeNode : RegexEscapeNode
    {
        public RegexOctalEscapeNode(RegexToken backslashToken, RegexToken octalText)
            : base(RegexKind.OctalEscape, backslashToken)
        {
            OctalText = octalText;
        }
 
        internal override int ChildCount => 2;
 
        public RegexToken OctalText { get; }
 
        internal override RegexNodeOrToken ChildAt(int index)
        {
            switch (index)
            {
                case 0: return BackslashToken;
                case 1: return OctalText;
            }
 
            throw new InvalidOperationException();
        }
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```\1```
    /// </summary>
    internal sealed class RegexBackreferenceEscapeNode : RegexEscapeNode
    {
        public RegexBackreferenceEscapeNode(RegexToken backslashToken, RegexToken numberToken)
            : base(RegexKind.BackreferenceEscape, backslashToken)
        {
            NumberToken = numberToken;
        }
 
        internal override int ChildCount => 2;
 
        public RegexToken NumberToken { get; }
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => BackslashToken,
                1 => NumberToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
 
    /// <summary>
    /// ```\p{...}```
    /// </summary>
    internal sealed class RegexCategoryEscapeNode : RegexEscapeNode
    {
        public RegexCategoryEscapeNode(
            RegexToken backslashToken, RegexToken typeToken, RegexToken openBraceToken, RegexToken categoryToken, RegexToken closeBraceToken)
            : base(RegexKind.CategoryEscape, backslashToken)
        {
            Debug.Assert(openBraceToken.Kind == RegexKind.OpenBraceToken);
            Debug.Assert(closeBraceToken.Kind == RegexKind.CloseBraceToken);
            TypeToken = typeToken;
            OpenBraceToken = openBraceToken;
            CategoryToken = categoryToken;
            CloseBraceToken = closeBraceToken;
        }
 
        public RegexToken TypeToken { get; }
        public RegexToken OpenBraceToken { get; }
        public RegexToken CategoryToken { get; }
        public RegexToken CloseBraceToken { get; }
 
        internal override int ChildCount => 5;
 
        internal override RegexNodeOrToken ChildAt(int index)
            => index switch
            {
                0 => BackslashToken,
                1 => TypeToken,
                2 => OpenBraceToken,
                3 => CategoryToken,
                4 => CloseBraceToken,
                _ => throw new InvalidOperationException(),
            };
 
        public override void Accept(IRegexNodeVisitor visitor)
            => visitor.Visit(this);
    }
}