Function parse_expression

Source
pub fn parse_expression(input: &str) -> Result<AstExpr, ExprError>
Expand description

Parse an expression string into an AST using the Pratt parser. Returns a Result with either the parsed AST or an error explaining what went wrong.

Examples found in repository?
examples/benchmark_compare.rs (line 57)
30fn main() {
31    let benchmarks = [
32        ("sqrt(a^1.5+a^2.5)", native_sqrt_expr as fn(Real) -> Real),
33        ("a+5", native_a_plus_5),
34        ("a+(5*2)", native_a_plus_5_times_2),
35        ("(a+5)*2", native_a_plus_5_all_times_2),
36        ("(1/(a+1)+2/(a+2)+3/(a+3))", native_sum_fractions),
37        // Additional benchmark expressions with other functions:
38        ("sin(a)", |a: Real| a.sin()),
39        ("cos(a)", |a: Real| a.cos()),
40        ("tan(a)", |a: Real| a.tan()),
41        ("log(a+10)", |a: Real| (a+10.0).log10()),
42        ("ln(a+10)", |a: Real| (a+10.0).ln()),
43        ("abs(a-50)", |a: Real| (a-50.0).abs()),
44        ("max(a,100-a)", |a: Real| a.max(100.0-a)),
45        ("min(a,100-a)", |a: Real| a.min(100.0-a)),
46        ("pow(a,1.5)", |a: Real| a.powf(1.5)),
47        ("exp(a/100.0)", |a: Real| (a/100.0).exp()),
48        ("floor(a/3.1)", |a: Real| (a/3.1).floor()),
49        ("ceil(a/3.1)", |a: Real| (a/3.1).ceil()),
50        ("fmod(a,7)", |a: Real| a % 7.0),
51        ("neg(a)", |a: Real| -a),
52    ];
53
54    for (expr, native_func) in benchmarks.iter() {
55        println!("Benchmarking: {}", expr);
56
57        let ast = parse_expression(expr).expect("parse failed");
58
59        // Create a mutable context first before wrapping in Rc
60        let mut ctx_base = EvalContext::new();
61        
62        // Only register default math functions if the feature is available
63        #[cfg(not(feature = "no-builtin-math"))]
64        {
65            ctx_base.register_default_math_functions();
66        }
67        
68        // When no-builtin-math is enabled, we need to register the functions manually
69        #[cfg(feature = "no-builtin-math")]
70        {
71            // Register the minimum functions needed for our benchmarks
72            #[cfg(feature = "f32")]
73            {
74                ctx_base.register_native_function("sqrt", 1, |args| libm::sqrtf(args[0]));
75                ctx_base.register_native_function("sin", 1, |args| libm::sinf(args[0]));
76                ctx_base.register_native_function("cos", 1, |args| libm::cosf(args[0]));
77                ctx_base.register_native_function("tan", 1, |args| libm::tanf(args[0]));
78                ctx_base.register_native_function("log", 1, |args| libm::logf(args[0]));
79                ctx_base.register_native_function("log10", 1, |args| libm::log10f(args[0]));
80                ctx_base.register_native_function("ln", 1, |args| libm::logf(args[0]));
81                ctx_base.register_native_function("abs", 1, |args| args[0].abs());
82                ctx_base.register_native_function("max", 2, |args| args[0].max(args[1]));
83                ctx_base.register_native_function("min", 2, |args| args[0].min(args[1]));
84                ctx_base.register_native_function("pow", 2, |args| libm::powf(args[0], args[1]));
85                ctx_base.register_native_function("^", 2, |args| libm::powf(args[0], args[1]));
86                ctx_base.register_native_function("exp", 1, |args| libm::expf(args[0]));
87                ctx_base.register_native_function("floor", 1, |args| libm::floorf(args[0]));
88                ctx_base.register_native_function("ceil", 1, |args| libm::ceilf(args[0]));
89                ctx_base.register_native_function("neg", 1, |args| -args[0]);
90                ctx_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
91            }
92            #[cfg(not(feature = "f32"))]
93            {
94                ctx_base.register_native_function("sqrt", 1, |args| libm::sqrt(args[0]));
95                ctx_base.register_native_function("sin", 1, |args| libm::sin(args[0]));
96                ctx_base.register_native_function("cos", 1, |args| libm::cos(args[0]));
97                ctx_base.register_native_function("tan", 1, |args| libm::tan(args[0]));
98                ctx_base.register_native_function("log", 1, |args| libm::log(args[0]));
99                ctx_base.register_native_function("log10", 1, |args| libm::log10(args[0]));
100                ctx_base.register_native_function("ln", 1, |args| libm::log(args[0]));
101                ctx_base.register_native_function("abs", 1, |args| args[0].abs());
102                ctx_base.register_native_function("max", 2, |args| args[0].max(args[1]));
103                ctx_base.register_native_function("min", 2, |args| args[0].min(args[1]));
104                ctx_base.register_native_function("pow", 2, |args| libm::pow(args[0], args[1]));
105                ctx_base.register_native_function("^", 2, |args| libm::pow(args[0], args[1]));
106                ctx_base.register_native_function("exp", 1, |args| libm::exp(args[0]));
107                ctx_base.register_native_function("floor", 1, |args| libm::floor(args[0]));
108                ctx_base.register_native_function("ceil", 1, |args| libm::ceil(args[0]));
109                ctx_base.register_native_function("neg", 1, |args| -args[0]);
110                ctx_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
111            }
112        }
113        
114        let mut evalctx_sum = 0.0;
115        let start = Instant::now();
116        for j in 0..N {
117            // Create a new context for each iteration with the parameter set
118            let mut ctx = ctx_base.clone();
119            ctx.set_parameter("a", j as Real);
120            let ctx_rc = Rc::new(ctx);
121            evalctx_sum += eval_ast(&ast, Some(ctx_rc)).unwrap();
122        }
123        let evalctx_time = start.elapsed();
124        std::hint::black_box(evalctx_sum);
125
126        // Create a mutable context first before wrapping in Rc
127        let mut ctx_interp_base = EvalContext::new();
128        
129        // Only register default math functions if the feature is available
130        #[cfg(not(feature = "no-builtin-math"))]
131        {
132            ctx_interp_base.register_default_math_functions();
133        }
134        
135        // When no-builtin-math is enabled, we need to register the functions manually
136        #[cfg(feature = "no-builtin-math")]
137        {
138            // Register the minimum functions needed for our benchmarks
139            #[cfg(feature = "f32")]
140            {
141                ctx_interp_base.register_native_function("sqrt", 1, |args| libm::sqrtf(args[0]));
142                ctx_interp_base.register_native_function("sin", 1, |args| libm::sinf(args[0]));
143                ctx_interp_base.register_native_function("cos", 1, |args| libm::cosf(args[0]));
144                ctx_interp_base.register_native_function("tan", 1, |args| libm::tanf(args[0]));
145                ctx_interp_base.register_native_function("log", 1, |args| libm::logf(args[0]));
146                ctx_interp_base.register_native_function("log10", 1, |args| libm::log10f(args[0]));
147                ctx_interp_base.register_native_function("ln", 1, |args| libm::logf(args[0]));
148                ctx_interp_base.register_native_function("abs", 1, |args| args[0].abs());
149                ctx_interp_base.register_native_function("max", 2, |args| args[0].max(args[1]));
150                ctx_interp_base.register_native_function("min", 2, |args| args[0].min(args[1]));
151                ctx_interp_base.register_native_function("pow", 2, |args| libm::powf(args[0], args[1]));
152                ctx_interp_base.register_native_function("^", 2, |args| libm::powf(args[0], args[1]));
153                ctx_interp_base.register_native_function("exp", 1, |args| libm::expf(args[0]));
154                ctx_interp_base.register_native_function("floor", 1, |args| libm::floorf(args[0]));
155                ctx_interp_base.register_native_function("ceil", 1, |args| libm::ceilf(args[0]));
156                ctx_interp_base.register_native_function("neg", 1, |args| -args[0]);
157                ctx_interp_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
158            }
159            #[cfg(not(feature = "f32"))]
160            {
161                ctx_interp_base.register_native_function("sqrt", 1, |args| libm::sqrt(args[0]));
162                ctx_interp_base.register_native_function("sin", 1, |args| libm::sin(args[0]));
163                ctx_interp_base.register_native_function("cos", 1, |args| libm::cos(args[0]));
164                ctx_interp_base.register_native_function("tan", 1, |args| libm::tan(args[0]));
165                ctx_interp_base.register_native_function("log", 1, |args| libm::log(args[0]));
166                ctx_interp_base.register_native_function("log10", 1, |args| libm::log10(args[0]));
167                ctx_interp_base.register_native_function("ln", 1, |args| libm::log(args[0]));
168                ctx_interp_base.register_native_function("abs", 1, |args| args[0].abs());
169                ctx_interp_base.register_native_function("max", 2, |args| args[0].max(args[1]));
170                ctx_interp_base.register_native_function("min", 2, |args| args[0].min(args[1]));
171                ctx_interp_base.register_native_function("pow", 2, |args| libm::pow(args[0], args[1]));
172                ctx_interp_base.register_native_function("^", 2, |args| libm::pow(args[0], args[1]));
173                ctx_interp_base.register_native_function("exp", 1, |args| libm::exp(args[0]));
174                ctx_interp_base.register_native_function("floor", 1, |args| libm::floor(args[0]));
175                ctx_interp_base.register_native_function("ceil", 1, |args| libm::ceil(args[0]));
176                ctx_interp_base.register_native_function("neg", 1, |args| -args[0]);
177                ctx_interp_base.register_native_function("fmod", 2, |args| args[0] % args[1]);
178            }
179        }
180        
181        // Enable AST cache for the base context
182        ctx_interp_base.enable_ast_cache();
183        
184        let mut interp_sum = 0.0;
185        let start = Instant::now();
186        for j in 0..N {
187            // Create a new context for each iteration with the parameter set
188            let mut ctx_interp = ctx_interp_base.clone();
189            ctx_interp.set_parameter("a", j as Real);
190            let ctx_interp_rc = Rc::new(ctx_interp);
191            interp_sum += exp_rs::engine::interp(expr, Some(ctx_interp_rc)).unwrap();
192        }
193        let interp_eval_time = start.elapsed();
194        std::hint::black_box(interp_sum);
195
196        let mut native_sum = 0.0;
197        let start = Instant::now();
198        for j in 0..N {
199            native_sum += native_func(j as Real);
200        }
201        let native_time = start.elapsed();
202        std::hint::black_box(native_sum);
203
204        let evalctx_us = evalctx_time.as_micros();
205        let interp_us = interp_eval_time.as_micros();
206        let native_us = native_time.as_micros();
207
208        let slowdown_evalctx_vs_native = if native_us > 0 {
209            evalctx_us as f64 / native_us as f64
210        } else {
211            f64::NAN
212        };
213        let slowdown_interp_vs_native = if native_us > 0 {
214            interp_us as f64 / native_us as f64
215        } else {
216            f64::NAN
217        };
218        let slowdown_interp_vs_evalctx = if evalctx_us > 0 {
219            interp_us as f64 / evalctx_us as f64
220        } else {
221            f64::NAN
222        };
223
224        println!("evalctx - time: {} us, {:.2}x slower than native", evalctx_us, slowdown_evalctx_vs_native);
225        println!("interp - time: {} us, {:.2}x slower than native", interp_us, slowdown_interp_vs_native);
226        println!("native - time: {} us", native_us);
227        println!("evalctx vs native: {:.2}x slower", slowdown_evalctx_vs_native);
228        println!("interp vs native: {:.2}x slower", slowdown_interp_vs_native);
229        println!("interp vs evalctx: {:.2}x slower\n", slowdown_interp_vs_evalctx);
230    }
231}