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}