private ExpressionNode FindOrInitializeExpressionNode(Expr expr, out ExpressionNode parentNode)
        {
            // 1. Ищем дерево, в котором содержится данный expr
            var root = SeekRootTree(expr);

            parentNode = null;
            // 2. Если дерево не нашлось, создаем новый ExpressionNode с expr в качестве листа дерева и initializedNewNode = true
            if (root == null)
            {
                ExpressionNode newNode = new ExpressionNode(expr);
                if (currentTree == null)
                {
                    currentTree = new ExpressionTree();
                    currentTree.AddNode(newNode);
                    exprForest.Add(currentTree);
                }
                return(newNode);
            }


            // 3. Если дерево нашлось, запускаем поиск в ширину от корня, чтобы найти самое актуальное вхождение переменной в дереве
            var lastExpression = BFS(root, expr, out parentNode);

            if (lastExpression == null)
            {
                return(new ExpressionNode(expr));
            }

            // 4. Возвращаем найденное вхождение
            return(lastExpression);
        }
        public List <Node> Optimize(List <Node> inputNodes, out bool applied)
        {
            exprForest = new List <ExpressionTree>();
            var nodes = inputNodes.OfType <Assign>()
                        .Where(assn => assn.Operation != OpCode.Copy && assn.Left != null);

            foreach (var node in nodes)
            {
                currentTree = null;
                var leftNode  = FindOrInitializeExpressionNode(node.Left, out ExpressionNode leftParentNode);
                var rightNode = FindOrInitializeExpressionNode(node.Right, out ExpressionNode rightParentNode);
                if (leftParentNode == null && rightParentNode == null)
                {
                    // создать новый ExpressionNode:
                    var resultNode = new ExpressionNode(node.Result);
                    resultNode.LeftNode  = leftNode;
                    resultNode.RightNode = rightNode;
                    resultNode.OpCode    = node.Operation;
                    currentTree.AddNode(resultNode);
                }
                else
                {
                    leftParentNode.AssigneeList.Add(node.Result);
                    applied = true;
                }
            }
            // Тут мы применяем оптимизацию, обходя дерево и строя новые выражения
            applied = false;
            return(RecoveryThreeAddrCode(inputNodes));
        }