Skip to main content

litex/execute/
exec_eval_stmt.rs

1use crate::prelude::*;
2use std::collections::HashMap;
3
4/// Right-hand side of a binary op waiting while we evaluate the left spine (iterative, no deep Rust recursion).
5enum PendingRight {
6    Add(Obj),
7    Sub(Obj),
8    Mul(Obj),
9    Div(Obj),
10}
11
12#[derive(Copy, Clone)]
13enum BinaryCombineOp {
14    Add,
15    Sub,
16    Mul,
17    Div,
18}
19
20impl Runtime {
21    fn object_supported_by_eval_stmt(obj: &Obj) -> bool {
22        matches!(
23            obj,
24            Obj::Number(_)
25                | Obj::FnObj(_)
26                | Obj::Add(_)
27                | Obj::Sub(_)
28                | Obj::Mul(_)
29                | Obj::Div(_)
30                | Obj::Pow(_)
31                | Obj::Sum(_)
32                | Obj::Product(_)
33                | Obj::MatrixListObj(_)
34                | Obj::MatrixAdd(_)
35                | Obj::MatrixSub(_)
36                | Obj::MatrixMul(_)
37                | Obj::MatrixScalarMul(_)
38                | Obj::MatrixPow(_)
39                | Obj::Atom(AtomObj::Identifier(_))
40        )
41    }
42
43    /// Only unary `'` … `{ … }` forms (or equivalent bare anonymous head); used by `eval` on sum/product.
44    fn summand_as_unary_anonymous_fn_cloned(obj: &Obj) -> Option<AnonymousFn> {
45        match obj {
46            Obj::AnonymousFn(af) => Some(af.clone()),
47            Obj::FnObj(fo) => {
48                if !fo.body.is_empty() {
49                    return None;
50                }
51                match fo.head.as_ref() {
52                    FnObjHead::AnonymousFnLiteral(a) => Some((**a).clone()),
53                    _ => None,
54                }
55            }
56            _ => None,
57        }
58    }
59
60    /// After substituting the sum/product index, evaluate any nested `sum` / `product` in the
61    /// expression to a numeric value, then the outer accumulation can use `evaluate_to_normalized_decimal_number`.
62    fn eval_reduce_nested_sum_product_in_obj(
63        &mut self,
64        obj: Obj,
65        eval_stmt: &EvalStmt,
66    ) -> Result<Obj, RuntimeError> {
67        match obj {
68            Obj::Sum(s) => self.eval_sum_or_product_for_eval_stmt(
69                s.start.as_ref(),
70                s.end.as_ref(),
71                s.func.as_ref(),
72                false,
73                eval_stmt,
74            ),
75            Obj::Product(p) => self.eval_sum_or_product_for_eval_stmt(
76                p.start.as_ref(),
77                p.end.as_ref(),
78                p.func.as_ref(),
79                true,
80                eval_stmt,
81            ),
82            Obj::Add(b) => {
83                let l = self.eval_reduce_nested_sum_product_in_obj((*b.left).clone(), eval_stmt)?;
84                let r =
85                    self.eval_reduce_nested_sum_product_in_obj((*b.right).clone(), eval_stmt)?;
86                Ok(Add::new(l, r).into())
87            }
88            Obj::Sub(b) => {
89                let l = self.eval_reduce_nested_sum_product_in_obj((*b.left).clone(), eval_stmt)?;
90                let r =
91                    self.eval_reduce_nested_sum_product_in_obj((*b.right).clone(), eval_stmt)?;
92                Ok(Sub::new(l, r).into())
93            }
94            Obj::Mul(b) => {
95                let l = self.eval_reduce_nested_sum_product_in_obj((*b.left).clone(), eval_stmt)?;
96                let r =
97                    self.eval_reduce_nested_sum_product_in_obj((*b.right).clone(), eval_stmt)?;
98                Ok(Mul::new(l, r).into())
99            }
100            Obj::Div(b) => {
101                let l = self.eval_reduce_nested_sum_product_in_obj((*b.left).clone(), eval_stmt)?;
102                let r =
103                    self.eval_reduce_nested_sum_product_in_obj((*b.right).clone(), eval_stmt)?;
104                Ok(Div::new(l, r).into())
105            }
106            Obj::Mod(m) => {
107                let l = self.eval_reduce_nested_sum_product_in_obj((*m.left).clone(), eval_stmt)?;
108                let r =
109                    self.eval_reduce_nested_sum_product_in_obj((*m.right).clone(), eval_stmt)?;
110                Ok(Mod::new(l, r).into())
111            }
112            Obj::Pow(p) => {
113                let base =
114                    self.eval_reduce_nested_sum_product_in_obj((*p.base).clone(), eval_stmt)?;
115                let exp =
116                    self.eval_reduce_nested_sum_product_in_obj((*p.exponent).clone(), eval_stmt)?;
117                Ok(Pow::new(base, exp).into())
118            }
119            Obj::Abs(a) => {
120                let arg =
121                    self.eval_reduce_nested_sum_product_in_obj((*a.arg).clone(), eval_stmt)?;
122                Ok(Abs::new(arg).into())
123            }
124            Obj::Log(l) => {
125                let b = self.eval_reduce_nested_sum_product_in_obj((*l.base).clone(), eval_stmt)?;
126                let x = self.eval_reduce_nested_sum_product_in_obj((*l.arg).clone(), eval_stmt)?;
127                Ok(Log::new(b, x).into())
128            }
129            Obj::Max(m) => {
130                let l = self.eval_reduce_nested_sum_product_in_obj((*m.left).clone(), eval_stmt)?;
131                let r =
132                    self.eval_reduce_nested_sum_product_in_obj((*m.right).clone(), eval_stmt)?;
133                Ok(Max::new(l, r).into())
134            }
135            Obj::Min(m) => {
136                let l = self.eval_reduce_nested_sum_product_in_obj((*m.left).clone(), eval_stmt)?;
137                let r =
138                    self.eval_reduce_nested_sum_product_in_obj((*m.right).clone(), eval_stmt)?;
139                Ok(Min::new(l, r).into())
140            }
141            other => Ok(other),
142        }
143    }
144
145    /// Closed integer range: substitute index into the anonymous body `equal_to` and total + or *; no `fn`/algo in terms.
146    fn eval_sum_or_product_for_eval_stmt(
147        &mut self,
148        start: &Obj,
149        end: &Obj,
150        func: &Obj,
151        is_product: bool,
152        eval_stmt: &EvalStmt,
153    ) -> Result<Obj, RuntimeError> {
154        let start_ev = self.evaluate_symbol_obj_iterative(start.clone(), eval_stmt)?;
155        let end_ev = self.evaluate_symbol_obj_iterative(end.clone(), eval_stmt)?;
156        let Some(a_num) = self.resolve_obj_to_number(&start_ev) else {
157            return Err(short_exec_error(
158                eval_stmt.clone().into(),
159                "eval: sum/product start must resolve to a number".to_string(),
160                None,
161                vec![],
162            ));
163        };
164        let Some(b_num) = self.resolve_obj_to_number(&end_ev) else {
165            return Err(short_exec_error(
166                eval_stmt.clone().into(),
167                "eval: sum/product end must resolve to a number".to_string(),
168                None,
169                vec![],
170            ));
171        };
172        let as_ = a_num.normalized_value.trim();
173        let bs = b_num.normalized_value.trim();
174        if !is_number_string_literally_integer_without_dot(as_.to_string())
175            || !is_number_string_literally_integer_without_dot(bs.to_string())
176        {
177            return Err(short_exec_error(
178                eval_stmt.clone().into(),
179                "eval: sum/product need integer (no fractional part) start and end for iteration"
180                    .to_string(),
181                None,
182                vec![],
183            ));
184        }
185        let ai = as_.parse::<i128>().map_err(|_| {
186            short_exec_error(
187                eval_stmt.clone().into(),
188                "eval: sum/product could not parse integer bounds".to_string(),
189                None,
190                vec![],
191            )
192        })?;
193        let bi = bs.parse::<i128>().map_err(|_| {
194            short_exec_error(
195                eval_stmt.clone().into(),
196                "eval: sum/product could not parse integer bounds".to_string(),
197                None,
198                vec![],
199            )
200        })?;
201        if ai > bi {
202            return Err(short_exec_error(
203                eval_stmt.clone().into(),
204                "eval: sum/product need start <= end (integer range)".to_string(),
205                None,
206                vec![],
207            ));
208        }
209        let Some(af) = Self::summand_as_unary_anonymous_fn_cloned(func) else {
210            return Err(short_exec_error(
211                eval_stmt.clone().into(),
212                "eval: sum/product third argument must be a unary anonymous function (no calls)"
213                    .to_string(),
214                None,
215                vec![],
216            ));
217        };
218        if ParamGroupWithSet::number_of_params(&af.body.params_def_with_set) != 1 {
219            return Err(short_exec_error(
220                eval_stmt.clone().into(),
221                "eval: sum/product index function must be unary".to_string(),
222                None,
223                vec![],
224            ));
225        }
226        let param_names = ParamGroupWithSet::collect_param_names(&af.body.params_def_with_set);
227        let pname = param_names[0].clone();
228        let mut acc_num = if is_product {
229            Number::new("1".to_string())
230        } else {
231            Number::new("0".to_string())
232        };
233        for k in ai..=bi {
234            let mut param_to_arg_map: HashMap<String, Obj> = HashMap::new();
235            param_to_arg_map.insert(pname.clone(), Number::new(k.to_string()).into());
236            let inst =
237                self.inst_obj(af.equal_to.as_ref(), &param_to_arg_map, ParamObjType::FnSet)?;
238            let term = self.resolve_obj(&inst);
239            let term = self.eval_reduce_nested_sum_product_in_obj(term, eval_stmt)?;
240            let Some(n) = term.evaluate_to_normalized_decimal_number() else {
241                return Err(short_exec_error(
242                    eval_stmt.clone().into(),
243                    format!(
244                        "eval: could not reduce sum/product body to a number at index {}",
245                        k
246                    ),
247                    None,
248                    vec![],
249                ));
250            };
251            if is_product {
252                let step: Obj = Mul::new(acc_num.into(), n.into()).into();
253                acc_num = step
254                    .evaluate_to_normalized_decimal_number()
255                    .ok_or_else(|| {
256                        short_exec_error(
257                            eval_stmt.clone().into(),
258                            "eval: product accumulation failed to normalize".to_string(),
259                            None,
260                            vec![],
261                        )
262                    })?;
263            } else {
264                let step: Obj = Add::new(acc_num.into(), n.into()).into();
265                acc_num = step
266                    .evaluate_to_normalized_decimal_number()
267                    .ok_or_else(|| {
268                        short_exec_error(
269                            eval_stmt.clone().into(),
270                            "eval: sum accumulation failed to normalize".to_string(),
271                            None,
272                            vec![],
273                        )
274                    })?;
275            }
276        }
277        Ok(acc_num.into())
278    }
279
280    fn eval_matrix_list_cells_for_eval_stmt(
281        &mut self,
282        m: MatrixListObj,
283        eval_stmt: &EvalStmt,
284    ) -> Result<MatrixListObj, RuntimeError> {
285        let mut rows_out = Vec::with_capacity(m.rows.len());
286        for row in m.rows {
287            let mut out_row = Vec::with_capacity(row.len());
288            for cell in row {
289                let v = self.evaluate_symbol_obj_iterative(*cell, eval_stmt)?;
290                out_row.push(Box::new(v));
291            }
292            rows_out.push(out_row);
293        }
294        Ok(MatrixListObj { rows: rows_out })
295    }
296
297    fn add_matrix_lists_under_eval(
298        &self,
299        left: MatrixListObj,
300        right: MatrixListObj,
301        eval_stmt: &EvalStmt,
302    ) -> Result<MatrixListObj, RuntimeError> {
303        if left.rows.len() != right.rows.len() {
304            return Err(short_exec_error(
305                eval_stmt.clone().into(),
306                "eval: matrix ++ row count mismatch".to_string(),
307                None,
308                vec![],
309            ));
310        }
311        let mut rows = Vec::with_capacity(left.rows.len());
312        for (lr, rr) in left.rows.into_iter().zip(right.rows.into_iter()) {
313            if lr.len() != rr.len() {
314                return Err(short_exec_error(
315                    eval_stmt.clone().into(),
316                    "eval: matrix ++ column count mismatch".to_string(),
317                    None,
318                    vec![],
319                ));
320            }
321            let mut row = Vec::with_capacity(lr.len());
322            for (a, b) in lr.into_iter().zip(rr.into_iter()) {
323                let sum_obj: Obj = Add::new(*a, *b).into();
324                let Some(n) = sum_obj.evaluate_to_normalized_decimal_number() else {
325                    return Err(short_exec_error(
326                        eval_stmt.clone().into(),
327                        "eval: matrix ++ needs numeric cells".to_string(),
328                        None,
329                        vec![],
330                    ));
331                };
332                row.push(Box::new(n.into()));
333            }
334            rows.push(row);
335        }
336        Ok(MatrixListObj { rows })
337    }
338
339    fn sub_matrix_lists_under_eval(
340        &self,
341        left: MatrixListObj,
342        right: MatrixListObj,
343        eval_stmt: &EvalStmt,
344    ) -> Result<MatrixListObj, RuntimeError> {
345        if left.rows.len() != right.rows.len() {
346            return Err(short_exec_error(
347                eval_stmt.clone().into(),
348                "eval: matrix -- row count mismatch".to_string(),
349                None,
350                vec![],
351            ));
352        }
353        let mut rows = Vec::with_capacity(left.rows.len());
354        for (lr, rr) in left.rows.into_iter().zip(right.rows.into_iter()) {
355            if lr.len() != rr.len() {
356                return Err(short_exec_error(
357                    eval_stmt.clone().into(),
358                    "eval: matrix -- column count mismatch".to_string(),
359                    None,
360                    vec![],
361                ));
362            }
363            let mut row = Vec::with_capacity(lr.len());
364            for (a, b) in lr.into_iter().zip(rr.into_iter()) {
365                let diff_obj: Obj = Sub::new(*a, *b).into();
366                let Some(n) = diff_obj.evaluate_to_normalized_decimal_number() else {
367                    return Err(short_exec_error(
368                        eval_stmt.clone().into(),
369                        "eval: matrix -- needs numeric cells".to_string(),
370                        None,
371                        vec![],
372                    ));
373                };
374                row.push(Box::new(n.into()));
375            }
376            rows.push(row);
377        }
378        Ok(MatrixListObj { rows })
379    }
380
381    fn multiply_matrix_lists_under_eval(
382        &self,
383        left: MatrixListObj,
384        right: MatrixListObj,
385        eval_stmt: &EvalStmt,
386    ) -> Result<MatrixListObj, RuntimeError> {
387        let r1 = left.rows.len();
388        let c1 = if r1 == 0 { 0 } else { left.rows[0].len() };
389        let r2 = right.rows.len();
390        let c2 = if r2 == 0 { 0 } else { right.rows[0].len() };
391        if c1 != r2 {
392            return Err(short_exec_error(
393                eval_stmt.clone().into(),
394                "eval: matrix ** inner dimension mismatch".to_string(),
395                None,
396                vec![],
397            ));
398        }
399        let mut rows: Vec<Vec<Box<Obj>>> = Vec::with_capacity(r1);
400        for i in 0..r1 {
401            let mut row: Vec<Box<Obj>> = Vec::with_capacity(c2);
402            for k in 0..c2 {
403                let mut acc_num = Number::new("0".to_string());
404                for j in 0..c1 {
405                    let prod_obj: Obj =
406                        Mul::new((*left.rows[i][j]).clone(), (*right.rows[j][k]).clone()).into();
407                    let Some(p) = prod_obj.evaluate_to_normalized_decimal_number() else {
408                        return Err(short_exec_error(
409                            eval_stmt.clone().into(),
410                            "eval: matrix ** cell multiply failed".to_string(),
411                            None,
412                            vec![],
413                        ));
414                    };
415                    let sum_obj: Obj = Add::new(acc_num.into(), p.into()).into();
416                    let Some(s) = sum_obj.evaluate_to_normalized_decimal_number() else {
417                        return Err(short_exec_error(
418                            eval_stmt.clone().into(),
419                            "eval: matrix ** accumulation failed".to_string(),
420                            None,
421                            vec![],
422                        ));
423                    };
424                    acc_num = s;
425                }
426                row.push(Box::new(acc_num.into()));
427            }
428            rows.push(row);
429        }
430        Ok(MatrixListObj { rows })
431    }
432
433    fn scalar_matrix_mul_under_eval(
434        &self,
435        scalar: Obj,
436        matrix: MatrixListObj,
437        eval_stmt: &EvalStmt,
438    ) -> Result<MatrixListObj, RuntimeError> {
439        let mut rows_out = Vec::with_capacity(matrix.rows.len());
440        for row in matrix.rows {
441            let mut out_row = Vec::with_capacity(row.len());
442            for cell in row {
443                let prod_obj: Obj = Mul::new(scalar.clone(), (*cell).clone()).into();
444                let Some(n) = prod_obj.evaluate_to_normalized_decimal_number() else {
445                    return Err(short_exec_error(
446                        eval_stmt.clone().into(),
447                        "eval: *. needs scalar and numeric matrix cells".to_string(),
448                        None,
449                        vec![],
450                    ));
451                };
452                out_row.push(Box::new(n.into()));
453            }
454            rows_out.push(out_row);
455        }
456        Ok(MatrixListObj { rows: rows_out })
457    }
458
459    fn matrix_pow_under_eval(
460        &self,
461        base: MatrixListObj,
462        exponent: usize,
463        eval_stmt: &EvalStmt,
464    ) -> Result<MatrixListObj, RuntimeError> {
465        if exponent == 0 {
466            return Err(short_exec_error(
467                eval_stmt.clone().into(),
468                "eval: matrix ^^ exponent must be at least 1".to_string(),
469                None,
470                vec![],
471            ));
472        }
473        let mut acc = base.clone();
474        for _ in 1..exponent {
475            acc = self.multiply_matrix_lists_under_eval(acc, base.clone(), eval_stmt)?;
476        }
477        Ok(acc)
478    }
479
480    fn eval_to_matrix_list_for_eval_stmt(
481        &mut self,
482        obj: Obj,
483        eval_stmt: &EvalStmt,
484    ) -> Result<MatrixListObj, RuntimeError> {
485        let cur = self.peel_fn_obj_dispatch_loop(obj, eval_stmt)?;
486        match cur {
487            Obj::MatrixListObj(m) => self.eval_matrix_list_cells_for_eval_stmt(m, eval_stmt),
488            Obj::MatrixAdd(ma) => {
489                let l = self.eval_to_matrix_list_for_eval_stmt((*ma.left).clone(), eval_stmt)?;
490                let r = self.eval_to_matrix_list_for_eval_stmt((*ma.right).clone(), eval_stmt)?;
491                self.add_matrix_lists_under_eval(l, r, eval_stmt)
492            }
493            Obj::MatrixSub(ms) => {
494                let l = self.eval_to_matrix_list_for_eval_stmt((*ms.left).clone(), eval_stmt)?;
495                let r = self.eval_to_matrix_list_for_eval_stmt((*ms.right).clone(), eval_stmt)?;
496                self.sub_matrix_lists_under_eval(l, r, eval_stmt)
497            }
498            Obj::MatrixMul(mm) => {
499                let l = self.eval_to_matrix_list_for_eval_stmt((*mm.left).clone(), eval_stmt)?;
500                let r = self.eval_to_matrix_list_for_eval_stmt((*mm.right).clone(), eval_stmt)?;
501                self.multiply_matrix_lists_under_eval(l, r, eval_stmt)
502            }
503            Obj::MatrixScalarMul(m) => {
504                let s = self.evaluate_symbol_obj_iterative((*m.scalar).clone(), eval_stmt)?;
505                let mat = self.eval_to_matrix_list_for_eval_stmt((*m.matrix).clone(), eval_stmt)?;
506                self.scalar_matrix_mul_under_eval(s, mat, eval_stmt)
507            }
508            Obj::MatrixPow(mp) => {
509                let base = self.eval_to_matrix_list_for_eval_stmt((*mp.base).clone(), eval_stmt)?;
510                let exp_obj =
511                    self.evaluate_symbol_obj_iterative((*mp.exponent).clone(), eval_stmt)?;
512                let Some(exp_num) = exp_obj.evaluate_to_normalized_decimal_number() else {
513                    return Err(short_exec_error(
514                        eval_stmt.clone().into(),
515                        "eval: matrix ^^ exponent must evaluate to a number".to_string(),
516                        None,
517                        vec![],
518                    ));
519                };
520                let exp_u = exp_num.normalized_value.parse::<usize>().map_err(|_| {
521                    short_exec_error(
522                        eval_stmt.clone().into(),
523                        "eval: matrix ^^ exponent must be a non-negative integer".to_string(),
524                        None,
525                        vec![],
526                    )
527                })?;
528                self.matrix_pow_under_eval(base, exp_u, eval_stmt)
529            }
530            other => {
531                let lookup_key = match &other {
532                    Obj::Atom(AtomObj::Identifier(id)) => id.name.clone(),
533                    _ => other.to_string(),
534                };
535                let Some(ml) = self.get_obj_equal_to_matrix_list(&lookup_key) else {
536                    return Err(short_exec_error(
537                        eval_stmt.clone().into(),
538                        format!("eval: `{}` is not a matrix list", lookup_key),
539                        None,
540                        vec![],
541                    ));
542                };
543                self.eval_to_matrix_list_for_eval_stmt(ml.into(), eval_stmt)
544            }
545        }
546    }
547
548    fn finish_numeric_accumulator_with_pending_rights(
549        &mut self,
550        acc: Obj,
551        pending: &mut Vec<PendingRight>,
552        eval_stmt: &EvalStmt,
553    ) -> Result<Obj, RuntimeError> {
554        let mut acc = acc;
555        while let Some(pend) = pending.pop() {
556            let (combine_op, right_obj) = match pend {
557                PendingRight::Add(o) => (BinaryCombineOp::Add, o),
558                PendingRight::Sub(o) => (BinaryCombineOp::Sub, o),
559                PendingRight::Mul(o) => (BinaryCombineOp::Mul, o),
560                PendingRight::Div(o) => (BinaryCombineOp::Div, o),
561            };
562            let right_eval = self.evaluate_symbol_obj_iterative(right_obj, eval_stmt)?;
563            acc = self.combine_two_numeric_objs(acc, right_eval, combine_op, eval_stmt)?;
564        }
565        Ok(acc)
566    }
567
568    /// Evaluates numeric expressions for `eval` without deep recursion on the Rust stack.
569    /// Algorithm calls are expanded in a loop; `Add`/`Sub`/`Mul`/`Div` use an explicit stack for the left spine.
570    fn evaluate_symbol_obj_iterative(
571        &mut self,
572        initial: Obj,
573        eval_stmt: &EvalStmt,
574    ) -> Result<Obj, RuntimeError> {
575        let mut pending: Vec<PendingRight> = Vec::new();
576        let mut cur = initial;
577
578        loop {
579            cur = self.peel_fn_obj_dispatch_loop(cur, eval_stmt)?;
580
581            match cur {
582                Obj::Add(add) => {
583                    pending.push(PendingRight::Add(*add.right));
584                    cur = *add.left;
585                    continue;
586                }
587                Obj::Sub(sub) => {
588                    pending.push(PendingRight::Sub(*sub.right));
589                    cur = *sub.left;
590                    continue;
591                }
592                Obj::Mul(mul) => {
593                    pending.push(PendingRight::Mul(*mul.right));
594                    cur = *mul.left;
595                    continue;
596                }
597                Obj::Div(div) => {
598                    pending.push(PendingRight::Div(*div.right));
599                    cur = *div.left;
600                    continue;
601                }
602                Obj::Number(acc_num) => {
603                    return self.finish_numeric_accumulator_with_pending_rights(
604                        acc_num.into(),
605                        &mut pending,
606                        eval_stmt,
607                    );
608                }
609                Obj::Pow(pow) => {
610                    let left =
611                        self.evaluate_symbol_obj_iterative((*pow.base).clone(), eval_stmt)?;
612                    let right =
613                        self.evaluate_symbol_obj_iterative((*pow.exponent).clone(), eval_stmt)?;
614                    let combined: Obj = Pow::new(left, right).into();
615                    match combined.evaluate_to_normalized_decimal_number() {
616                        Some(acc_num) => {
617                            return self.finish_numeric_accumulator_with_pending_rights(
618                                acc_num.into(),
619                                &mut pending,
620                                eval_stmt,
621                            );
622                        }
623                        None => {
624                            if pending.is_empty() {
625                                return Ok(combined);
626                            }
627                            return Err(short_exec_error(
628                                eval_stmt.clone().into(),
629                                "eval: non-numeric power with pending binary operation".to_string(),
630                                None,
631                                vec![],
632                            ));
633                        }
634                    }
635                }
636                Obj::Sum(sum) => {
637                    if !pending.is_empty() {
638                        return Err(short_exec_error(
639                            eval_stmt.clone().into(),
640                            "eval: sum with pending binary operation".to_string(),
641                            None,
642                            vec![],
643                        ));
644                    }
645                    let v = self.eval_sum_or_product_for_eval_stmt(
646                        sum.start.as_ref(),
647                        sum.end.as_ref(),
648                        sum.func.as_ref(),
649                        false,
650                        eval_stmt,
651                    )?;
652                    return self.finish_numeric_accumulator_with_pending_rights(
653                        v,
654                        &mut pending,
655                        eval_stmt,
656                    );
657                }
658                Obj::Product(prod) => {
659                    if !pending.is_empty() {
660                        return Err(short_exec_error(
661                            eval_stmt.clone().into(),
662                            "eval: product with pending binary operation".to_string(),
663                            None,
664                            vec![],
665                        ));
666                    }
667                    let v = self.eval_sum_or_product_for_eval_stmt(
668                        prod.start.as_ref(),
669                        prod.end.as_ref(),
670                        prod.func.as_ref(),
671                        true,
672                        eval_stmt,
673                    )?;
674                    return self.finish_numeric_accumulator_with_pending_rights(
675                        v,
676                        &mut pending,
677                        eval_stmt,
678                    );
679                }
680                Obj::MatrixListObj(m) => {
681                    if !pending.is_empty() {
682                        return Err(short_exec_error(
683                            eval_stmt.clone().into(),
684                            "eval: matrix value with pending binary operation".to_string(),
685                            None,
686                            vec![],
687                        ));
688                    }
689                    let done = self.eval_matrix_list_cells_for_eval_stmt(m, eval_stmt)?;
690                    return Ok(done.into());
691                }
692                Obj::MatrixAdd(ma) => {
693                    if !pending.is_empty() {
694                        return Err(short_exec_error(
695                            eval_stmt.clone().into(),
696                            "eval: matrix ++ with pending binary operation".to_string(),
697                            None,
698                            vec![],
699                        ));
700                    }
701                    let done =
702                        self.eval_to_matrix_list_for_eval_stmt(Obj::MatrixAdd(ma), eval_stmt)?;
703                    return Ok(done.into());
704                }
705                Obj::MatrixSub(ms) => {
706                    if !pending.is_empty() {
707                        return Err(short_exec_error(
708                            eval_stmt.clone().into(),
709                            "eval: matrix -- with pending binary operation".to_string(),
710                            None,
711                            vec![],
712                        ));
713                    }
714                    let done =
715                        self.eval_to_matrix_list_for_eval_stmt(Obj::MatrixSub(ms), eval_stmt)?;
716                    return Ok(done.into());
717                }
718                Obj::MatrixMul(mm) => {
719                    if !pending.is_empty() {
720                        return Err(short_exec_error(
721                            eval_stmt.clone().into(),
722                            "eval: matrix ** with pending binary operation".to_string(),
723                            None,
724                            vec![],
725                        ));
726                    }
727                    let done =
728                        self.eval_to_matrix_list_for_eval_stmt(Obj::MatrixMul(mm), eval_stmt)?;
729                    return Ok(done.into());
730                }
731                Obj::MatrixScalarMul(m) => {
732                    if !pending.is_empty() {
733                        return Err(short_exec_error(
734                            eval_stmt.clone().into(),
735                            "eval: *. with pending binary operation".to_string(),
736                            None,
737                            vec![],
738                        ));
739                    }
740                    let done =
741                        self.eval_to_matrix_list_for_eval_stmt(Obj::MatrixScalarMul(m), eval_stmt)?;
742                    return Ok(done.into());
743                }
744                Obj::MatrixPow(mp) => {
745                    if !pending.is_empty() {
746                        return Err(short_exec_error(
747                            eval_stmt.clone().into(),
748                            "eval: matrix ^^ with pending binary operation".to_string(),
749                            None,
750                            vec![],
751                        ));
752                    }
753                    let done =
754                        self.eval_to_matrix_list_for_eval_stmt(Obj::MatrixPow(mp), eval_stmt)?;
755                    return Ok(done.into());
756                }
757                _ => {
758                    if pending.is_empty() {
759                        return Ok(cur);
760                    }
761                    return Err(short_exec_error(
762                        eval_stmt.clone().into(),
763                        "eval: non-numeric intermediate with pending binary operation".to_string(),
764                        None,
765                        vec![],
766                    ));
767                }
768            }
769        }
770    }
771
772    fn combine_two_numeric_objs(
773        &mut self,
774        left: Obj,
775        right: Obj,
776        combine_op: BinaryCombineOp,
777        eval_stmt: &EvalStmt,
778    ) -> Result<Obj, RuntimeError> {
779        let combined: Obj = match combine_op {
780            BinaryCombineOp::Add => Add::new(left, right).into(),
781            BinaryCombineOp::Sub => Sub::new(left, right).into(),
782            BinaryCombineOp::Mul => Mul::new(left, right).into(),
783            BinaryCombineOp::Div => Div::new(left, right).into(),
784        };
785        let calculated = combined.evaluate_to_normalized_decimal_number();
786        match calculated {
787            Some(number) => Ok(number.into()),
788            None => Err(short_exec_error(
789                eval_stmt.clone().into(),
790                "eval: failed to combine numeric sub-expression".to_string(),
791                None,
792                vec![],
793            )),
794        }
795    }
796
797    /// Repeatedly expands `FnObj` using the algo definition until the head is not a call.
798    fn peel_fn_obj_dispatch_loop(
799        &mut self,
800        mut cur: Obj,
801        eval_stmt: &EvalStmt,
802    ) -> Result<Obj, RuntimeError> {
803        while let Obj::FnObj(ref fn_obj) = cur {
804            cur = self.dispatch_algo_one_return_expr(fn_obj, eval_stmt)?;
805        }
806        Ok(cur)
807    }
808
809    /// One algo step: bind numeric args, match case / default, return **instantiated** return expression only (no recursive eval).
810    fn dispatch_algo_one_return_expr(
811        &mut self,
812        fn_obj_to_evaluate: &FnObj,
813        eval_stmt: &EvalStmt,
814    ) -> Result<Obj, RuntimeError> {
815        let fn_name = fn_obj_to_evaluate.head.to_string();
816        let mut flattened_number_args: Vec<Obj> = Vec::new();
817        for arg_group in fn_obj_to_evaluate.body.iter() {
818            for arg in arg_group.iter() {
819                let evaluated_arg_obj =
820                    self.evaluate_symbol_obj_iterative((**arg).clone(), eval_stmt)?;
821                match evaluated_arg_obj {
822                    Obj::Number(number) => {
823                        flattened_number_args.push(number.into());
824                    }
825                    _ => {
826                        return Err(short_exec_error(
827                            eval_stmt.clone().into(),
828                            "eval: function arguments must evaluate to Number".to_string(),
829                            None,
830                            vec![],
831                        ));
832                    }
833                }
834            }
835        }
836
837        let algo_definition = match self.get_algo_definition_by_name(&fn_name) {
838            Some(definition) => definition.clone(),
839            None => {
840                return Err(short_exec_error(
841                    eval_stmt.clone().into(),
842                    format!("eval: algorithm `{}` is not defined", fn_name),
843                    None,
844                    vec![],
845                ));
846            }
847        };
848
849        if flattened_number_args.len() != algo_definition.params.len() {
850            return Err(short_exec_error(
851                eval_stmt.clone().into(),
852                format!(
853                    "eval: argument count mismatch (expected {}, got {})",
854                    algo_definition.params.len(),
855                    flattened_number_args.len()
856                ),
857                None,
858                vec![],
859            ));
860        }
861
862        let mut param_to_arg_map: HashMap<String, Obj> = HashMap::new();
863        for (param_name, arg_obj) in algo_definition
864            .params
865            .iter()
866            .zip(flattened_number_args.iter())
867        {
868            param_to_arg_map.insert(param_name.clone(), arg_obj.clone());
869        }
870
871        for algo_case in algo_definition.cases.iter() {
872            let instantiated_case_condition = self.inst_atomic_fact(
873                &algo_case.condition,
874                &param_to_arg_map,
875                ParamObjType::DefAlgo,
876                None,
877            )?;
878            let verify_result = self
879                .verify_atomic_fact(&instantiated_case_condition, &VerifyState::new(0, false))
880                .map_err(|verify_error| {
881                    short_exec_error(
882                        eval_stmt.clone().into(),
883                        "eval: failed to verify case condition".to_string(),
884                        Some(verify_error),
885                        vec![],
886                    )
887                })?;
888
889            if verify_result.is_true() {
890                return self.inst_obj(
891                    &algo_case.return_stmt.value,
892                    &param_to_arg_map,
893                    ParamObjType::DefAlgo,
894                );
895            }
896            if verify_result.is_unknown() {
897                let reversed_case_condition = instantiated_case_condition.make_reversed();
898                let verify_reversed_result = self
899                    .verify_atomic_fact(&reversed_case_condition, &VerifyState::new(0, false))
900                    .map_err(|verify_error| {
901                        short_exec_error(
902                            eval_stmt.clone().into(),
903                            "eval: failed to verify reversed case condition".to_string(),
904                            Some(verify_error),
905                            vec![],
906                        )
907                    })?;
908                if verify_reversed_result.is_unknown() {
909                    return Err(short_exec_error(
910                        eval_stmt.clone().into(),
911                        format!(
912                            "eval: case `{}` is unknown and its reverse is also unknown",
913                            instantiated_case_condition
914                        ),
915                        None,
916                        vec![],
917                    ));
918                }
919            }
920        }
921
922        if let Some(default_return_stmt) = &algo_definition.default_return {
923            self.inst_obj(
924                &default_return_stmt.value,
925                &param_to_arg_map,
926                ParamObjType::DefAlgo,
927            )
928        } else {
929            Err(short_exec_error(
930                eval_stmt.clone().into(),
931                "eval: no case matched and no default return".to_string(),
932                None,
933                vec![],
934            ))
935        }
936    }
937
938    pub fn exec_eval_stmt(&mut self, stmt: &EvalStmt) -> Result<StmtResult, RuntimeError> {
939        self.verify_obj_well_defined_and_store_cache(
940            &stmt.obj_to_eval,
941            &VerifyState::new(0, false),
942        )?;
943
944        let resolved_obj = self.resolve_obj(&stmt.obj_to_eval);
945        let eval_result = self.run_in_local_env(|rt| {
946            if !Self::object_supported_by_eval_stmt(&resolved_obj) {
947                return Err(short_exec_error(
948                    stmt.clone().into(),
949                    "eval: need a function call, numeric expression (+ - * / ^), sum/product over a unary anonymous body, or matrix ++ -- ** *. ^^ / matrix literal"
950                        .to_string(),
951                    None,
952                    vec![],
953                ));
954            }
955            rt.evaluate_symbol_obj_iterative(resolved_obj.clone(), stmt)
956        });
957
958        let evaluated_obj = eval_result?;
959        let evaluated_equal_fact = EqualFact::new(
960            stmt.obj_to_eval.clone(),
961            evaluated_obj,
962            stmt.line_file.clone(),
963        )
964        .into();
965
966        let mut infer_result = InferResult::new();
967        infer_result.new_fact(&evaluated_equal_fact);
968        self.verify_well_defined_and_store_and_infer_with_default_verify_state(
969            evaluated_equal_fact,
970        )?;
971
972        Ok((NonFactualStmtSuccess::new(stmt.clone().into(), infer_result, vec![])).into())
973    }
974}