1use std::collections::{HashMap, HashSet};
11
12use ocas_atom::Atom;
13
14use crate::domain::{EvaluationDomain, PowfExtension};
15use crate::error::{EvaluationError, Result};
16use crate::evaluator::ExpressionEvaluator;
17use crate::function_map::FunctionMap;
18use crate::instruction::Instr;
19use crate::optimize;
20use crate::tree::EvalTree;
21
22pub fn compile_atom<T: EvaluationDomain + PowfExtension>(
24 atom: Atom<'_>,
25) -> Result<ExpressionEvaluator<T>> {
26 compile_atom_with(atom, None)
27}
28
29pub fn compile_atom_with<T: EvaluationDomain + PowfExtension>(
31 atom: Atom<'_>,
32 function_map: Option<FunctionMap<T>>,
33) -> Result<ExpressionEvaluator<T>> {
34 let tree = EvalTree::from_atom(atom);
35 compile_tree_with(&tree, function_map)
36}
37
38#[allow(dead_code)]
40pub fn compile_tree<T: EvaluationDomain + PowfExtension>(
41 tree: &EvalTree,
42) -> Result<ExpressionEvaluator<T>> {
43 compile_tree_with(tree, None)
44}
45
46pub fn compile_tree_with<T: EvaluationDomain + PowfExtension>(
48 tree: &EvalTree,
49 function_map: Option<FunctionMap<T>>,
50) -> Result<ExpressionEvaluator<T>> {
51 let mut var_names = HashSet::new();
53 let mut const_count = 0usize;
54 scan_tree(tree, &mut var_names, &mut const_count);
55 let param_count = var_names.len();
56
57 let mut sorted_vars: Vec<String> = var_names.into_iter().collect();
59 sorted_vars.sort();
60 let var_to_param: HashMap<String, usize> = sorted_vars
61 .iter()
62 .enumerate()
63 .map(|(i, v)| (v.clone(), i))
64 .collect();
65
66 let temp_base = param_count + const_count;
68 let (instructions, next_temp, constants, result_slot) = {
69 let mut ctx =
70 CompileContext::<T>::new(param_count, temp_base, var_to_param, function_map.as_ref());
71 let result_slot = ctx.compile_node(tree)?;
72 (ctx.instructions, ctx.next_temp, ctx.constants, result_slot)
73 };
74
75 let actual_const_count = constants.len();
76 let (instructions, _next_temp) = optimize::optimize(instructions, next_temp);
77 let stack_size = temp_base + next_temp;
78
79 match function_map {
80 Some(fm) => Ok(ExpressionEvaluator::new_with_functions(
81 instructions,
82 param_count,
83 actual_const_count,
84 stack_size,
85 vec![result_slot],
86 constants,
87 fm,
88 )),
89 None => Ok(ExpressionEvaluator::new(
90 instructions,
91 param_count,
92 actual_const_count,
93 stack_size,
94 vec![result_slot],
95 constants,
96 )),
97 }
98}
99
100fn scan_tree(tree: &EvalTree, vars: &mut HashSet<String>, const_count: &mut usize) {
102 match tree {
103 EvalTree::Num(_) => {
104 *const_count += 1;
105 }
106 EvalTree::Var(name) => {
107 vars.insert(name.clone());
108 }
109 EvalTree::Add(terms) | EvalTree::Mul(terms) => {
110 for t in terms {
111 scan_tree(t, vars, const_count);
112 }
113 }
114 EvalTree::Pow(base, exp) => {
115 scan_tree(base, vars, const_count);
116 scan_tree(exp, vars, const_count);
117 }
118 EvalTree::Fun(_, args) => {
119 for a in args {
120 scan_tree(a, vars, const_count);
121 }
122 }
123 }
124}
125
126struct CompileContext<'a, T: EvaluationDomain> {
127 instructions: Vec<Instr>,
128 next_temp: usize,
130 param_count: usize,
132 temp_base: usize,
134 variables: HashMap<String, usize>,
136 constants: Vec<T>,
138 function_map: Option<&'a FunctionMap<T>>,
140}
141
142impl<'a, T: EvaluationDomain> CompileContext<'a, T> {
143 fn new(
144 param_count: usize,
145 temp_base: usize,
146 variables: HashMap<String, usize>,
147 function_map: Option<&'a FunctionMap<T>>,
148 ) -> Self {
149 Self {
150 instructions: Vec::new(),
151 next_temp: 0,
152 param_count,
153 temp_base,
154 variables,
155 constants: Vec::new(),
156 function_map,
157 }
158 }
159
160 fn alloc_temp(&mut self) -> usize {
161 let slot = self.next_temp + self.temp_base;
162 self.next_temp += 1;
163 slot
164 }
165
166 fn param_slot(&self, name: &str) -> usize {
167 self.variables[name]
168 }
169
170 fn const_slot(&mut self, value: T) -> usize {
171 let idx = self.constants.len();
172 self.constants.push(value);
173 self.param_count + idx
174 }
175
176 fn compile_node(&mut self, node: &EvalTree) -> Result<usize> {
177 match node {
178 EvalTree::Num(n) => {
179 let dst = self.alloc_temp();
180 let const_slot = self.const_slot(T::from_f64(*n));
181 self.instructions.push(Instr::Copy {
182 dst,
183 src: const_slot,
184 });
185 Ok(dst)
186 }
187 EvalTree::Var(name) => {
188 let dst = self.alloc_temp();
189 let param_slot = self.param_slot(name);
190 self.instructions.push(Instr::Copy {
191 dst,
192 src: param_slot,
193 });
194 Ok(dst)
195 }
196 EvalTree::Add(terms) => {
197 let dst = self.alloc_temp();
198 let mut srcs = Vec::with_capacity(terms.len());
199 for term in terms {
200 srcs.push(self.compile_node(term)?);
201 }
202 self.instructions.push(Instr::Add { dst, srcs });
203 Ok(dst)
204 }
205 EvalTree::Mul(factors) => {
206 let dst = self.alloc_temp();
207 let mut srcs = Vec::with_capacity(factors.len());
208 for factor in factors {
209 srcs.push(self.compile_node(factor)?);
210 }
211 self.instructions.push(Instr::Mul { dst, srcs });
212 Ok(dst)
213 }
214 EvalTree::Pow(base, exp) => {
215 let base_slot = self.compile_node(base)?;
216 let dst = self.alloc_temp();
217 if let EvalTree::Num(n) = exp.as_ref()
218 && n.fract() == 0.0
219 && *n >= i64::MIN as f64
220 && *n <= i64::MAX as f64
221 {
222 self.instructions.push(Instr::Pow {
223 dst,
224 base: base_slot,
225 exp: *n as i64,
226 });
227 return Ok(dst);
228 }
229 let exp_slot = self.compile_node(exp)?;
230 self.instructions.push(Instr::Powf {
231 dst,
232 base: base_slot,
233 exp: exp_slot,
234 });
235 Ok(dst)
236 }
237 EvalTree::Fun(name, args) => {
238 if is_builtin(name) && args.len() == 1 {
239 let arg_slot = self.compile_node(&args[0])?;
240 let dst = self.alloc_temp();
241 let op = crate::instruction::BuiltinOp::from_name(name)
242 .expect("is_builtin guarantees known name");
243 self.instructions.push(Instr::BuiltinOp {
244 dst,
245 op,
246 src: arg_slot,
247 });
248 Ok(dst)
249 } else if let Some(fm) = self.function_map {
250 if let Some(_entry) = fm.resolve(name) {
252 let mut srcs = Vec::with_capacity(args.len());
253 for arg in args {
254 srcs.push(self.compile_node(arg)?);
255 }
256 let dst = self.alloc_temp();
257 let fn_idx =
259 fm.index_of(name)
260 .ok_or_else(|| EvaluationError::FunctionNotFound {
261 name: name.clone(),
262 })?;
263 self.instructions
264 .push(Instr::ExternalFun { dst, fn_idx, srcs });
265 Ok(dst)
266 } else {
267 Err(EvaluationError::FunctionNotFound { name: name.clone() })
268 }
269 } else {
270 Err(EvaluationError::FunctionNotFound { name: name.clone() })
271 }
272 }
273 }
274 }
275}
276
277fn is_builtin(name: &str) -> bool {
278 matches!(
279 name.to_lowercase().as_str(),
280 "sin" | "cos" | "tan" | "sec" | "csc" | "cot" | "exp" | "log" | "sqrt" | "abs"
281 )
282}
283
284impl<T: EvaluationDomain + PowfExtension> ExpressionEvaluator<T> {
289 pub fn compile(atom: Atom<'_>) -> Result<Self> {
291 compile_atom(atom)
292 }
293
294 pub fn compile_with(atom: Atom<'_>, map: FunctionMap<T>) -> Result<Self> {
296 compile_atom_with(atom, Some(map))
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303 use ocas_atom::AtomArena;
304 use ocas_core::arena::Arena;
305
306 #[test]
307 fn compile_constant() {
308 let arena = Arena::new();
309 let ctx = AtomArena::new(&arena);
310 let expr = ctx.num(42);
311 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
312 let result = eval.evaluate(&[]).unwrap();
313 assert!((result[0] - 42.0).abs() < 1e-10);
314 }
315
316 #[test]
317 fn compile_single_var() {
318 let arena = Arena::new();
319 let ctx = AtomArena::new(&arena);
320 let expr = ctx.var("x");
321 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
322 assert_eq!(eval.param_count(), 1);
323 let result = eval.evaluate(&[7.0]).unwrap();
324 assert!((result[0] - 7.0).abs() < 1e-10);
325 }
326
327 #[test]
328 fn compile_add_two_vars() {
329 let arena = Arena::new();
330 let ctx = AtomArena::new(&arena);
331 let expr = ctx.add(&[ctx.var("x"), ctx.var("y")]);
332 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
333 let result = eval.evaluate(&[2.0, 3.0]).unwrap();
334 assert!((result[0] - 5.0).abs() < 1e-10);
335 }
336
337 #[test]
338 fn compile_mul_var_const() {
339 let arena = Arena::new();
340 let ctx = AtomArena::new(&arena);
341 let expr = ctx.mul(&[ctx.var("x"), ctx.num(3)]);
342 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
343 let result = eval.evaluate(&[4.0]).unwrap();
344 assert!((result[0] - 12.0).abs() < 1e-10);
345 }
346
347 #[test]
348 fn compile_pow_integer_exp() {
349 let arena = Arena::new();
350 let ctx = AtomArena::new(&arena);
351 let expr = ctx.pow(ctx.var("x"), ctx.num(3));
352 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
353 let result = eval.evaluate(&[2.0]).unwrap();
354 assert!((result[0] - 8.0).abs() < 1e-10);
355 }
356
357 #[test]
358 fn compile_sin() {
359 let arena = Arena::new();
360 let ctx = AtomArena::new(&arena);
361 let expr = ctx.fun("sin", &[ctx.var("x")]);
362 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
363 let result = eval.evaluate(&[std::f64::consts::FRAC_PI_2]).unwrap();
364 assert!((result[0] - 1.0).abs() < 1e-10);
365 }
366
367 #[test]
368 fn compile_cos() {
369 let arena = Arena::new();
370 let ctx = AtomArena::new(&arena);
371 let expr = ctx.fun("cos", &[ctx.var("x")]);
372 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
373 let result = eval.evaluate(&[std::f64::consts::PI]).unwrap();
374 assert!((result[0] + 1.0).abs() < 1e-10);
375 }
376
377 #[test]
378 fn compile_exp_log_roundtrip() {
379 let arena = Arena::new();
380 let ctx = AtomArena::new(&arena);
381 let exp_x = ctx.fun("exp", &[ctx.var("x")]);
382 let expr = ctx.fun("log", &[exp_x]);
383 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
384 let result = eval.evaluate(&[2.0]).unwrap();
385 assert!((result[0] - 2.0).abs() < 1e-10);
386 }
387
388 #[test]
389 fn compile_nested_expression() {
390 let arena = Arena::new();
392 let ctx = AtomArena::new(&arena);
393 let x = ctx.var("x");
394 let x_plus_1 = ctx.add(&[x, ctx.num(1)]);
395 let x_minus_1 = ctx.add(&[x, ctx.num(-1)]);
396 let expr = ctx.mul(&[x_plus_1, x_minus_1]);
397 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
398 let result = eval.evaluate(&[3.0]).unwrap();
399 assert!((result[0] - 8.0).abs() < 1e-10);
400 }
401
402 #[test]
403 fn compile_sqrt() {
404 let arena = Arena::new();
405 let ctx = AtomArena::new(&arena);
406 let expr = ctx.fun("sqrt", &[ctx.num(16)]);
407 let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
408 let result = eval.evaluate(&[]).unwrap();
409 assert!((result[0] - 4.0).abs() < 1e-10);
410 }
411
412 #[test]
413 fn compile_zero_params() {
414 let arena = Arena::new();
415 let ctx = AtomArena::new(&arena);
416 let expr = ctx.fun("sin", &[ctx.num(1)]); let eval: ExpressionEvaluator<f64> = ExpressionEvaluator::compile(expr).unwrap();
418 assert_eq!(eval.param_count(), 0);
419 let result = eval.evaluate(&[]).unwrap();
420 assert!((result[0] - 1.0f64.sin()).abs() < 1e-10);
421 }
422
423 #[test]
424 fn compile_with_external_function() {
425 let arena = Arena::new();
426 let ctx = AtomArena::new(&arena);
427 let expr = ctx.fun("square", &[ctx.var("x")]);
428
429 let mut map = FunctionMap::<f64>::new();
430 map.register("square", 1, Box::new(|args| args[0] * args[0]));
431
432 let eval = ExpressionEvaluator::compile_with(expr, map).unwrap();
433 let result = eval.evaluate(&[3.0]).unwrap();
434 assert!((result[0] - 9.0).abs() < 1e-10);
435 }
436
437 #[test]
438 fn compile_external_function_not_registered() {
439 let arena = Arena::new();
440 let ctx = AtomArena::new(&arena);
441 let expr = ctx.fun("missing_fn", &[ctx.var("x")]);
442
443 let result: Result<ExpressionEvaluator<f64>> = ExpressionEvaluator::compile(expr);
444 assert!(result.is_err());
445 }
446
447 #[test]
448 fn compile_with_case_insensitive_external() {
449 let arena = Arena::new();
450 let ctx = AtomArena::new(&arena);
451 let expr = ctx.fun("Square", &[ctx.num(4)]);
452
453 let mut map = FunctionMap::<f64>::new();
454 map.register("square", 1, Box::new(|args| args[0] * args[0]));
455
456 let eval = ExpressionEvaluator::compile_with(expr, map).unwrap();
457 let result = eval.evaluate(&[]).unwrap();
458 assert!((result[0] - 16.0).abs() < 1e-10);
459 }
460}