public void TreeBuilding_BuildExpressionNode_ShouldBuildBalancingGroupNode()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.GroupStart, "(", 0),
                new Token(TokenType.GroupDirectiveStart, "?", 1),
                new Token(TokenType.NamedIdentifierStartOrLookBehindMarker, "<", 2),
                new Token(TokenType.Literal, "foo", 3),
                new Token(TokenType.BalancingGroupNamedIdentifierSeparator, "-", 6),
                new Token(TokenType.Literal, "bar", 7),
                new Token(TokenType.NamedIdentifierEnd, ">", 10),
                new Token(TokenType.Literal, "baz", 11),
                new Token(TokenType.GroupEnd, ")", 14)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new GroupNode("(?<foo-bar>baz)", 0,
                        new LiteralNode("baz", 11))
                    { NamedIdentifier = "foo", BalancingGroupIdentifier = "bar" }
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilder_BuildExpressionNode_ShouldBuildMaxParametizedQuantifierNodeForSingleCharacterLiteral()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.Literal, "a", 0),
                new Token(TokenType.ParametizedQuantifierStart, "{", 1),
                new Token(TokenType.ParametizedQuantifierRangeSeparator, ",", 2),
                new Token(TokenType.Number, "3", 3),
                new Token(TokenType.ParametizedQuantifierEnd, "}", 4)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new QuantifierNode("a{,3}", 0,
                        null, 3,
                        new LiteralNode("a", 0))
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilder_BuildExpressionNode_ShouldBuildMinMaxParametizedQuantifierNodeAroundLastCharacterOfMultiCharacterLiteral()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.Literal, "abc", 0),
                new Token(TokenType.ParametizedQuantifierStart, "{", 3),
                new Token(TokenType.Number, "3", 4),
                new Token(TokenType.ParametizedQuantifierRangeSeparator, ",", 5),
                new Token(TokenType.Number, "16", 6),
                new Token(TokenType.ParametizedQuantifierEnd, "}", 8)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new Node[]
                {
                    new LiteralNode("ab", 0),
                    new QuantifierNode("c{3,16}", 2,
                        3, 16,
                        new LiteralNode("c", 2))
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilder_BuildExpressionNode_ShouldConvertParseFailureToken()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.ParseFailure, "x", 0)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new ParseFailureNode("x", 0, "unrecognised token")
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilder_BuildExpressionNode_ShouldBuildLiteralTokenIntoLiteralNode()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.Literal, "abc", 0)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new LiteralNode("abc", 0)
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilding_BuildExpressionNode_ShouldBuildEmptyGroupNode()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.GroupStart, "(", 0),
                new Token(TokenType.GroupEnd, ")", 1),
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new GroupNode("()", 0)
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilder_BuildExpressionNode_ShouldBuildCharacterEscapeIntoEscapeCharacterNode()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.CharacterEscapeMarker, @"\", 0),
                new Token(TokenType.CharacterEscapeData, ")", 1)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new EscapedCharacterNode(@"\)", 0, ")")
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilder_BuildExpressionNode_ShouldBuildNonDigitsCharacterClassNode()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.CharacterEscapeMarker, @"\", 10),
                new Token(TokenType.CharacterEscapeData, "D", 11)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new CharacterClassNode(@"\D", 10, CharacterClass.NonDigits)
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilding_BuildExpressionNode_ShouldBuildCapturingGroupNode()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.GroupStart, "(", 0),
                new Token(TokenType.Literal, "abc", 1),
                new Token(TokenType.GroupEnd, ")", 4),
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new GroupNode("(abc)", 0,
                        new LiteralNode("abc", 1))
                    { GroupMode = GroupMode.CapturingGroup }
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilder_Build_ShouldAssignIds()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.Literal, "1", 0),    // 1
                new Token(TokenType.Literal, "2", 1),    // 2
                new Token(TokenType.Literal, "3", 2),    // 3
            };

            // Act
            var nodes = new TreeBuilder().Build(tokens);

            // Assert
            CollectionAssert.AreEqual(new Node[]
                {
                    new LiteralNode("1", 0) { NodeId = 1 },
                    new LiteralNode("2", 1) { NodeId = 2 },
                    new LiteralNode("3", 2) { NodeId = 3 },
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilding_BuildExpressionNode_ShouldBuildNestedGroupNodeWithLiteralContent()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.GroupStart, "(", 0),
                new Token(TokenType.Literal, "abc", 1),
                new Token(TokenType.GroupStart, "(", 4),
                new Token(TokenType.Literal, "def", 5),
                new Token(TokenType.GroupEnd, ")", 8),
                new Token(TokenType.Literal, "ghi", 9),
                new Token(TokenType.GroupEnd, ")", 12),
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new GroupNode("(abc(def)ghi)", 0,
                        new LiteralNode("abc", 1),
                        new GroupNode("(def)", 4,
                            new LiteralNode("def", 5)),
                        new LiteralNode("ghi", 9))
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilding_BuildExpressionNode_ShouldBuildParseFailureNodeForUnclosedGroup()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.GroupStart, "(", 0)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new ParseFailureNode("(", 0, "group is never closed")
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilder_BuildExpressionNode_ShouldHandleMalformedParametizedQuantifierTokensAsLiteral()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.Literal, "a", 0),
                new Token(TokenType.ParametizedQuantifierStart, "{", 1),
                new Token(TokenType.Number, "8", 2),
                new Token(TokenType.ParametizedQuantifierRangeSeparator, ",", 3),
                new Token(TokenType.ParametizedQuantifierRangeSeparator, ",", 4),
                new Token(TokenType.ParametizedQuantifierEnd, "}", 5)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new LiteralNode("a", 0),
                    new LiteralNode("{8,,}", 1)
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilder_BuildExpressionNode_ShouldBuildZeroOrOneQuantifierNodeForSingleCharacterLiteral()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.Literal, "a", 0),
                new Token(TokenType.Quantifier, "?", 1)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new QuantifierNode("a?", 0,
                        0, 1,
                        new LiteralNode("a", 0))
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilder_BuildExpressionNode_ShouldBuildQuantifierNodeAroundLastCharacterOfMultiCharacterLiteral()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.Literal, "abc", 0),
                new Token(TokenType.Quantifier, "*", 1)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new Node[]
                {
                    new LiteralNode("ab", 0),
                    new QuantifierNode("c*", 2,
                        0, null,
                        new LiteralNode("c", 2))
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilding_BuildExpressionNode_ShouldBuildPositiveLookBehindGroupNode()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.GroupStart, "(", 0),
                new Token(TokenType.GroupDirectiveStart, "?", 1),
                new Token(TokenType.NamedIdentifierStartOrLookBehindMarker, "<", 2),
                new Token(TokenType.PositiveLookBehindMarker, "=", 3),
                new Token(TokenType.Literal, "abc", 4),
                new Token(TokenType.GroupEnd, ")", 7),
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new GroupNode("(?<=abc)", 0,
                        new LiteralNode("abc", 4))
                    { GroupMode = GroupMode.PositiveLookBehind }
                },
                nodes.Children.ToArray()
            );
        }
        ActionResult AnalyzeVerbose(string expression)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();

            var tokens = Tokenizer.Tokenize(expression);
            var rootNode = new TreeBuilder().Build(tokens);

            stopwatch.Stop();

            ViewData["TimeTaken"] = stopwatch.Elapsed;

            ViewData["Tokens"] = tokens;

            ViewData["ExpressionMarkup"] = RenderExpressionAsHtml(rootNode);
            ViewData["AllNodes"] = FlattenNodes(rootNode.Children);
            ViewData["NodesMarkup"] = RenderNodesAsHtml(rootNode.Children);

            return View(Views.Verbose);
        }
        public void TreeBuilder_BuildExpressionNode_ShouldConvertUnexpectedTokenToParseFailureNode()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.GroupEnd, ")", 0)
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new ParseFailureNode(")", 0, "unexpected token")
                },
                nodes.Children.ToArray()
            );
        }
        public void TreeBuilding_BuildExpressionNode_ShouldBuildNegativeLookAheadGroupNode()
        {
            // Arrange
            var tokens = new[]
            {
                new Token(TokenType.GroupStart, "(", 0),
                new Token(TokenType.GroupDirectiveStart, "?", 1),
                new Token(TokenType.NegativeLookAheadMarker, "!", 2),
                new Token(TokenType.Literal, "abc", 3),
                new Token(TokenType.GroupEnd, ")", 6),
            };

            // Act
            var nodes = new TreeBuilder().BuildExpressionNode(tokens);

            // Assert
            CollectionAssert.AreEqual(new[]
                {
                    new GroupNode("(?!abc)", 0,
                        new LiteralNode("abc", 3))
                    { GroupMode = GroupMode.NegativeLookAhead }
                },
                nodes.Children.ToArray()
            );
        }