| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| //! [`TreeNode`] for visiting and rewriting expression and plan trees |
| |
| use crate::Result; |
| use std::collections::HashMap; |
| use std::hash::Hash; |
| use std::sync::Arc; |
| |
| /// These macros are used to determine continuation during transforming traversals. |
| macro_rules! handle_transform_recursion { |
| ($F_DOWN:expr, $F_CHILD:expr, $F_UP:expr) => {{ |
| $F_DOWN? |
| .transform_children(|n| n.map_children($F_CHILD))? |
| .transform_parent($F_UP) |
| }}; |
| } |
| |
| /// API for inspecting and rewriting tree data structures. |
| /// |
| /// The `TreeNode` API is used to express algorithms separately from traversing |
| /// the structure of `TreeNode`s, avoiding substantial code duplication. |
| /// |
| /// This trait is implemented for plans ([`ExecutionPlan`], [`LogicalPlan`]) and |
| /// expression trees ([`PhysicalExpr`], [`Expr`]) as well as Plan+Payload |
| /// combinations [`PlanContext`] and [`ExprContext`]. |
| /// |
| /// # Overview |
| /// There are three categories of TreeNode APIs: |
| /// |
| /// 1. "Inspecting" APIs to traverse a tree of `&TreeNodes`: |
| /// [`apply`], [`visit`], [`exists`]. |
| /// |
| /// 2. "Transforming" APIs that traverse and consume a tree of `TreeNode`s |
| /// producing possibly changed `TreeNode`s: [`transform`], [`transform_up`], |
| /// [`transform_down`], [`transform_down_up`], and [`rewrite`]. |
| /// |
| /// 3. Internal APIs used to implement the `TreeNode` API: [`apply_children`], |
| /// and [`map_children`]. |
| /// |
| /// | Traversal Order | Inspecting | Transforming | |
| /// | --- | --- | --- | |
| /// | top-down | [`apply`], [`exists`] | [`transform_down`]| |
| /// | bottom-up | | [`transform`] , [`transform_up`]| |
| /// | combined with separate `f_down` and `f_up` closures | | [`transform_down_up`] | |
| /// | combined with `f_down()` and `f_up()` in an object | [`visit`] | [`rewrite`] | |
| /// |
| /// **Note**:while there is currently no in-place mutation API that uses `&mut |
| /// TreeNode`, the transforming APIs are efficient and optimized to avoid |
| /// cloning. |
| /// |
| /// [`apply`]: Self::apply |
| /// [`visit`]: Self::visit |
| /// [`exists`]: Self::exists |
| /// [`transform`]: Self::transform |
| /// [`transform_up`]: Self::transform_up |
| /// [`transform_down`]: Self::transform_down |
| /// [`transform_down_up`]: Self::transform_down_up |
| /// [`rewrite`]: Self::rewrite |
| /// [`apply_children`]: Self::apply_children |
| /// [`map_children`]: Self::map_children |
| /// |
| /// # Terminology |
| /// The following terms are used in this trait |
| /// |
| /// * `f_down`: Invoked before any children of the current node are visited. |
| /// * `f_up`: Invoked after all children of the current node are visited. |
| /// * `f`: closure that is applied to the current node. |
| /// * `map_*`: applies a transformation to rewrite owned nodes |
| /// * `apply_*`: invokes a function on borrowed nodes |
| /// * `transform_`: applies a transformation to rewrite owned nodes |
| /// |
| /// <!-- Since these are in the datafusion-common crate, can't use intra doc links) --> |
| /// [`ExecutionPlan`]: https://docs.rs/datafusion/latest/datafusion/physical_plan/trait.ExecutionPlan.html |
| /// [`PhysicalExpr`]: https://docs.rs/datafusion/latest/datafusion/physical_plan/trait.PhysicalExpr.html |
| /// [`LogicalPlan`]: https://docs.rs/datafusion-expr/latest/datafusion_expr/logical_plan/enum.LogicalPlan.html |
| /// [`Expr`]: https://docs.rs/datafusion-expr/latest/datafusion_expr/expr/enum.Expr.html |
| /// [`PlanContext`]: https://docs.rs/datafusion/latest/datafusion/physical_plan/tree_node/struct.PlanContext.html |
| /// [`ExprContext`]: https://docs.rs/datafusion/latest/datafusion/physical_expr/tree_node/struct.ExprContext.html |
| pub trait TreeNode: Sized { |
| /// Visit the tree node with a [`TreeNodeVisitor`], performing a |
| /// depth-first walk of the node and its children. |
| /// |
| /// [`TreeNodeVisitor::f_down()`] is called in top-down order (before |
| /// children are visited), [`TreeNodeVisitor::f_up()`] is called in |
| /// bottom-up order (after children are visited). |
| /// |
| /// # Return Value |
| /// Specifies how the tree walk ended. See [`TreeNodeRecursion`] for details. |
| /// |
| /// # See Also: |
| /// * [`Self::apply`] for inspecting nodes with a closure |
| /// * [`Self::rewrite`] to rewrite owned `TreeNode`s |
| /// |
| /// # Example |
| /// Consider the following tree structure: |
| /// ```text |
| /// ParentNode |
| /// left: ChildNode1 |
| /// right: ChildNode2 |
| /// ``` |
| /// |
| /// Here, the nodes would be visited using the following order: |
| /// ```text |
| /// TreeNodeVisitor::f_down(ParentNode) |
| /// TreeNodeVisitor::f_down(ChildNode1) |
| /// TreeNodeVisitor::f_up(ChildNode1) |
| /// TreeNodeVisitor::f_down(ChildNode2) |
| /// TreeNodeVisitor::f_up(ChildNode2) |
| /// TreeNodeVisitor::f_up(ParentNode) |
| /// ``` |
| #[cfg_attr(feature = "recursive_protection", recursive::recursive)] |
| fn visit<'n, V: TreeNodeVisitor<'n, Node = Self>>( |
| &'n self, |
| visitor: &mut V, |
| ) -> Result<TreeNodeRecursion> { |
| visitor |
| .f_down(self)? |
| .visit_children(|| self.apply_children(|c| c.visit(visitor)))? |
| .visit_parent(|| visitor.f_up(self)) |
| } |
| |
| /// Rewrite the tree node with a [`TreeNodeRewriter`], performing a |
| /// depth-first walk of the node and its children. |
| /// |
| /// [`TreeNodeRewriter::f_down()`] is called in top-down order (before |
| /// children are visited), [`TreeNodeRewriter::f_up()`] is called in |
| /// bottom-up order (after children are visited). |
| /// |
| /// Note: If using the default [`TreeNodeRewriter::f_up`] or |
| /// [`TreeNodeRewriter::f_down`] that do nothing, consider using |
| /// [`Self::transform_down`] instead. |
| /// |
| /// # Return Value |
| /// The returns value specifies how the tree walk should proceed. See |
| /// [`TreeNodeRecursion`] for details. If an [`Err`] is returned, the |
| /// recursion stops immediately. |
| /// |
| /// # See Also |
| /// * [`Self::visit`] for inspecting (without modification) `TreeNode`s |
| /// * [Self::transform_down_up] for a top-down (pre-order) traversal. |
| /// * [Self::transform_down] for a top-down (pre-order) traversal. |
| /// * [`Self::transform_up`] for a bottom-up (post-order) traversal. |
| /// |
| /// # Example |
| /// Consider the following tree structure: |
| /// ```text |
| /// ParentNode |
| /// left: ChildNode1 |
| /// right: ChildNode2 |
| /// ``` |
| /// |
| /// Here, the nodes would be visited using the following order: |
| /// ```text |
| /// TreeNodeRewriter::f_down(ParentNode) |
| /// TreeNodeRewriter::f_down(ChildNode1) |
| /// TreeNodeRewriter::f_up(ChildNode1) |
| /// TreeNodeRewriter::f_down(ChildNode2) |
| /// TreeNodeRewriter::f_up(ChildNode2) |
| /// TreeNodeRewriter::f_up(ParentNode) |
| /// ``` |
| #[cfg_attr(feature = "recursive_protection", recursive::recursive)] |
| fn rewrite<R: TreeNodeRewriter<Node = Self>>( |
| self, |
| rewriter: &mut R, |
| ) -> Result<Transformed<Self>> { |
| handle_transform_recursion!(rewriter.f_down(self), |c| c.rewrite(rewriter), |n| { |
| rewriter.f_up(n) |
| }) |
| } |
| |
| /// Applies `f` to the node then each of its children, recursively (a |
| /// top-down, pre-order traversal). |
| /// |
| /// The return [`TreeNodeRecursion`] controls the recursion and can cause |
| /// an early return. |
| /// |
| /// # See Also |
| /// * [`Self::transform_down`] for the equivalent transformation API. |
| /// * [`Self::visit`] for both top-down and bottom up traversal. |
| fn apply<'n, F: FnMut(&'n Self) -> Result<TreeNodeRecursion>>( |
| &'n self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| #[cfg_attr(feature = "recursive_protection", recursive::recursive)] |
| fn apply_impl<'n, N: TreeNode, F: FnMut(&'n N) -> Result<TreeNodeRecursion>>( |
| node: &'n N, |
| f: &mut F, |
| ) -> Result<TreeNodeRecursion> { |
| f(node)?.visit_children(|| node.apply_children(|c| apply_impl(c, f))) |
| } |
| |
| apply_impl(self, &mut f) |
| } |
| |
| /// Recursively rewrite the node's children and then the node using `f` |
| /// (a bottom-up post-order traversal). |
| /// |
| /// A synonym of [`Self::transform_up`]. |
| fn transform<F: FnMut(Self) -> Result<Transformed<Self>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<Self>> { |
| self.transform_up(f) |
| } |
| |
| /// Recursively rewrite the tree using `f` in a top-down (pre-order) |
| /// fashion. |
| /// |
| /// `f` is applied to the node first, and then its children. |
| /// |
| /// # See Also |
| /// * [`Self::transform_up`] for a bottom-up (post-order) traversal. |
| /// * [Self::transform_down_up] for a combined traversal with closures |
| /// * [`Self::rewrite`] for a combined traversal with a visitor |
| fn transform_down<F: FnMut(Self) -> Result<Transformed<Self>>>( |
| self, |
| mut f: F, |
| ) -> Result<Transformed<Self>> { |
| #[cfg_attr(feature = "recursive_protection", recursive::recursive)] |
| fn transform_down_impl<N: TreeNode, F: FnMut(N) -> Result<Transformed<N>>>( |
| node: N, |
| f: &mut F, |
| ) -> Result<Transformed<N>> { |
| f(node)?.transform_children(|n| n.map_children(|c| transform_down_impl(c, f))) |
| } |
| |
| transform_down_impl(self, &mut f) |
| } |
| |
| /// Recursively rewrite the node using `f` in a bottom-up (post-order) |
| /// fashion. |
| /// |
| /// `f` is applied to the node's children first, and then to the node itself. |
| /// |
| /// # See Also |
| /// * [`Self::transform_down`] top-down (pre-order) traversal. |
| /// * [Self::transform_down_up] for a combined traversal with closures |
| /// * [`Self::rewrite`] for a combined traversal with a visitor |
| fn transform_up<F: FnMut(Self) -> Result<Transformed<Self>>>( |
| self, |
| mut f: F, |
| ) -> Result<Transformed<Self>> { |
| #[cfg_attr(feature = "recursive_protection", recursive::recursive)] |
| fn transform_up_impl<N: TreeNode, F: FnMut(N) -> Result<Transformed<N>>>( |
| node: N, |
| f: &mut F, |
| ) -> Result<Transformed<N>> { |
| node.map_children(|c| transform_up_impl(c, f))? |
| .transform_parent(f) |
| } |
| |
| transform_up_impl(self, &mut f) |
| } |
| |
| /// Transforms the node using `f_down` while traversing the tree top-down |
| /// (pre-order), and using `f_up` while traversing the tree bottom-up |
| /// (post-order). |
| /// |
| /// The method behaves the same as calling [`Self::transform_down`] followed |
| /// by [`Self::transform_up`] on the same node. Use this method if you want |
| /// to start the `f_up` process right where `f_down` jumps. This can make |
| /// the whole process faster by reducing the number of `f_up` steps. |
| /// |
| /// # See Also |
| /// * [`Self::transform_up`] for a bottom-up (post-order) traversal. |
| /// * [Self::transform_down] for a top-down (pre-order) traversal. |
| /// * [`Self::rewrite`] for a combined traversal with a visitor |
| /// |
| /// # Example |
| /// Consider the following tree structure: |
| /// ```text |
| /// ParentNode |
| /// left: ChildNode1 |
| /// right: ChildNode2 |
| /// ``` |
| /// |
| /// The nodes are visited using the following order: |
| /// ```text |
| /// f_down(ParentNode) |
| /// f_down(ChildNode1) |
| /// f_up(ChildNode1) |
| /// f_down(ChildNode2) |
| /// f_up(ChildNode2) |
| /// f_up(ParentNode) |
| /// ``` |
| /// |
| /// See [`TreeNodeRecursion`] for more details on controlling the traversal. |
| /// |
| /// If `f_down` or `f_up` returns [`Err`], the recursion stops immediately. |
| /// |
| /// Example: |
| /// ```text |
| /// | +---+ |
| /// | | J | |
| /// | +---+ |
| /// | | |
| /// | +---+ |
| /// TreeNodeRecursion::Continue | | I | |
| /// | +---+ |
| /// | | |
| /// | +---+ |
| /// \|/ | F | |
| /// ' +---+ |
| /// / \ ___________________ |
| /// When `f_down` is +---+ \ ---+ |
| /// applied on node "E", | E | | G | |
| /// it returns with "Jump". +---+ +---+ |
| /// | | |
| /// +---+ +---+ |
| /// | C | | H | |
| /// +---+ +---+ |
| /// / \ |
| /// +---+ +---+ |
| /// | B | | D | |
| /// +---+ +---+ |
| /// | |
| /// +---+ |
| /// | A | |
| /// +---+ |
| /// |
| /// Instead of starting from leaf nodes, `f_up` starts from the node "E". |
| /// +---+ |
| /// | | J | |
| /// | +---+ |
| /// | | |
| /// | +---+ |
| /// | | I | |
| /// | +---+ |
| /// | | |
| /// / +---+ |
| /// / | F | |
| /// / +---+ |
| /// / / \ ______________________ |
| /// | +---+ . \ ---+ |
| /// | | E | /|\ After `f_down` jumps | G | |
| /// | +---+ | on node E, `f_up` +---+ |
| /// \------| ---/ if applied on node E. | |
| /// +---+ +---+ |
| /// | C | | H | |
| /// +---+ +---+ |
| /// / \ |
| /// +---+ +---+ |
| /// | B | | D | |
| /// +---+ +---+ |
| /// | |
| /// +---+ |
| /// | A | |
| /// +---+ |
| /// ``` |
| fn transform_down_up< |
| FD: FnMut(Self) -> Result<Transformed<Self>>, |
| FU: FnMut(Self) -> Result<Transformed<Self>>, |
| >( |
| self, |
| mut f_down: FD, |
| mut f_up: FU, |
| ) -> Result<Transformed<Self>> { |
| #[cfg_attr(feature = "recursive_protection", recursive::recursive)] |
| fn transform_down_up_impl< |
| N: TreeNode, |
| FD: FnMut(N) -> Result<Transformed<N>>, |
| FU: FnMut(N) -> Result<Transformed<N>>, |
| >( |
| node: N, |
| f_down: &mut FD, |
| f_up: &mut FU, |
| ) -> Result<Transformed<N>> { |
| handle_transform_recursion!( |
| f_down(node), |
| |c| transform_down_up_impl(c, f_down, f_up), |
| f_up |
| ) |
| } |
| |
| transform_down_up_impl(self, &mut f_down, &mut f_up) |
| } |
| |
| /// Returns true if `f` returns true for any node in the tree. |
| /// |
| /// Stops recursion as soon as a matching node is found |
| fn exists<F: FnMut(&Self) -> Result<bool>>(&self, mut f: F) -> Result<bool> { |
| let mut found = false; |
| self.apply(|n| { |
| Ok(if f(n)? { |
| found = true; |
| TreeNodeRecursion::Stop |
| } else { |
| TreeNodeRecursion::Continue |
| }) |
| }) |
| .map(|_| found) |
| } |
| |
| /// Low-level API used to implement other APIs. |
| /// |
| /// If you want to implement the [`TreeNode`] trait for your own type, you |
| /// should implement this method and [`Self::map_children`]. |
| /// |
| /// Users should use one of the higher level APIs described on [`Self`]. |
| /// |
| /// Description: Apply `f` to inspect node's children (but not the node |
| /// itself). |
| fn apply_children<'n, F: FnMut(&'n Self) -> Result<TreeNodeRecursion>>( |
| &'n self, |
| f: F, |
| ) -> Result<TreeNodeRecursion>; |
| |
| /// Low-level API used to implement other APIs. |
| /// |
| /// If you want to implement the [`TreeNode`] trait for your own type, you |
| /// should implement this method and [`Self::apply_children`]. |
| /// |
| /// Users should use one of the higher level APIs described on [`Self`]. |
| /// |
| /// Description: Apply `f` to rewrite the node's children (but not the node itself). |
| fn map_children<F: FnMut(Self) -> Result<Transformed<Self>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<Self>>; |
| } |
| |
| /// A [Visitor](https://en.wikipedia.org/wiki/Visitor_pattern) for recursively |
| /// inspecting [`TreeNode`]s via [`TreeNode::visit`]. |
| /// |
| /// See [`TreeNode`] for more details on available APIs |
| /// |
| /// When passed to [`TreeNode::visit`], [`TreeNodeVisitor::f_down`] and |
| /// [`TreeNodeVisitor::f_up`] are invoked recursively on the tree. |
| /// See [`TreeNodeRecursion`] for more details on controlling the traversal. |
| /// |
| /// # Return Value |
| /// The returns value of `f_up` and `f_down` specifies how the tree walk should |
| /// proceed. See [`TreeNodeRecursion`] for details. If an [`Err`] is returned, |
| /// the recursion stops immediately. |
| /// |
| /// Note: If using the default implementations of [`TreeNodeVisitor::f_up`] or |
| /// [`TreeNodeVisitor::f_down`] that do nothing, consider using |
| /// [`TreeNode::apply`] instead. |
| /// |
| /// # See Also: |
| /// * [`TreeNode::rewrite`] to rewrite owned `TreeNode`s |
| pub trait TreeNodeVisitor<'n>: Sized { |
| /// The node type which is visitable. |
| type Node: TreeNode; |
| |
| /// Invoked while traversing down the tree, before any children are visited. |
| /// Default implementation continues the recursion. |
| fn f_down(&mut self, _node: &'n Self::Node) -> Result<TreeNodeRecursion> { |
| Ok(TreeNodeRecursion::Continue) |
| } |
| |
| /// Invoked while traversing up the tree after children are visited. Default |
| /// implementation continues the recursion. |
| fn f_up(&mut self, _node: &'n Self::Node) -> Result<TreeNodeRecursion> { |
| Ok(TreeNodeRecursion::Continue) |
| } |
| } |
| |
| /// A [Visitor](https://en.wikipedia.org/wiki/Visitor_pattern) for recursively |
| /// rewriting [`TreeNode`]s via [`TreeNode::rewrite`]. |
| /// |
| /// For example you can implement this trait on a struct to rewrite `Expr` or |
| /// `LogicalPlan` that needs to track state during the rewrite. |
| /// |
| /// See [`TreeNode`] for more details on available APIs |
| /// |
| /// When passed to [`TreeNode::rewrite`], [`TreeNodeRewriter::f_down`] and |
| /// [`TreeNodeRewriter::f_up`] are invoked recursively on the tree. |
| /// See [`TreeNodeRecursion`] for more details on controlling the traversal. |
| /// |
| /// # Return Value |
| /// The returns value of `f_up` and `f_down` specifies how the tree walk should |
| /// proceed. See [`TreeNodeRecursion`] for details. If an [`Err`] is returned, |
| /// the recursion stops immediately. |
| /// |
| /// Note: If using the default implementations of [`TreeNodeRewriter::f_up`] or |
| /// [`TreeNodeRewriter::f_down`] that do nothing, consider using |
| /// [`TreeNode::transform_up`] or [`TreeNode::transform_down`] instead. |
| /// |
| /// # See Also: |
| /// * [`TreeNode::visit`] to inspect borrowed `TreeNode`s |
| pub trait TreeNodeRewriter: Sized { |
| /// The node type which is rewritable. |
| type Node: TreeNode; |
| |
| /// Invoked while traversing down the tree before any children are rewritten. |
| /// Default implementation returns the node as is and continues recursion. |
| fn f_down(&mut self, node: Self::Node) -> Result<Transformed<Self::Node>> { |
| Ok(Transformed::no(node)) |
| } |
| |
| /// Invoked while traversing up the tree after all children have been rewritten. |
| /// Default implementation returns the node as is and continues recursion. |
| fn f_up(&mut self, node: Self::Node) -> Result<Transformed<Self::Node>> { |
| Ok(Transformed::no(node)) |
| } |
| } |
| |
| /// Controls how [`TreeNode`] recursions should proceed. |
| #[derive(Debug, PartialEq, Clone, Copy)] |
| pub enum TreeNodeRecursion { |
| /// Continue recursion with the next node. |
| Continue, |
| /// In top-down traversals, skip recursing into children but continue with |
| /// the next node, which actually means pruning of the subtree. |
| /// |
| /// In bottom-up traversals, bypass calling bottom-up closures till the next |
| /// leaf node. |
| /// |
| /// In combined traversals, if it is the `f_down` (pre-order) phase, execution |
| /// "jumps" to the next `f_up` (post-order) phase by shortcutting its children. |
| /// If it is the `f_up` (post-order) phase, execution "jumps" to the next `f_down` |
| /// (pre-order) phase by shortcutting its parent nodes until the first parent node |
| /// having unvisited children path. |
| Jump, |
| /// Stop recursion. |
| Stop, |
| } |
| |
| impl TreeNodeRecursion { |
| /// Continues visiting nodes with `f` depending on the current [`TreeNodeRecursion`] |
| /// value and the fact that `f` is visiting the current node's children. |
| pub fn visit_children<F: FnOnce() -> Result<TreeNodeRecursion>>( |
| self, |
| f: F, |
| ) -> Result<TreeNodeRecursion> { |
| match self { |
| TreeNodeRecursion::Continue => f(), |
| TreeNodeRecursion::Jump => Ok(TreeNodeRecursion::Continue), |
| TreeNodeRecursion::Stop => Ok(self), |
| } |
| } |
| |
| /// Continues visiting nodes with `f` depending on the current [`TreeNodeRecursion`] |
| /// value and the fact that `f` is visiting the current node's sibling. |
| pub fn visit_sibling<F: FnOnce() -> Result<TreeNodeRecursion>>( |
| self, |
| f: F, |
| ) -> Result<TreeNodeRecursion> { |
| match self { |
| TreeNodeRecursion::Continue | TreeNodeRecursion::Jump => f(), |
| TreeNodeRecursion::Stop => Ok(self), |
| } |
| } |
| |
| /// Continues visiting nodes with `f` depending on the current [`TreeNodeRecursion`] |
| /// value and the fact that `f` is visiting the current node's parent. |
| pub fn visit_parent<F: FnOnce() -> Result<TreeNodeRecursion>>( |
| self, |
| f: F, |
| ) -> Result<TreeNodeRecursion> { |
| match self { |
| TreeNodeRecursion::Continue => f(), |
| TreeNodeRecursion::Jump | TreeNodeRecursion::Stop => Ok(self), |
| } |
| } |
| } |
| |
| /// Result of tree walk / transformation APIs |
| /// |
| /// `Transformed` is a wrapper around the tree node data (e.g. `Expr` or |
| /// `LogicalPlan`). It is used to indicate whether the node was transformed |
| /// and how the recursion should proceed. |
| /// |
| /// [`TreeNode`] API users control the transformation by returning: |
| /// - The resulting (possibly transformed) node, |
| /// - `transformed`: flag indicating whether any change was made to the node |
| /// - `tnr`: [`TreeNodeRecursion`] specifying how to proceed with the recursion. |
| /// |
| /// At the end of the transformation, the return value will contain: |
| /// - The final (possibly transformed) tree, |
| /// - `transformed`: flag indicating whether any change was made to the node |
| /// - `tnr`: [`TreeNodeRecursion`] specifying how the recursion ended. |
| /// |
| /// See also |
| /// * [`Transformed::update_data`] to modify the node without changing the `transformed` flag |
| /// * [`Transformed::map_data`] for fallable operation that return the same type |
| /// * [`Transformed::transform_data`] to chain fallable transformations |
| /// * [`TransformedResult`] for working with `Result<Transformed<U>>` |
| /// |
| /// # Examples |
| /// |
| /// Use [`Transformed::yes`] and [`Transformed::no`] to signal that a node was |
| /// rewritten and the recursion should continue: |
| /// |
| /// ``` |
| /// # use datafusion_common::tree_node::Transformed; |
| /// # // note use i64 instead of Expr as Expr is not in datafusion-common |
| /// # fn orig_expr() -> i64 { 1 } |
| /// # fn make_new_expr(i: i64) -> i64 { 2 } |
| /// let expr = orig_expr(); |
| /// |
| /// // Create a new `Transformed` object signaling the node was not rewritten |
| /// let ret = Transformed::no(expr.clone()); |
| /// assert!(!ret.transformed); |
| /// |
| /// // Create a new `Transformed` object signaling the node was rewritten |
| /// let ret = Transformed::yes(expr); |
| /// assert!(ret.transformed) |
| /// ``` |
| /// |
| /// Access the node within the `Transformed` object: |
| /// ``` |
| /// # use datafusion_common::tree_node::Transformed; |
| /// # // note use i64 instead of Expr as Expr is not in datafusion-common |
| /// # fn orig_expr() -> i64 { 1 } |
| /// # fn make_new_expr(i: i64) -> i64 { 2 } |
| /// let expr = orig_expr(); |
| /// |
| /// // `Transformed` object signaling the node was not rewritten |
| /// let ret = Transformed::no(expr.clone()); |
| /// // Access the inner object using .data |
| /// assert_eq!(expr, ret.data); |
| /// ``` |
| /// |
| /// Transform the node within the `Transformed` object. |
| /// |
| /// ``` |
| /// # use datafusion_common::tree_node::Transformed; |
| /// # // note use i64 instead of Expr as Expr is not in datafusion-common |
| /// # fn orig_expr() -> i64 { 1 } |
| /// # fn make_new_expr(i: i64) -> i64 { 2 } |
| /// let expr = orig_expr(); |
| /// let ret = Transformed::no(expr.clone()) |
| /// .transform_data(|expr| { |
| /// // closure returns a result and potentially transforms the node |
| /// // in this example, it does transform the node |
| /// let new_expr = make_new_expr(expr); |
| /// Ok(Transformed::yes(new_expr)) |
| /// }) |
| /// .unwrap(); |
| /// // transformed flag is the union of the original ans closure's transformed flag |
| /// assert!(ret.transformed); |
| /// ``` |
| /// # Example APIs that use `TreeNode` |
| /// - [`TreeNode`], |
| /// - [`TreeNode::rewrite`], |
| /// - [`TreeNode::transform_down`], |
| /// - [`TreeNode::transform_up`], |
| /// - [`TreeNode::transform_down_up`] |
| #[derive(PartialEq, Debug)] |
| pub struct Transformed<T> { |
| pub data: T, |
| pub transformed: bool, |
| pub tnr: TreeNodeRecursion, |
| } |
| |
| impl<T> Transformed<T> { |
| /// Create a new `Transformed` object with the given information. |
| pub fn new(data: T, transformed: bool, tnr: TreeNodeRecursion) -> Self { |
| Self { |
| data, |
| transformed, |
| tnr, |
| } |
| } |
| |
| /// Create a `Transformed` with `transformed` and [`TreeNodeRecursion::Continue`]. |
| pub fn new_transformed(data: T, transformed: bool) -> Self { |
| Self::new(data, transformed, TreeNodeRecursion::Continue) |
| } |
| |
| /// Wrapper for transformed data with [`TreeNodeRecursion::Continue`] statement. |
| pub fn yes(data: T) -> Self { |
| Self::new(data, true, TreeNodeRecursion::Continue) |
| } |
| |
| /// Wrapper for transformed data with [`TreeNodeRecursion::Stop`] statement. |
| pub fn complete(data: T) -> Self { |
| Self::new(data, true, TreeNodeRecursion::Stop) |
| } |
| |
| /// Wrapper for unchanged data with [`TreeNodeRecursion::Continue`] statement. |
| pub fn no(data: T) -> Self { |
| Self::new(data, false, TreeNodeRecursion::Continue) |
| } |
| |
| /// Applies an infallible `f` to the data of this [`Transformed`] object, |
| /// without modifying the `transformed` flag. |
| pub fn update_data<U, F: FnOnce(T) -> U>(self, f: F) -> Transformed<U> { |
| Transformed::new(f(self.data), self.transformed, self.tnr) |
| } |
| |
| /// Applies a fallible `f` (returns `Result`) to the data of this |
| /// [`Transformed`] object, without modifying the `transformed` flag. |
| pub fn map_data<U, F: FnOnce(T) -> Result<U>>(self, f: F) -> Result<Transformed<U>> { |
| f(self.data).map(|data| Transformed::new(data, self.transformed, self.tnr)) |
| } |
| |
| /// Applies a fallible transforming `f` to the data of this [`Transformed`] |
| /// object. |
| /// |
| /// The returned `Transformed` object has the `transformed` flag set if either |
| /// `self` or the return value of `f` have the `transformed` flag set. |
| pub fn transform_data<U, F: FnOnce(T) -> Result<Transformed<U>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<U>> { |
| f(self.data).map(|mut t| { |
| t.transformed |= self.transformed; |
| t |
| }) |
| } |
| |
| /// Maps the [`Transformed`] object to the result of the given `f` depending on the |
| /// current [`TreeNodeRecursion`] value and the fact that `f` is changing the current |
| /// node's children. |
| pub fn transform_children<F: FnOnce(T) -> Result<Transformed<T>>>( |
| mut self, |
| f: F, |
| ) -> Result<Transformed<T>> { |
| match self.tnr { |
| TreeNodeRecursion::Continue => { |
| return f(self.data).map(|mut t| { |
| t.transformed |= self.transformed; |
| t |
| }); |
| } |
| TreeNodeRecursion::Jump => { |
| self.tnr = TreeNodeRecursion::Continue; |
| } |
| TreeNodeRecursion::Stop => {} |
| } |
| Ok(self) |
| } |
| |
| /// Maps the [`Transformed`] object to the result of the given `f` depending on the |
| /// current [`TreeNodeRecursion`] value and the fact that `f` is changing the current |
| /// node's sibling. |
| pub fn transform_sibling<F: FnOnce(T) -> Result<Transformed<T>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<T>> { |
| match self.tnr { |
| TreeNodeRecursion::Continue | TreeNodeRecursion::Jump => { |
| f(self.data).map(|mut t| { |
| t.transformed |= self.transformed; |
| t |
| }) |
| } |
| TreeNodeRecursion::Stop => Ok(self), |
| } |
| } |
| |
| /// Maps the [`Transformed`] object to the result of the given `f` depending on the |
| /// current [`TreeNodeRecursion`] value and the fact that `f` is changing the current |
| /// node's parent. |
| pub fn transform_parent<F: FnOnce(T) -> Result<Transformed<T>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<T>> { |
| match self.tnr { |
| TreeNodeRecursion::Continue => f(self.data).map(|mut t| { |
| t.transformed |= self.transformed; |
| t |
| }), |
| TreeNodeRecursion::Jump | TreeNodeRecursion::Stop => Ok(self), |
| } |
| } |
| } |
| |
| /// [`TreeNodeContainer`] contains elements that a function can be applied on or mapped. |
| /// The elements of the container are siblings so the continuation rules are similar to |
| /// [`TreeNodeRecursion::visit_sibling`] / [`Transformed::transform_sibling`]. |
| pub trait TreeNodeContainer<'a, T: 'a>: Sized { |
| /// Applies `f` to all elements of the container. |
| /// This method is usually called from [`TreeNode::apply_children`] implementations as |
| /// a node is actually a container of the node's children. |
| fn apply_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &'a self, |
| f: F, |
| ) -> Result<TreeNodeRecursion>; |
| |
| /// Maps all elements of the container with `f`. |
| /// This method is usually called from [`TreeNode::map_children`] implementations as |
| /// a node is actually a container of the node's children. |
| fn map_elements<F: FnMut(T) -> Result<Transformed<T>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<Self>>; |
| } |
| |
| impl<'a, T: 'a, C: TreeNodeContainer<'a, T>> TreeNodeContainer<'a, T> for Box<C> { |
| fn apply_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &'a self, |
| f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.as_ref().apply_elements(f) |
| } |
| |
| fn map_elements<F: FnMut(T) -> Result<Transformed<T>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<Self>> { |
| (*self).map_elements(f)?.map_data(|c| Ok(Self::new(c))) |
| } |
| } |
| |
| impl<'a, T: 'a, C: TreeNodeContainer<'a, T> + Clone> TreeNodeContainer<'a, T> for Arc<C> { |
| fn apply_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &'a self, |
| f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.as_ref().apply_elements(f) |
| } |
| |
| fn map_elements<F: FnMut(T) -> Result<Transformed<T>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<Self>> { |
| Arc::unwrap_or_clone(self) |
| .map_elements(f)? |
| .map_data(|c| Ok(Arc::new(c))) |
| } |
| } |
| |
| impl<'a, T: 'a, C: TreeNodeContainer<'a, T>> TreeNodeContainer<'a, T> for Option<C> { |
| fn apply_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &'a self, |
| f: F, |
| ) -> Result<TreeNodeRecursion> { |
| match self { |
| Some(t) => t.apply_elements(f), |
| None => Ok(TreeNodeRecursion::Continue), |
| } |
| } |
| |
| fn map_elements<F: FnMut(T) -> Result<Transformed<T>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<Self>> { |
| self.map_or(Ok(Transformed::no(None)), |c| { |
| c.map_elements(f)?.map_data(|c| Ok(Some(c))) |
| }) |
| } |
| } |
| |
| impl<'a, T: 'a, C: TreeNodeContainer<'a, T>> TreeNodeContainer<'a, T> for Vec<C> { |
| fn apply_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &'a self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| let mut tnr = TreeNodeRecursion::Continue; |
| for c in self { |
| tnr = c.apply_elements(&mut f)?; |
| match tnr { |
| TreeNodeRecursion::Continue | TreeNodeRecursion::Jump => {} |
| TreeNodeRecursion::Stop => return Ok(TreeNodeRecursion::Stop), |
| } |
| } |
| Ok(tnr) |
| } |
| |
| fn map_elements<F: FnMut(T) -> Result<Transformed<T>>>( |
| self, |
| mut f: F, |
| ) -> Result<Transformed<Self>> { |
| let mut tnr = TreeNodeRecursion::Continue; |
| let mut transformed = false; |
| self.into_iter() |
| .map(|c| match tnr { |
| TreeNodeRecursion::Continue | TreeNodeRecursion::Jump => { |
| c.map_elements(&mut f).map(|result| { |
| tnr = result.tnr; |
| transformed |= result.transformed; |
| result.data |
| }) |
| } |
| TreeNodeRecursion::Stop => Ok(c), |
| }) |
| .collect::<Result<Vec<_>>>() |
| .map(|data| Transformed::new(data, transformed, tnr)) |
| } |
| } |
| |
| impl<'a, T: 'a, K: Eq + Hash, C: TreeNodeContainer<'a, T>> TreeNodeContainer<'a, T> |
| for HashMap<K, C> |
| { |
| fn apply_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &'a self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| let mut tnr = TreeNodeRecursion::Continue; |
| for c in self.values() { |
| tnr = c.apply_elements(&mut f)?; |
| match tnr { |
| TreeNodeRecursion::Continue | TreeNodeRecursion::Jump => {} |
| TreeNodeRecursion::Stop => return Ok(TreeNodeRecursion::Stop), |
| } |
| } |
| Ok(tnr) |
| } |
| |
| fn map_elements<F: FnMut(T) -> Result<Transformed<T>>>( |
| self, |
| mut f: F, |
| ) -> Result<Transformed<Self>> { |
| let mut tnr = TreeNodeRecursion::Continue; |
| let mut transformed = false; |
| self.into_iter() |
| .map(|(k, c)| match tnr { |
| TreeNodeRecursion::Continue | TreeNodeRecursion::Jump => { |
| c.map_elements(&mut f).map(|result| { |
| tnr = result.tnr; |
| transformed |= result.transformed; |
| (k, result.data) |
| }) |
| } |
| TreeNodeRecursion::Stop => Ok((k, c)), |
| }) |
| .collect::<Result<HashMap<_, _>>>() |
| .map(|data| Transformed::new(data, transformed, tnr)) |
| } |
| } |
| |
| impl<'a, T: 'a, C0: TreeNodeContainer<'a, T>, C1: TreeNodeContainer<'a, T>> |
| TreeNodeContainer<'a, T> for (C0, C1) |
| { |
| fn apply_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &'a self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.0 |
| .apply_elements(&mut f)? |
| .visit_sibling(|| self.1.apply_elements(&mut f)) |
| } |
| |
| fn map_elements<F: FnMut(T) -> Result<Transformed<T>>>( |
| self, |
| mut f: F, |
| ) -> Result<Transformed<Self>> { |
| self.0 |
| .map_elements(&mut f)? |
| .map_data(|new_c0| Ok((new_c0, self.1)))? |
| .transform_sibling(|(new_c0, c1)| { |
| c1.map_elements(&mut f)? |
| .map_data(|new_c1| Ok((new_c0, new_c1))) |
| }) |
| } |
| } |
| |
| impl< |
| 'a, |
| T: 'a, |
| C0: TreeNodeContainer<'a, T>, |
| C1: TreeNodeContainer<'a, T>, |
| C2: TreeNodeContainer<'a, T>, |
| > TreeNodeContainer<'a, T> for (C0, C1, C2) |
| { |
| fn apply_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &'a self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.0 |
| .apply_elements(&mut f)? |
| .visit_sibling(|| self.1.apply_elements(&mut f))? |
| .visit_sibling(|| self.2.apply_elements(&mut f)) |
| } |
| |
| fn map_elements<F: FnMut(T) -> Result<Transformed<T>>>( |
| self, |
| mut f: F, |
| ) -> Result<Transformed<Self>> { |
| self.0 |
| .map_elements(&mut f)? |
| .map_data(|new_c0| Ok((new_c0, self.1, self.2)))? |
| .transform_sibling(|(new_c0, c1, c2)| { |
| c1.map_elements(&mut f)? |
| .map_data(|new_c1| Ok((new_c0, new_c1, c2))) |
| })? |
| .transform_sibling(|(new_c0, new_c1, c2)| { |
| c2.map_elements(&mut f)? |
| .map_data(|new_c2| Ok((new_c0, new_c1, new_c2))) |
| }) |
| } |
| } |
| |
| impl< |
| 'a, |
| T: 'a, |
| C0: TreeNodeContainer<'a, T>, |
| C1: TreeNodeContainer<'a, T>, |
| C2: TreeNodeContainer<'a, T>, |
| C3: TreeNodeContainer<'a, T>, |
| > TreeNodeContainer<'a, T> for (C0, C1, C2, C3) |
| { |
| fn apply_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &'a self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.0 |
| .apply_elements(&mut f)? |
| .visit_sibling(|| self.1.apply_elements(&mut f))? |
| .visit_sibling(|| self.2.apply_elements(&mut f))? |
| .visit_sibling(|| self.3.apply_elements(&mut f)) |
| } |
| |
| fn map_elements<F: FnMut(T) -> Result<Transformed<T>>>( |
| self, |
| mut f: F, |
| ) -> Result<Transformed<Self>> { |
| self.0 |
| .map_elements(&mut f)? |
| .map_data(|new_c0| Ok((new_c0, self.1, self.2, self.3)))? |
| .transform_sibling(|(new_c0, c1, c2, c3)| { |
| c1.map_elements(&mut f)? |
| .map_data(|new_c1| Ok((new_c0, new_c1, c2, c3))) |
| })? |
| .transform_sibling(|(new_c0, new_c1, c2, c3)| { |
| c2.map_elements(&mut f)? |
| .map_data(|new_c2| Ok((new_c0, new_c1, new_c2, c3))) |
| })? |
| .transform_sibling(|(new_c0, new_c1, new_c2, c3)| { |
| c3.map_elements(&mut f)? |
| .map_data(|new_c3| Ok((new_c0, new_c1, new_c2, new_c3))) |
| }) |
| } |
| } |
| |
| /// [`TreeNodeRefContainer`] contains references to elements that a function can be |
| /// applied on. The elements of the container are siblings so the continuation rules are |
| /// similar to [`TreeNodeRecursion::visit_sibling`]. |
| /// |
| /// This container is similar to [`TreeNodeContainer`], but the lifetime of the reference |
| /// elements (`T`) are not derived from the container's lifetime. |
| /// A typical usage of this container is in `Expr::apply_children` when we need to |
| /// construct a temporary container to be able to call `apply_ref_elements` on a |
| /// collection of tree node references. But in that case the container's temporary |
| /// lifetime is different to the lifetime of tree nodes that we put into it. |
| /// Please find an example use case in `Expr::apply_children` with the `Expr::Case` case. |
| /// |
| /// Most of the cases we don't need to create a temporary container with |
| /// `TreeNodeRefContainer`, but we can just call `TreeNodeContainer::apply_elements`. |
| /// Please find an example use case in `Expr::apply_children` with the `Expr::GroupingSet` |
| /// case. |
| pub trait TreeNodeRefContainer<'a, T: 'a>: Sized { |
| /// Applies `f` to all elements of the container. |
| /// This method is usually called from [`TreeNode::apply_children`] implementations as |
| /// a node is actually a container of the node's children. |
| fn apply_ref_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &self, |
| f: F, |
| ) -> Result<TreeNodeRecursion>; |
| } |
| |
| impl<'a, T: 'a, C: TreeNodeContainer<'a, T>> TreeNodeRefContainer<'a, T> for Vec<&'a C> { |
| fn apply_ref_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| let mut tnr = TreeNodeRecursion::Continue; |
| for c in self { |
| tnr = c.apply_elements(&mut f)?; |
| match tnr { |
| TreeNodeRecursion::Continue | TreeNodeRecursion::Jump => {} |
| TreeNodeRecursion::Stop => return Ok(TreeNodeRecursion::Stop), |
| } |
| } |
| Ok(tnr) |
| } |
| } |
| |
| impl<'a, T: 'a, C0: TreeNodeContainer<'a, T>, C1: TreeNodeContainer<'a, T>> |
| TreeNodeRefContainer<'a, T> for (&'a C0, &'a C1) |
| { |
| fn apply_ref_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.0 |
| .apply_elements(&mut f)? |
| .visit_sibling(|| self.1.apply_elements(&mut f)) |
| } |
| } |
| |
| impl< |
| 'a, |
| T: 'a, |
| C0: TreeNodeContainer<'a, T>, |
| C1: TreeNodeContainer<'a, T>, |
| C2: TreeNodeContainer<'a, T>, |
| > TreeNodeRefContainer<'a, T> for (&'a C0, &'a C1, &'a C2) |
| { |
| fn apply_ref_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.0 |
| .apply_elements(&mut f)? |
| .visit_sibling(|| self.1.apply_elements(&mut f))? |
| .visit_sibling(|| self.2.apply_elements(&mut f)) |
| } |
| } |
| |
| impl< |
| 'a, |
| T: 'a, |
| C0: TreeNodeContainer<'a, T>, |
| C1: TreeNodeContainer<'a, T>, |
| C2: TreeNodeContainer<'a, T>, |
| C3: TreeNodeContainer<'a, T>, |
| > TreeNodeRefContainer<'a, T> for (&'a C0, &'a C1, &'a C2, &'a C3) |
| { |
| fn apply_ref_elements<F: FnMut(&'a T) -> Result<TreeNodeRecursion>>( |
| &self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.0 |
| .apply_elements(&mut f)? |
| .visit_sibling(|| self.1.apply_elements(&mut f))? |
| .visit_sibling(|| self.2.apply_elements(&mut f))? |
| .visit_sibling(|| self.3.apply_elements(&mut f)) |
| } |
| } |
| |
| /// Transformation helper to process a sequence of iterable tree nodes that are siblings. |
| pub trait TreeNodeIterator: Iterator { |
| /// Apples `f` to each item in this iterator |
| /// |
| /// Visits all items in the iterator unless |
| /// `f` returns an error or `f` returns `TreeNodeRecursion::Stop`. |
| /// |
| /// # Returns |
| /// Error if `f` returns an error or `Ok(TreeNodeRecursion)` from the last invocation |
| /// of `f` or `Continue` if the iterator is empty |
| fn apply_until_stop<F: FnMut(Self::Item) -> Result<TreeNodeRecursion>>( |
| self, |
| f: F, |
| ) -> Result<TreeNodeRecursion>; |
| |
| /// Apples `f` to each item in this iterator |
| /// |
| /// Visits all items in the iterator unless |
| /// `f` returns an error or `f` returns `TreeNodeRecursion::Stop`. |
| /// |
| /// # Returns |
| /// Error if `f` returns an error |
| /// |
| /// Ok(Transformed) such that: |
| /// 1. `transformed` is true if any return from `f` had transformed true |
| /// 2. `data` from the last invocation of `f` |
| /// 3. `tnr` from the last invocation of `f` or `Continue` if the iterator is empty |
| fn map_until_stop_and_collect< |
| F: FnMut(Self::Item) -> Result<Transformed<Self::Item>>, |
| >( |
| self, |
| f: F, |
| ) -> Result<Transformed<Vec<Self::Item>>>; |
| } |
| |
| impl<I: Iterator> TreeNodeIterator for I { |
| fn apply_until_stop<F: FnMut(Self::Item) -> Result<TreeNodeRecursion>>( |
| self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| let mut tnr = TreeNodeRecursion::Continue; |
| for i in self { |
| tnr = f(i)?; |
| match tnr { |
| TreeNodeRecursion::Continue | TreeNodeRecursion::Jump => {} |
| TreeNodeRecursion::Stop => return Ok(TreeNodeRecursion::Stop), |
| } |
| } |
| Ok(tnr) |
| } |
| |
| fn map_until_stop_and_collect< |
| F: FnMut(Self::Item) -> Result<Transformed<Self::Item>>, |
| >( |
| self, |
| mut f: F, |
| ) -> Result<Transformed<Vec<Self::Item>>> { |
| let mut tnr = TreeNodeRecursion::Continue; |
| let mut transformed = false; |
| self.map(|item| match tnr { |
| TreeNodeRecursion::Continue | TreeNodeRecursion::Jump => { |
| f(item).map(|result| { |
| tnr = result.tnr; |
| transformed |= result.transformed; |
| result.data |
| }) |
| } |
| TreeNodeRecursion::Stop => Ok(item), |
| }) |
| .collect::<Result<Vec<_>>>() |
| .map(|data| Transformed::new(data, transformed, tnr)) |
| } |
| } |
| |
| /// Transformation helper to access [`Transformed`] fields in a [`Result`] easily. |
| /// |
| /// # Example |
| /// Access the internal data of a `Result<Transformed<T>>` |
| /// as a `Result<T>` using the `data` method: |
| /// ``` |
| /// # use datafusion_common::Result; |
| /// # use datafusion_common::tree_node::{Transformed, TransformedResult}; |
| /// # // note use i64 instead of Expr as Expr is not in datafusion-common |
| /// # fn update_expr() -> i64 { 1 } |
| /// # fn main() -> Result<()> { |
| /// let transformed: Result<Transformed<_>> = Ok(Transformed::yes(update_expr())); |
| /// // access the internal data of the transformed result, or return the error |
| /// let transformed_expr = transformed.data()?; |
| /// # Ok(()) |
| /// # } |
| /// ``` |
| pub trait TransformedResult<T> { |
| fn data(self) -> Result<T>; |
| |
| fn transformed(self) -> Result<bool>; |
| |
| fn tnr(self) -> Result<TreeNodeRecursion>; |
| } |
| |
| impl<T> TransformedResult<T> for Result<Transformed<T>> { |
| fn data(self) -> Result<T> { |
| self.map(|t| t.data) |
| } |
| |
| fn transformed(self) -> Result<bool> { |
| self.map(|t| t.transformed) |
| } |
| |
| fn tnr(self) -> Result<TreeNodeRecursion> { |
| self.map(|t| t.tnr) |
| } |
| } |
| |
| /// Helper trait for implementing [`TreeNode`] that have children stored as |
| /// `Arc`s. If some trait object, such as `dyn T`, implements this trait, |
| /// its related `Arc<dyn T>` will automatically implement [`TreeNode`]. |
| pub trait DynTreeNode { |
| /// Returns all children of the specified `TreeNode`. |
| fn arc_children(&self) -> Vec<&Arc<Self>>; |
| |
| /// Constructs a new node with the specified children. |
| fn with_new_arc_children( |
| &self, |
| arc_self: Arc<Self>, |
| new_children: Vec<Arc<Self>>, |
| ) -> Result<Arc<Self>>; |
| } |
| |
| /// Blanket implementation for any `Arc<T>` where `T` implements [`DynTreeNode`] |
| /// (such as [`Arc<dyn PhysicalExpr>`]). |
| impl<T: DynTreeNode + ?Sized> TreeNode for Arc<T> { |
| fn apply_children<'n, F: FnMut(&'n Self) -> Result<TreeNodeRecursion>>( |
| &'n self, |
| f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.arc_children().into_iter().apply_until_stop(f) |
| } |
| |
| fn map_children<F: FnMut(Self) -> Result<Transformed<Self>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<Self>> { |
| let children = self.arc_children(); |
| if !children.is_empty() { |
| let new_children = children |
| .into_iter() |
| .cloned() |
| .map_until_stop_and_collect(f)?; |
| // Propagate up `new_children.transformed` and `new_children.tnr` |
| // along with the node containing transformed children. |
| if new_children.transformed { |
| let arc_self = Arc::clone(&self); |
| new_children.map_data(|new_children| { |
| self.with_new_arc_children(arc_self, new_children) |
| }) |
| } else { |
| Ok(Transformed::new(self, false, new_children.tnr)) |
| } |
| } else { |
| Ok(Transformed::no(self)) |
| } |
| } |
| } |
| |
| /// Instead of implementing [`TreeNode`], it's recommended to implement a [`ConcreteTreeNode`] for |
| /// trees that contain nodes with payloads. This approach ensures safe execution of algorithms |
| /// involving payloads, by enforcing rules for detaching and reattaching child nodes. |
| pub trait ConcreteTreeNode: Sized { |
| /// Provides read-only access to child nodes. |
| fn children(&self) -> &[Self]; |
| |
| /// Detaches the node from its children, returning the node itself and its detached children. |
| fn take_children(self) -> (Self, Vec<Self>); |
| |
| /// Reattaches updated child nodes to the node, returning the updated node. |
| fn with_new_children(self, children: Vec<Self>) -> Result<Self>; |
| } |
| |
| impl<T: ConcreteTreeNode> TreeNode for T { |
| fn apply_children<'n, F: FnMut(&'n Self) -> Result<TreeNodeRecursion>>( |
| &'n self, |
| f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.children().iter().apply_until_stop(f) |
| } |
| |
| fn map_children<F: FnMut(Self) -> Result<Transformed<Self>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<Self>> { |
| let (new_self, children) = self.take_children(); |
| if !children.is_empty() { |
| let new_children = children.into_iter().map_until_stop_and_collect(f)?; |
| // Propagate up `new_children.transformed` and `new_children.tnr` along with |
| // the node containing transformed children. |
| new_children.map_data(|new_children| new_self.with_new_children(new_children)) |
| } else { |
| Ok(Transformed::no(new_self)) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| pub(crate) mod tests { |
| use std::collections::HashMap; |
| use std::fmt::Display; |
| |
| use crate::tree_node::{ |
| Transformed, TreeNode, TreeNodeContainer, TreeNodeRecursion, TreeNodeRewriter, |
| TreeNodeVisitor, |
| }; |
| use crate::Result; |
| |
| #[derive(Debug, Eq, Hash, PartialEq, Clone)] |
| pub struct TestTreeNode<T> { |
| pub(crate) children: Vec<TestTreeNode<T>>, |
| pub(crate) data: T, |
| } |
| |
| impl<T> TestTreeNode<T> { |
| pub(crate) fn new(children: Vec<TestTreeNode<T>>, data: T) -> Self { |
| Self { children, data } |
| } |
| |
| pub(crate) fn new_leaf(data: T) -> Self { |
| Self { |
| children: vec![], |
| data, |
| } |
| } |
| |
| pub(crate) fn is_leaf(&self) -> bool { |
| self.children.is_empty() |
| } |
| } |
| |
| impl<T> TreeNode for TestTreeNode<T> { |
| fn apply_children<'n, F: FnMut(&'n Self) -> Result<TreeNodeRecursion>>( |
| &'n self, |
| f: F, |
| ) -> Result<TreeNodeRecursion> { |
| self.children.apply_elements(f) |
| } |
| |
| fn map_children<F: FnMut(Self) -> Result<Transformed<Self>>>( |
| self, |
| f: F, |
| ) -> Result<Transformed<Self>> { |
| Ok(self |
| .children |
| .map_elements(f)? |
| .update_data(|new_children| Self { |
| children: new_children, |
| ..self |
| })) |
| } |
| } |
| |
| impl<'a, T: 'a> TreeNodeContainer<'a, Self> for TestTreeNode<T> { |
| fn apply_elements<F: FnMut(&'a Self) -> Result<TreeNodeRecursion>>( |
| &'a self, |
| mut f: F, |
| ) -> Result<TreeNodeRecursion> { |
| f(self) |
| } |
| |
| fn map_elements<F: FnMut(Self) -> Result<Transformed<Self>>>( |
| self, |
| mut f: F, |
| ) -> Result<Transformed<Self>> { |
| f(self) |
| } |
| } |
| |
| // J |
| // | |
| // I |
| // | |
| // F |
| // / \ |
| // E G |
| // | | |
| // C H |
| // / \ |
| // B D |
| // | |
| // A |
| fn test_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("a".to_string()); |
| let node_b = TestTreeNode::new_leaf("b".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "d".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "e".to_string()); |
| let node_h = TestTreeNode::new_leaf("h".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "g".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "i".to_string()); |
| TestTreeNode::new(vec![node_i], "j".to_string()) |
| } |
| |
| // Continue on all nodes |
| // Expected visits in a combined traversal |
| fn all_visits() -> Vec<String> { |
| vec![ |
| "f_down(j)", |
| "f_down(i)", |
| "f_down(f)", |
| "f_down(e)", |
| "f_down(c)", |
| "f_down(b)", |
| "f_up(b)", |
| "f_down(d)", |
| "f_down(a)", |
| "f_up(a)", |
| "f_up(d)", |
| "f_up(c)", |
| "f_up(e)", |
| "f_down(g)", |
| "f_down(h)", |
| "f_up(h)", |
| "f_up(g)", |
| "f_up(f)", |
| "f_up(i)", |
| "f_up(j)", |
| ] |
| .into_iter() |
| .map(|s| s.to_string()) |
| .collect() |
| } |
| |
| // Expected transformed tree after a combined traversal |
| fn transformed_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_up(f_down(a))".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_up(f_down(b))".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "f_up(f_down(d))".to_string()); |
| let node_c = |
| TestTreeNode::new(vec![node_b, node_d], "f_up(f_down(c))".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_up(f_down(e))".to_string()); |
| let node_h = TestTreeNode::new_leaf("f_up(f_down(h))".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "f_up(f_down(g))".to_string()); |
| let node_f = |
| TestTreeNode::new(vec![node_e, node_g], "f_up(f_down(f))".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_up(f_down(i))".to_string()); |
| TestTreeNode::new(vec![node_i], "f_up(f_down(j))".to_string()) |
| } |
| |
| // Expected transformed tree after a top-down traversal |
| fn transformed_down_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_down(a)".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_down(b)".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("f_down(h)".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "f_down(g)".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_down(j)".to_string()) |
| } |
| |
| // Expected transformed tree after a bottom-up traversal |
| fn transformed_up_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_up(a)".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_up(b)".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "f_up(d)".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "f_up(c)".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_up(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("f_up(h)".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "f_up(g)".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_up(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_up(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_up(j)".to_string()) |
| } |
| |
| // f_down Jump on A node |
| fn f_down_jump_on_a_visits() -> Vec<String> { |
| vec![ |
| "f_down(j)", |
| "f_down(i)", |
| "f_down(f)", |
| "f_down(e)", |
| "f_down(c)", |
| "f_down(b)", |
| "f_up(b)", |
| "f_down(d)", |
| "f_down(a)", |
| "f_up(a)", |
| "f_up(d)", |
| "f_up(c)", |
| "f_up(e)", |
| "f_down(g)", |
| "f_down(h)", |
| "f_up(h)", |
| "f_up(g)", |
| "f_up(f)", |
| "f_up(i)", |
| "f_up(j)", |
| ] |
| .into_iter() |
| .map(|s| s.to_string()) |
| .collect() |
| } |
| |
| fn f_down_jump_on_a_transformed_down_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_down(a)".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_down(b)".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("f_down(h)".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "f_down(g)".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_down(j)".to_string()) |
| } |
| |
| // f_down Jump on E node |
| fn f_down_jump_on_e_visits() -> Vec<String> { |
| vec![ |
| "f_down(j)", |
| "f_down(i)", |
| "f_down(f)", |
| "f_down(e)", |
| "f_up(e)", |
| "f_down(g)", |
| "f_down(h)", |
| "f_up(h)", |
| "f_up(g)", |
| "f_up(f)", |
| "f_up(i)", |
| "f_up(j)", |
| ] |
| .into_iter() |
| .map(|s| s.to_string()) |
| .collect() |
| } |
| |
| fn f_down_jump_on_e_transformed_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("a".to_string()); |
| let node_b = TestTreeNode::new_leaf("b".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "d".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_up(f_down(e))".to_string()); |
| let node_h = TestTreeNode::new_leaf("f_up(f_down(h))".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "f_up(f_down(g))".to_string()); |
| let node_f = |
| TestTreeNode::new(vec![node_e, node_g], "f_up(f_down(f))".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_up(f_down(i))".to_string()); |
| TestTreeNode::new(vec![node_i], "f_up(f_down(j))".to_string()) |
| } |
| |
| fn f_down_jump_on_e_transformed_down_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("a".to_string()); |
| let node_b = TestTreeNode::new_leaf("b".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "d".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("f_down(h)".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "f_down(g)".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_down(j)".to_string()) |
| } |
| |
| // f_up Jump on A node |
| fn f_up_jump_on_a_visits() -> Vec<String> { |
| vec![ |
| "f_down(j)", |
| "f_down(i)", |
| "f_down(f)", |
| "f_down(e)", |
| "f_down(c)", |
| "f_down(b)", |
| "f_up(b)", |
| "f_down(d)", |
| "f_down(a)", |
| "f_up(a)", |
| "f_down(g)", |
| "f_down(h)", |
| "f_up(h)", |
| "f_up(g)", |
| "f_up(f)", |
| "f_up(i)", |
| "f_up(j)", |
| ] |
| .into_iter() |
| .map(|s| s.to_string()) |
| .collect() |
| } |
| |
| fn f_up_jump_on_a_transformed_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_up(f_down(a))".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_up(f_down(b))".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("f_up(f_down(h))".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "f_up(f_down(g))".to_string()); |
| let node_f = |
| TestTreeNode::new(vec![node_e, node_g], "f_up(f_down(f))".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_up(f_down(i))".to_string()); |
| TestTreeNode::new(vec![node_i], "f_up(f_down(j))".to_string()) |
| } |
| |
| fn f_up_jump_on_a_transformed_up_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_up(a)".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_up(b)".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "d".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "e".to_string()); |
| let node_h = TestTreeNode::new_leaf("f_up(h)".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "f_up(g)".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_up(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_up(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_up(j)".to_string()) |
| } |
| |
| // f_up Jump on E node |
| fn f_up_jump_on_e_visits() -> Vec<String> { |
| vec![ |
| "f_down(j)", |
| "f_down(i)", |
| "f_down(f)", |
| "f_down(e)", |
| "f_down(c)", |
| "f_down(b)", |
| "f_up(b)", |
| "f_down(d)", |
| "f_down(a)", |
| "f_up(a)", |
| "f_up(d)", |
| "f_up(c)", |
| "f_up(e)", |
| "f_down(g)", |
| "f_down(h)", |
| "f_up(h)", |
| "f_up(g)", |
| "f_up(f)", |
| "f_up(i)", |
| "f_up(j)", |
| ] |
| .into_iter() |
| .map(|s| s.to_string()) |
| .collect() |
| } |
| |
| fn f_up_jump_on_e_transformed_tree() -> TestTreeNode<String> { |
| transformed_tree() |
| } |
| |
| fn f_up_jump_on_e_transformed_up_tree() -> TestTreeNode<String> { |
| transformed_up_tree() |
| } |
| |
| // f_down Stop on A node |
| |
| fn f_down_stop_on_a_visits() -> Vec<String> { |
| vec![ |
| "f_down(j)", |
| "f_down(i)", |
| "f_down(f)", |
| "f_down(e)", |
| "f_down(c)", |
| "f_down(b)", |
| "f_up(b)", |
| "f_down(d)", |
| "f_down(a)", |
| ] |
| .into_iter() |
| .map(|s| s.to_string()) |
| .collect() |
| } |
| |
| fn f_down_stop_on_a_transformed_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_down(a)".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_up(f_down(b))".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("h".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "g".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_down(j)".to_string()) |
| } |
| |
| fn f_down_stop_on_a_transformed_down_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_down(a)".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_down(b)".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("h".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "g".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_down(j)".to_string()) |
| } |
| |
| // f_down Stop on E node |
| fn f_down_stop_on_e_visits() -> Vec<String> { |
| vec!["f_down(j)", "f_down(i)", "f_down(f)", "f_down(e)"] |
| .into_iter() |
| .map(|s| s.to_string()) |
| .collect() |
| } |
| |
| fn f_down_stop_on_e_transformed_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("a".to_string()); |
| let node_b = TestTreeNode::new_leaf("b".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "d".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("h".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "g".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_down(j)".to_string()) |
| } |
| |
| fn f_down_stop_on_e_transformed_down_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("a".to_string()); |
| let node_b = TestTreeNode::new_leaf("b".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "d".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("h".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "g".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_down(j)".to_string()) |
| } |
| |
| // f_up Stop on A node |
| fn f_up_stop_on_a_visits() -> Vec<String> { |
| vec![ |
| "f_down(j)", |
| "f_down(i)", |
| "f_down(f)", |
| "f_down(e)", |
| "f_down(c)", |
| "f_down(b)", |
| "f_up(b)", |
| "f_down(d)", |
| "f_down(a)", |
| "f_up(a)", |
| ] |
| .into_iter() |
| .map(|s| s.to_string()) |
| .collect() |
| } |
| |
| fn f_up_stop_on_a_transformed_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_up(f_down(a))".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_up(f_down(b))".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "f_down(d)".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "f_down(c)".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_down(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("h".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "g".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_down(j)".to_string()) |
| } |
| |
| fn f_up_stop_on_a_transformed_up_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_up(a)".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_up(b)".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "d".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "e".to_string()); |
| let node_h = TestTreeNode::new_leaf("h".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "g".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "i".to_string()); |
| TestTreeNode::new(vec![node_i], "j".to_string()) |
| } |
| |
| // f_up Stop on E node |
| fn f_up_stop_on_e_visits() -> Vec<String> { |
| vec![ |
| "f_down(j)", |
| "f_down(i)", |
| "f_down(f)", |
| "f_down(e)", |
| "f_down(c)", |
| "f_down(b)", |
| "f_up(b)", |
| "f_down(d)", |
| "f_down(a)", |
| "f_up(a)", |
| "f_up(d)", |
| "f_up(c)", |
| "f_up(e)", |
| ] |
| .into_iter() |
| .map(|s| s.to_string()) |
| .collect() |
| } |
| |
| fn f_up_stop_on_e_transformed_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_up(f_down(a))".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_up(f_down(b))".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "f_up(f_down(d))".to_string()); |
| let node_c = |
| TestTreeNode::new(vec![node_b, node_d], "f_up(f_down(c))".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_up(f_down(e))".to_string()); |
| let node_h = TestTreeNode::new_leaf("h".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "g".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f_down(f)".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "f_down(i)".to_string()); |
| TestTreeNode::new(vec![node_i], "f_down(j)".to_string()) |
| } |
| |
| fn f_up_stop_on_e_transformed_up_tree() -> TestTreeNode<String> { |
| let node_a = TestTreeNode::new_leaf("f_up(a)".to_string()); |
| let node_b = TestTreeNode::new_leaf("f_up(b)".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "f_up(d)".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "f_up(c)".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "f_up(e)".to_string()); |
| let node_h = TestTreeNode::new_leaf("h".to_string()); |
| let node_g = TestTreeNode::new(vec![node_h], "g".to_string()); |
| let node_f = TestTreeNode::new(vec![node_e, node_g], "f".to_string()); |
| let node_i = TestTreeNode::new(vec![node_f], "i".to_string()); |
| TestTreeNode::new(vec![node_i], "j".to_string()) |
| } |
| |
| fn down_visits(visits: Vec<String>) -> Vec<String> { |
| visits |
| .into_iter() |
| .filter(|v| v.starts_with("f_down")) |
| .collect() |
| } |
| |
| type TestVisitorF<T> = Box<dyn FnMut(&TestTreeNode<T>) -> Result<TreeNodeRecursion>>; |
| |
| struct TestVisitor<T> { |
| visits: Vec<String>, |
| f_down: TestVisitorF<T>, |
| f_up: TestVisitorF<T>, |
| } |
| |
| impl<T> TestVisitor<T> { |
| fn new(f_down: TestVisitorF<T>, f_up: TestVisitorF<T>) -> Self { |
| Self { |
| visits: vec![], |
| f_down, |
| f_up, |
| } |
| } |
| } |
| |
| impl<'n, T: Display> TreeNodeVisitor<'n> for TestVisitor<T> { |
| type Node = TestTreeNode<T>; |
| |
| fn f_down(&mut self, node: &'n Self::Node) -> Result<TreeNodeRecursion> { |
| self.visits.push(format!("f_down({})", node.data)); |
| (*self.f_down)(node) |
| } |
| |
| fn f_up(&mut self, node: &'n Self::Node) -> Result<TreeNodeRecursion> { |
| self.visits.push(format!("f_up({})", node.data)); |
| (*self.f_up)(node) |
| } |
| } |
| |
| fn visit_continue<T>(_: &TestTreeNode<T>) -> Result<TreeNodeRecursion> { |
| Ok(TreeNodeRecursion::Continue) |
| } |
| |
| fn visit_event_on<T: PartialEq, D: Into<T>>( |
| data: D, |
| event: TreeNodeRecursion, |
| ) -> impl FnMut(&TestTreeNode<T>) -> Result<TreeNodeRecursion> { |
| let d = data.into(); |
| move |node| { |
| Ok(if node.data == d { |
| event |
| } else { |
| TreeNodeRecursion::Continue |
| }) |
| } |
| } |
| |
| macro_rules! visit_test { |
| ($NAME:ident, $F_DOWN:expr, $F_UP:expr, $EXPECTED_VISITS:expr) => { |
| #[test] |
| fn $NAME() -> Result<()> { |
| let tree = test_tree(); |
| let mut visitor = TestVisitor::new(Box::new($F_DOWN), Box::new($F_UP)); |
| tree.visit(&mut visitor)?; |
| assert_eq!(visitor.visits, $EXPECTED_VISITS); |
| |
| Ok(()) |
| } |
| }; |
| } |
| |
| macro_rules! test_apply { |
| ($NAME:ident, $F:expr, $EXPECTED_VISITS:expr) => { |
| #[test] |
| fn $NAME() -> Result<()> { |
| let tree = test_tree(); |
| let mut visits = vec![]; |
| tree.apply(|node| { |
| visits.push(format!("f_down({})", node.data)); |
| $F(node) |
| })?; |
| assert_eq!(visits, $EXPECTED_VISITS); |
| |
| Ok(()) |
| } |
| }; |
| } |
| |
| type TestRewriterF<T> = |
| Box<dyn FnMut(TestTreeNode<T>) -> Result<Transformed<TestTreeNode<T>>>>; |
| |
| struct TestRewriter<T> { |
| f_down: TestRewriterF<T>, |
| f_up: TestRewriterF<T>, |
| } |
| |
| impl<T> TestRewriter<T> { |
| fn new(f_down: TestRewriterF<T>, f_up: TestRewriterF<T>) -> Self { |
| Self { f_down, f_up } |
| } |
| } |
| |
| impl<T: Display> TreeNodeRewriter for TestRewriter<T> { |
| type Node = TestTreeNode<T>; |
| |
| fn f_down(&mut self, node: Self::Node) -> Result<Transformed<Self::Node>> { |
| (*self.f_down)(node) |
| } |
| |
| fn f_up(&mut self, node: Self::Node) -> Result<Transformed<Self::Node>> { |
| (*self.f_up)(node) |
| } |
| } |
| |
| fn transform_yes<N: Display, T: Display + From<String>>( |
| transformation_name: N, |
| ) -> impl FnMut(TestTreeNode<T>) -> Result<Transformed<TestTreeNode<T>>> { |
| move |node| { |
| Ok(Transformed::yes(TestTreeNode::new( |
| node.children, |
| format!("{}({})", transformation_name, node.data).into(), |
| ))) |
| } |
| } |
| |
| fn transform_and_event_on< |
| N: Display, |
| T: PartialEq + Display + From<String>, |
| D: Into<T>, |
| >( |
| transformation_name: N, |
| data: D, |
| event: TreeNodeRecursion, |
| ) -> impl FnMut(TestTreeNode<T>) -> Result<Transformed<TestTreeNode<T>>> { |
| let d = data.into(); |
| move |node| { |
| let new_node = TestTreeNode::new( |
| node.children, |
| format!("{}({})", transformation_name, node.data).into(), |
| ); |
| Ok(if node.data == d { |
| Transformed::new(new_node, true, event) |
| } else { |
| Transformed::yes(new_node) |
| }) |
| } |
| } |
| |
| macro_rules! rewrite_test { |
| ($NAME:ident, $F_DOWN:expr, $F_UP:expr, $EXPECTED_TREE:expr) => { |
| #[test] |
| fn $NAME() -> Result<()> { |
| let tree = test_tree(); |
| let mut rewriter = TestRewriter::new(Box::new($F_DOWN), Box::new($F_UP)); |
| assert_eq!(tree.rewrite(&mut rewriter)?, $EXPECTED_TREE); |
| |
| Ok(()) |
| } |
| }; |
| } |
| |
| macro_rules! transform_test { |
| ($NAME:ident, $F_DOWN:expr, $F_UP:expr, $EXPECTED_TREE:expr) => { |
| #[test] |
| fn $NAME() -> Result<()> { |
| let tree = test_tree(); |
| assert_eq!(tree.transform_down_up($F_DOWN, $F_UP,)?, $EXPECTED_TREE); |
| |
| Ok(()) |
| } |
| }; |
| } |
| |
| macro_rules! transform_down_test { |
| ($NAME:ident, $F:expr, $EXPECTED_TREE:expr) => { |
| #[test] |
| fn $NAME() -> Result<()> { |
| let tree = test_tree(); |
| assert_eq!(tree.transform_down($F)?, $EXPECTED_TREE); |
| |
| Ok(()) |
| } |
| }; |
| } |
| |
| macro_rules! transform_up_test { |
| ($NAME:ident, $F:expr, $EXPECTED_TREE:expr) => { |
| #[test] |
| fn $NAME() -> Result<()> { |
| let tree = test_tree(); |
| assert_eq!(tree.transform_up($F)?, $EXPECTED_TREE); |
| |
| Ok(()) |
| } |
| }; |
| } |
| |
| visit_test!(test_visit, visit_continue, visit_continue, all_visits()); |
| visit_test!( |
| test_visit_f_down_jump_on_a, |
| visit_event_on("a", TreeNodeRecursion::Jump), |
| visit_continue, |
| f_down_jump_on_a_visits() |
| ); |
| visit_test!( |
| test_visit_f_down_jump_on_e, |
| visit_event_on("e", TreeNodeRecursion::Jump), |
| visit_continue, |
| f_down_jump_on_e_visits() |
| ); |
| visit_test!( |
| test_visit_f_up_jump_on_a, |
| visit_continue, |
| visit_event_on("a", TreeNodeRecursion::Jump), |
| f_up_jump_on_a_visits() |
| ); |
| visit_test!( |
| test_visit_f_up_jump_on_e, |
| visit_continue, |
| visit_event_on("e", TreeNodeRecursion::Jump), |
| f_up_jump_on_e_visits() |
| ); |
| visit_test!( |
| test_visit_f_down_stop_on_a, |
| visit_event_on("a", TreeNodeRecursion::Stop), |
| visit_continue, |
| f_down_stop_on_a_visits() |
| ); |
| visit_test!( |
| test_visit_f_down_stop_on_e, |
| visit_event_on("e", TreeNodeRecursion::Stop), |
| visit_continue, |
| f_down_stop_on_e_visits() |
| ); |
| visit_test!( |
| test_visit_f_up_stop_on_a, |
| visit_continue, |
| visit_event_on("a", TreeNodeRecursion::Stop), |
| f_up_stop_on_a_visits() |
| ); |
| visit_test!( |
| test_visit_f_up_stop_on_e, |
| visit_continue, |
| visit_event_on("e", TreeNodeRecursion::Stop), |
| f_up_stop_on_e_visits() |
| ); |
| |
| test_apply!(test_apply, visit_continue, down_visits(all_visits())); |
| test_apply!( |
| test_apply_f_down_jump_on_a, |
| visit_event_on("a", TreeNodeRecursion::Jump), |
| down_visits(f_down_jump_on_a_visits()) |
| ); |
| test_apply!( |
| test_apply_f_down_jump_on_e, |
| visit_event_on("e", TreeNodeRecursion::Jump), |
| down_visits(f_down_jump_on_e_visits()) |
| ); |
| test_apply!( |
| test_apply_f_down_stop_on_a, |
| visit_event_on("a", TreeNodeRecursion::Stop), |
| down_visits(f_down_stop_on_a_visits()) |
| ); |
| test_apply!( |
| test_apply_f_down_stop_on_e, |
| visit_event_on("e", TreeNodeRecursion::Stop), |
| down_visits(f_down_stop_on_e_visits()) |
| ); |
| |
| rewrite_test!( |
| test_rewrite, |
| transform_yes("f_down"), |
| transform_yes("f_up"), |
| Transformed::yes(transformed_tree()) |
| ); |
| rewrite_test!( |
| test_rewrite_f_down_jump_on_a, |
| transform_and_event_on("f_down", "a", TreeNodeRecursion::Jump), |
| transform_yes("f_up"), |
| Transformed::yes(transformed_tree()) |
| ); |
| rewrite_test!( |
| test_rewrite_f_down_jump_on_e, |
| transform_and_event_on("f_down", "e", TreeNodeRecursion::Jump), |
| transform_yes("f_up"), |
| Transformed::yes(f_down_jump_on_e_transformed_tree()) |
| ); |
| rewrite_test!( |
| test_rewrite_f_up_jump_on_a, |
| transform_yes("f_down"), |
| transform_and_event_on("f_up", "f_down(a)", TreeNodeRecursion::Jump), |
| Transformed::yes(f_up_jump_on_a_transformed_tree()) |
| ); |
| rewrite_test!( |
| test_rewrite_f_up_jump_on_e, |
| transform_yes("f_down"), |
| transform_and_event_on("f_up", "f_down(e)", TreeNodeRecursion::Jump), |
| Transformed::yes(f_up_jump_on_e_transformed_tree()) |
| ); |
| rewrite_test!( |
| test_rewrite_f_down_stop_on_a, |
| transform_and_event_on("f_down", "a", TreeNodeRecursion::Stop), |
| transform_yes("f_up"), |
| Transformed::new( |
| f_down_stop_on_a_transformed_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| rewrite_test!( |
| test_rewrite_f_down_stop_on_e, |
| transform_and_event_on("f_down", "e", TreeNodeRecursion::Stop), |
| transform_yes("f_up"), |
| Transformed::new( |
| f_down_stop_on_e_transformed_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| rewrite_test!( |
| test_rewrite_f_up_stop_on_a, |
| transform_yes("f_down"), |
| transform_and_event_on("f_up", "f_down(a)", TreeNodeRecursion::Stop), |
| Transformed::new( |
| f_up_stop_on_a_transformed_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| rewrite_test!( |
| test_rewrite_f_up_stop_on_e, |
| transform_yes("f_down"), |
| transform_and_event_on("f_up", "f_down(e)", TreeNodeRecursion::Stop), |
| Transformed::new( |
| f_up_stop_on_e_transformed_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| |
| transform_test!( |
| test_transform, |
| transform_yes("f_down"), |
| transform_yes("f_up"), |
| Transformed::yes(transformed_tree()) |
| ); |
| transform_test!( |
| test_transform_f_down_jump_on_a, |
| transform_and_event_on("f_down", "a", TreeNodeRecursion::Jump), |
| transform_yes("f_up"), |
| Transformed::yes(transformed_tree()) |
| ); |
| transform_test!( |
| test_transform_f_down_jump_on_e, |
| transform_and_event_on("f_down", "e", TreeNodeRecursion::Jump), |
| transform_yes("f_up"), |
| Transformed::yes(f_down_jump_on_e_transformed_tree()) |
| ); |
| transform_test!( |
| test_transform_f_up_jump_on_a, |
| transform_yes("f_down"), |
| transform_and_event_on("f_up", "f_down(a)", TreeNodeRecursion::Jump), |
| Transformed::yes(f_up_jump_on_a_transformed_tree()) |
| ); |
| transform_test!( |
| test_transform_f_up_jump_on_e, |
| transform_yes("f_down"), |
| transform_and_event_on("f_up", "f_down(e)", TreeNodeRecursion::Jump), |
| Transformed::yes(f_up_jump_on_e_transformed_tree()) |
| ); |
| transform_test!( |
| test_transform_f_down_stop_on_a, |
| transform_and_event_on("f_down", "a", TreeNodeRecursion::Stop), |
| transform_yes("f_up"), |
| Transformed::new( |
| f_down_stop_on_a_transformed_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| transform_test!( |
| test_transform_f_down_stop_on_e, |
| transform_and_event_on("f_down", "e", TreeNodeRecursion::Stop), |
| transform_yes("f_up"), |
| Transformed::new( |
| f_down_stop_on_e_transformed_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| transform_test!( |
| test_transform_f_up_stop_on_a, |
| transform_yes("f_down"), |
| transform_and_event_on("f_up", "f_down(a)", TreeNodeRecursion::Stop), |
| Transformed::new( |
| f_up_stop_on_a_transformed_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| transform_test!( |
| test_transform_f_up_stop_on_e, |
| transform_yes("f_down"), |
| transform_and_event_on("f_up", "f_down(e)", TreeNodeRecursion::Stop), |
| Transformed::new( |
| f_up_stop_on_e_transformed_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| |
| transform_down_test!( |
| test_transform_down, |
| transform_yes("f_down"), |
| Transformed::yes(transformed_down_tree()) |
| ); |
| transform_down_test!( |
| test_transform_down_f_down_jump_on_a, |
| transform_and_event_on("f_down", "a", TreeNodeRecursion::Jump), |
| Transformed::yes(f_down_jump_on_a_transformed_down_tree()) |
| ); |
| transform_down_test!( |
| test_transform_down_f_down_jump_on_e, |
| transform_and_event_on("f_down", "e", TreeNodeRecursion::Jump), |
| Transformed::yes(f_down_jump_on_e_transformed_down_tree()) |
| ); |
| transform_down_test!( |
| test_transform_down_f_down_stop_on_a, |
| transform_and_event_on("f_down", "a", TreeNodeRecursion::Stop), |
| Transformed::new( |
| f_down_stop_on_a_transformed_down_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| transform_down_test!( |
| test_transform_down_f_down_stop_on_e, |
| transform_and_event_on("f_down", "e", TreeNodeRecursion::Stop), |
| Transformed::new( |
| f_down_stop_on_e_transformed_down_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| |
| transform_up_test!( |
| test_transform_up, |
| transform_yes("f_up"), |
| Transformed::yes(transformed_up_tree()) |
| ); |
| transform_up_test!( |
| test_transform_up_f_up_jump_on_a, |
| transform_and_event_on("f_up", "a", TreeNodeRecursion::Jump), |
| Transformed::yes(f_up_jump_on_a_transformed_up_tree()) |
| ); |
| transform_up_test!( |
| test_transform_up_f_up_jump_on_e, |
| transform_and_event_on("f_up", "e", TreeNodeRecursion::Jump), |
| Transformed::yes(f_up_jump_on_e_transformed_up_tree()) |
| ); |
| transform_up_test!( |
| test_transform_up_f_up_stop_on_a, |
| transform_and_event_on("f_up", "a", TreeNodeRecursion::Stop), |
| Transformed::new( |
| f_up_stop_on_a_transformed_up_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| transform_up_test!( |
| test_transform_up_f_up_stop_on_e, |
| transform_and_event_on("f_up", "e", TreeNodeRecursion::Stop), |
| Transformed::new( |
| f_up_stop_on_e_transformed_up_tree(), |
| true, |
| TreeNodeRecursion::Stop |
| ) |
| ); |
| |
| // F |
| // / | \ |
| // / | \ |
| // E C A |
| // | / \ |
| // C B D |
| // / \ | |
| // B D A |
| // | |
| // A |
| #[test] |
| fn test_apply_and_visit_references() -> Result<()> { |
| let node_a = TestTreeNode::new_leaf("a".to_string()); |
| let node_b = TestTreeNode::new_leaf("b".to_string()); |
| let node_d = TestTreeNode::new(vec![node_a], "d".to_string()); |
| let node_c = TestTreeNode::new(vec![node_b, node_d], "c".to_string()); |
| let node_e = TestTreeNode::new(vec![node_c], "e".to_string()); |
| let node_a_2 = TestTreeNode::new_leaf("a".to_string()); |
| let node_b_2 = TestTreeNode::new_leaf("b".to_string()); |
| let node_d_2 = TestTreeNode::new(vec![node_a_2], "d".to_string()); |
| let node_c_2 = TestTreeNode::new(vec![node_b_2, node_d_2], "c".to_string()); |
| let node_a_3 = TestTreeNode::new_leaf("a".to_string()); |
| let tree = TestTreeNode::new(vec![node_e, node_c_2, node_a_3], "f".to_string()); |
| |
| let node_f_ref = &tree; |
| let node_e_ref = &node_f_ref.children[0]; |
| let node_c_ref = &node_e_ref.children[0]; |
| let node_b_ref = &node_c_ref.children[0]; |
| let node_d_ref = &node_c_ref.children[1]; |
| let node_a_ref = &node_d_ref.children[0]; |
| |
| let mut m: HashMap<&TestTreeNode<String>, usize> = HashMap::new(); |
| tree.apply(|e| { |
| *m.entry(e).or_insert(0) += 1; |
| Ok(TreeNodeRecursion::Continue) |
| })?; |
| |
| let expected = HashMap::from([ |
| (node_f_ref, 1), |
| (node_e_ref, 1), |
| (node_c_ref, 2), |
| (node_d_ref, 2), |
| (node_b_ref, 2), |
| (node_a_ref, 3), |
| ]); |
| assert_eq!(m, expected); |
| |
| struct TestVisitor<'n> { |
| m: HashMap<&'n TestTreeNode<String>, (usize, usize)>, |
| } |
| |
| impl<'n> TreeNodeVisitor<'n> for TestVisitor<'n> { |
| type Node = TestTreeNode<String>; |
| |
| fn f_down(&mut self, node: &'n Self::Node) -> Result<TreeNodeRecursion> { |
| let (down_count, _) = self.m.entry(node).or_insert((0, 0)); |
| *down_count += 1; |
| Ok(TreeNodeRecursion::Continue) |
| } |
| |
| fn f_up(&mut self, node: &'n Self::Node) -> Result<TreeNodeRecursion> { |
| let (_, up_count) = self.m.entry(node).or_insert((0, 0)); |
| *up_count += 1; |
| Ok(TreeNodeRecursion::Continue) |
| } |
| } |
| |
| let mut visitor = TestVisitor { m: HashMap::new() }; |
| tree.visit(&mut visitor)?; |
| |
| let expected = HashMap::from([ |
| (node_f_ref, (1, 1)), |
| (node_e_ref, (1, 1)), |
| (node_c_ref, (2, 2)), |
| (node_d_ref, (2, 2)), |
| (node_b_ref, (2, 2)), |
| (node_a_ref, (3, 3)), |
| ]); |
| assert_eq!(visitor.m, expected); |
| |
| Ok(()) |
| } |
| |
| #[cfg(feature = "recursive_protection")] |
| #[test] |
| fn test_large_tree() { |
| let mut item = TestTreeNode::new_leaf("initial".to_string()); |
| for i in 0..3000 { |
| item = TestTreeNode::new(vec![item], format!("parent-{i}")); |
| } |
| |
| let mut visitor = |
| TestVisitor::new(Box::new(visit_continue), Box::new(visit_continue)); |
| |
| item.visit(&mut visitor).unwrap(); |
| } |
| } |