From c178253d24e39ebe161decf42936b603fbd6c902 Mon Sep 17 00:00:00 2001 From: sieri Date: Mon, 12 Jan 2026 22:27:01 +0100 Subject: [PATCH] Add a method to combine compiled trees together --- benches/benchs.rs | 25 ++++++++++++++++++++++++- src/error/display.rs | 1 + src/error/mod.rs | 3 +++ src/interface/mod.rs | 19 ++++++++++++++++++- src/tree/mod.rs | 36 ++++++++++++++++++++++++++++++++++++ tests/integration.rs | 15 +++++++++++++++ 6 files changed, 97 insertions(+), 2 deletions(-) diff --git a/benches/benchs.rs b/benches/benchs.rs index 47aacf7..3716950 100644 --- a/benches/benchs.rs +++ b/benches/benchs.rs @@ -5,7 +5,7 @@ extern crate rand; extern crate rand_pcg; extern crate test; -use evalexpr::{build_operator_tree, DefaultNumericTypes}; +use evalexpr::{build_operator_tree, combine_trees, DefaultNumericTypes, Operator}; use rand::{distributions::Uniform, seq::SliceRandom, Rng, SeedableRng}; use rand_pcg::Pcg32; use std::{fmt::Write, hint::black_box}; @@ -138,3 +138,26 @@ fn bench_evaluate_large_tuple_expression(bencher: &mut Bencher) { bencher.iter(|| large_tuple_expression.eval().unwrap()); } + +#[bench] +fn bench_tree_combining(bencher: &mut Bencher) { + let mut gen = Pcg32::seed_from_u64(33); + let small_expressions: Vec<_> = generate_small_expressions(BENCHMARK_LEN, &mut gen) + .iter() + .zip(&generate_small_expressions(BENCHMARK_LEN, &mut gen)) + .map(|(expression_a, expression_b)| { + ( + build_operator_tree::(expression_a).unwrap(), + build_operator_tree::(expression_b).unwrap(), + ) + }) + .collect(); + + bencher.iter(|| { + for expression in &small_expressions { + black_box( + combine_trees(expression.0.clone(), expression.1.clone(), Operator::Add).unwrap(), + ); + } + }); +} diff --git a/src/error/display.rs b/src/error/display.rs index effcf95..1ce44c2 100644 --- a/src/error/display.rs +++ b/src/error/display.rs @@ -150,6 +150,7 @@ impl fmt::Display for EvalexprError write!(f, "The feature 'rand' must be enabled to use randomness"), + UnsuitableOperator(operator) => write!(f, "Unsuitable operator used: {operator}"), CustomMessage(message) => write!(f, "Error: {}", message), } } diff --git a/src/error/mod.rs b/src/error/mod.rs index 3af244e..e8ca73e 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -246,6 +246,9 @@ pub enum EvalexprError /// The feature `rand` is not enabled, but required for the used function. RandNotEnabled, + /// An unsuitable Operator was used + UnsuitableOperator(Operator), + /// A custom error explained by its message. CustomMessage(String), } diff --git a/src/interface/mod.rs b/src/interface/mod.rs index 54785c6..1e76e75 100644 --- a/src/interface/mod.rs +++ b/src/interface/mod.rs @@ -6,7 +6,7 @@ use crate::{ TupleType, }, Context, ContextWithMutableVariables, EmptyType, EvalexprError, EvalexprResult, HashMapContext, - Node, Value, EMPTY_VALUE, + Node, Operator, Value, EMPTY_VALUE, }; /// Evaluate the given expression string. @@ -357,3 +357,20 @@ pub fn eval_empty_with_context_mut( Err(error) => Err(error), } } + +/// Combined two parsed expression trees around an operator +/// ``` +///use evalexpr::{build_operator_tree, combine_trees, eval, Operator}; +///let expr_1 = build_operator_tree("42/2").unwrap(); +///let expr_2 = build_operator_tree("34*5").unwrap(); +/// +///let combined = combine_trees(expr_1, expr_2, Operator::Add).unwrap(); +///assert_eq!(combined.eval(), eval("(42/2)+(34*5)")) +/// ``` +pub fn combine_trees( + a: Node, + b: Node, + operator: Operator, +) -> EvalexprResult, NumericTypes> { + tree::combine_trees(a, b, operator) +} diff --git a/src/tree/mod.rs b/src/tree/mod.rs index 51d200c..e0fac44 100644 --- a/src/tree/mod.rs +++ b/src/tree/mod.rs @@ -973,3 +973,39 @@ pub(crate) fn tokens_to_operator_tree( Err(EvalexprError::UnmatchedRBrace) } } + +/// combine two trees with a specific binary+ operator. +pub(crate) fn combine_trees( + a: Node, + b: Node, + operator: Operator, +) -> EvalexprResult, NumericTypes> { + if operator.is_unary() { + return Err(EvalexprError::UnsuitableOperator(operator)); + } + + let mut node: Node = Node::new(operator); + + node.children.push(extract_root(a)?); + node.children.push(extract_root(b)?); + + let mut root = Node::root_node(); + + root.children.push(node); + + Ok(root) +} + +/// extract root +fn extract_root( + a: Node, +) -> EvalexprResult, NumericTypes> { + if a.operator == Operator::RootNode { + Ok(a) + } else { + a.children + .into_iter() + .next() + .map_or_else(|| Err(EvalexprError::OutOfBoundsAccess), Ok) + } +} diff --git a/tests/integration.rs b/tests/integration.rs index f40bc38..54e88ee 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2610,3 +2610,18 @@ fn test_node_mutable_access() { assert_eq!(node.children_mut().len(), 1); assert_eq!(*node.operator_mut(), Operator::RootNode); } + +#[test] +fn test_tree_combining() { + let a = build_operator_tree::("1").unwrap(); + let b = build_operator_tree::("4").unwrap(); + + let combined = combine_trees(a.clone(), b, Operator::Add).unwrap(); + assert_eq!(combined.eval(), Ok(Value::Int(5))); + + let c = build_operator_tree::("4+2").unwrap(); + assert_eq!( + combine_trees(a, c, Operator::Add).unwrap().eval(), + Ok(Value::Int(7)) + ); +}