Skip to main content

formualizer_eval/
interpreter.rs

1use crate::{
2    CellRef,
3    broadcast::{broadcast_shape, project_index},
4    coercion,
5    traits::{ArgumentHandle, DefaultFunctionContext, EvaluationContext},
6};
7use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
8use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
9
10use crate::engine::arena::{AstNodeData, AstNodeId, DataStore};
11use crate::engine::sheet_registry::SheetRegistry;
12
13// no Arc needed here after cache removal
14
15pub struct Interpreter<'a> {
16    pub context: &'a dyn EvaluationContext,
17    current_sheet: &'a str,
18    current_cell: Option<crate::CellRef>,
19}
20
21impl<'a> Interpreter<'a> {
22    pub fn new(context: &'a dyn EvaluationContext, current_sheet: &'a str) -> Self {
23        Self {
24            context,
25            current_sheet,
26            current_cell: None,
27        }
28    }
29
30    pub fn new_with_cell(
31        context: &'a dyn EvaluationContext,
32        current_sheet: &'a str,
33        cell: crate::CellRef,
34    ) -> Self {
35        Self {
36            context,
37            current_sheet,
38            current_cell: Some(cell),
39        }
40    }
41
42    pub fn current_sheet(&self) -> &'a str {
43        self.current_sheet
44    }
45
46    pub fn resolve_range_view<'c>(
47        &'c self,
48        reference: &ReferenceType,
49        current_sheet: &str,
50    ) -> Result<crate::engine::range_view::RangeView<'c>, ExcelError> {
51        self.context.resolve_range_view(reference, current_sheet)
52    }
53
54    /// Evaluate an AST node in a reference context and return a ReferenceType.
55    /// This is used for range combinators (e.g., ":"), by-ref argument flows,
56    /// and spill planning. Functions that can return references must set
57    /// `FnCaps::RETURNS_REFERENCE` and override `eval_reference`.
58    pub fn evaluate_ast_as_reference(&self, node: &ASTNode) -> Result<ReferenceType, ExcelError> {
59        match &node.node_type {
60            ASTNodeType::Reference { reference, .. } => Ok(reference.clone()),
61            ASTNodeType::Function { name, args } => {
62                if let Some(fun) = self.context.get_function("", name) {
63                    // Build handles; allow function to decide reference semantics
64                    let handles: Vec<ArgumentHandle> =
65                        args.iter().map(|n| ArgumentHandle::new(n, self)).collect();
66                    let fctx = DefaultFunctionContext::new_with_sheet(
67                        self.context,
68                        None,
69                        self.current_sheet,
70                    );
71                    if let Some(res) = fun.eval_reference(&handles, &fctx) {
72                        res
73                    } else {
74                        Err(ExcelError::new(ExcelErrorKind::Ref)
75                            .with_message("Function does not return a reference"))
76                    }
77                } else {
78                    Err(ExcelError::new(ExcelErrorKind::Name)
79                        .with_message(format!("Unknown function: {name}")))
80                }
81            }
82            ASTNodeType::BinaryOp { op, left, right } if op == ":" => {
83                let lref = self.evaluate_ast_as_reference(left)?;
84                let rref = self.evaluate_ast_as_reference(right)?;
85                crate::reference::combine_references(&lref, &rref)
86            }
87            ASTNodeType::Array(_)
88            | ASTNodeType::UnaryOp { .. }
89            | ASTNodeType::BinaryOp { .. }
90            | ASTNodeType::Literal(_) => Err(ExcelError::new(ExcelErrorKind::Ref)
91                .with_message("Expression cannot be used as a reference")),
92        }
93    }
94
95    pub(crate) fn evaluate_arena_ast_as_reference(
96        &self,
97        node_id: AstNodeId,
98        data_store: &DataStore,
99        sheet_registry: &SheetRegistry,
100    ) -> Result<ReferenceType, ExcelError> {
101        let node = data_store.get_node(node_id).ok_or_else(|| {
102            ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
103        })?;
104
105        match node {
106            AstNodeData::Reference { ref_type, .. } => {
107                Ok(data_store.reconstruct_reference_type_for_eval(ref_type, sheet_registry))
108            }
109            AstNodeData::Function { name_id, .. } => {
110                let name = data_store.resolve_ast_string(*name_id);
111                let fun = self.context.get_function("", name).ok_or_else(|| {
112                    ExcelError::new(ExcelErrorKind::Name)
113                        .with_message(format!("Unknown function: {name}"))
114                })?;
115
116                let args = data_store.get_args(node_id).ok_or_else(|| {
117                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing function args")
118                })?;
119
120                let handles: Vec<ArgumentHandle> = args
121                    .iter()
122                    .copied()
123                    .map(|arg_id| {
124                        ArgumentHandle::new_arena(arg_id, self, data_store, sheet_registry)
125                    })
126                    .collect();
127
128                let fctx =
129                    DefaultFunctionContext::new_with_sheet(self.context, None, self.current_sheet);
130
131                fun.eval_reference(&handles, &fctx).ok_or_else(|| {
132                    ExcelError::new(ExcelErrorKind::Ref)
133                        .with_message("Function does not return a reference")
134                })?
135            }
136            AstNodeData::BinaryOp {
137                op_id,
138                left_id,
139                right_id,
140            } => {
141                let op = data_store.resolve_ast_string(*op_id);
142                if op != ":" {
143                    return Err(ExcelError::new(ExcelErrorKind::Ref)
144                        .with_message("Expression cannot be used as a reference"));
145                }
146                let lref =
147                    self.evaluate_arena_ast_as_reference(*left_id, data_store, sheet_registry)?;
148                let rref =
149                    self.evaluate_arena_ast_as_reference(*right_id, data_store, sheet_registry)?;
150                crate::reference::combine_references(&lref, &rref)
151            }
152            _ => Err(ExcelError::new(ExcelErrorKind::Ref)
153                .with_message("Expression cannot be used as a reference")),
154        }
155    }
156
157    /* ===================  public  =================== */
158    pub fn evaluate_ast(&self, node: &ASTNode) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
159        self.evaluate_ast_uncached(node)
160    }
161
162    pub(crate) fn evaluate_arena_ast(
163        &self,
164        node_id: AstNodeId,
165        data_store: &DataStore,
166        sheet_registry: &SheetRegistry,
167    ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
168        let node = data_store.get_node(node_id).ok_or_else(|| {
169            ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
170        })?;
171
172        match node {
173            AstNodeData::Literal(vref) => Ok(crate::traits::CalcValue::Scalar(
174                data_store.retrieve_value(*vref),
175            )),
176            AstNodeData::Reference { ref_type, .. } => {
177                let reference =
178                    data_store.reconstruct_reference_type_for_eval(ref_type, sheet_registry);
179                self.eval_reference_to_calc(&reference)
180            }
181            AstNodeData::UnaryOp { op_id, expr_id } => {
182                let expr = self.evaluate_arena_ast(*expr_id, data_store, sheet_registry)?;
183
184                let op = data_store.resolve_ast_string(*op_id);
185                if op == "@" {
186                    // Prefer reference-aware implicit intersection so we don't depend on
187                    // RangeView absolute coordinates (important for lightweight test contexts).
188                    if let Some(AstNodeData::Reference { ref_type, .. }) =
189                        data_store.get_node(*expr_id)
190                    {
191                        let reference = data_store
192                            .reconstruct_reference_type_for_eval(ref_type, sheet_registry);
193                        let v = self.implicit_intersection_from_reference(&reference);
194                        return Ok(crate::traits::CalcValue::Scalar(v));
195                    }
196
197                    let v = self.eval_implicit_intersection_calc(expr);
198                    return Ok(crate::traits::CalcValue::Scalar(v));
199                }
200                // For now, materialize for operators. Future: virtual range ops.
201                let v = expr.into_literal();
202                match v {
203                    LiteralValue::Array(arr) => self
204                        .map_array(arr, |cell| self.eval_unary_scalar(op, cell))
205                        .map(crate::traits::CalcValue::Scalar),
206                    other => self
207                        .eval_unary_scalar(op, other)
208                        .map(crate::traits::CalcValue::Scalar),
209                }
210            }
211            AstNodeData::BinaryOp {
212                op_id,
213                left_id,
214                right_id,
215            } => {
216                let op = data_store.resolve_ast_string(*op_id);
217                if op == ":" {
218                    let lref =
219                        self.evaluate_arena_ast_as_reference(*left_id, data_store, sheet_registry)?;
220                    let rref = self.evaluate_arena_ast_as_reference(
221                        *right_id,
222                        data_store,
223                        sheet_registry,
224                    )?;
225                    return match crate::reference::combine_references(&lref, &rref) {
226                        Ok(_r) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
227                            ExcelError::new(ExcelErrorKind::Ref).with_message(
228                                "Reference produced by ':' cannot be used directly as a value",
229                            ),
230                        ))),
231                        Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
232                    };
233                }
234
235                let left = self
236                    .evaluate_arena_ast(*left_id, data_store, sheet_registry)?
237                    .into_literal();
238                let right = self
239                    .evaluate_arena_ast(*right_id, data_store, sheet_registry)?
240                    .into_literal();
241
242                if matches!(op, "=" | "<>" | ">" | "<" | ">=" | "<=") {
243                    return self
244                        .compare(op, left, right)
245                        .map(crate::traits::CalcValue::Scalar);
246                }
247
248                match op {
249                    "+" => self
250                        .add_sub_date_aware('+', left, right)
251                        .map(crate::traits::CalcValue::Scalar),
252                    "-" => self
253                        .add_sub_date_aware('-', left, right)
254                        .map(crate::traits::CalcValue::Scalar),
255                    "*" => self
256                        .numeric_binary(left, right, |a, b| a * b)
257                        .map(crate::traits::CalcValue::Scalar),
258                    "/" => self
259                        .divide(left, right)
260                        .map(crate::traits::CalcValue::Scalar),
261                    "^" => self
262                        .power(left, right)
263                        .map(crate::traits::CalcValue::Scalar),
264                    "&" => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
265                        format!(
266                            "{}{}",
267                            crate::coercion::to_text_invariant(&left),
268                            crate::coercion::to_text_invariant(&right)
269                        ),
270                    ))),
271                    _ => Err(ExcelError::new(ExcelErrorKind::NImpl)
272                        .with_message(format!("Binary op '{op}'"))),
273                }
274            }
275            AstNodeData::Array { .. } => {
276                let (rows, cols, elements) =
277                    data_store.get_array_elems(node_id).ok_or_else(|| {
278                        ExcelError::new(ExcelErrorKind::Value).with_message("Invalid array")
279                    })?;
280
281                let rows_usize = rows as usize;
282                let cols_usize = cols as usize;
283                let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows_usize);
284                for r in 0..rows_usize {
285                    let mut row = Vec::with_capacity(cols_usize);
286                    for c in 0..cols_usize {
287                        let idx = r * cols_usize + c;
288                        if let Some(&elem_id) = elements.get(idx) {
289                            row.push(
290                                self.evaluate_arena_ast(elem_id, data_store, sheet_registry)?
291                                    .into_literal(),
292                            );
293                        }
294                    }
295                    out.push(row);
296                }
297
298                Ok(crate::traits::CalcValue::Range(
299                    crate::engine::range_view::RangeView::from_owned_rows(
300                        out,
301                        self.context.date_system(),
302                    ),
303                ))
304            }
305            AstNodeData::Function { name_id, .. } => {
306                let name = data_store.resolve_ast_string(*name_id);
307                let fun = self.context.get_function("", name).ok_or_else(|| {
308                    ExcelError::new(ExcelErrorKind::Name)
309                        .with_message(format!("Unknown function: {name}"))
310                })?;
311
312                let args = data_store.get_args(node_id).ok_or_else(|| {
313                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing function args")
314                })?;
315
316                let handles: Vec<ArgumentHandle> = args
317                    .iter()
318                    .copied()
319                    .map(|arg_id| {
320                        ArgumentHandle::new_arena(arg_id, self, data_store, sheet_registry)
321                    })
322                    .collect();
323
324                let fctx = DefaultFunctionContext::new_with_sheet(
325                    self.context,
326                    self.current_cell,
327                    self.current_sheet,
328                );
329
330                fun.dispatch(&handles, &fctx)
331            }
332        }
333    }
334
335    fn evaluate_ast_uncached(
336        &self,
337        node: &ASTNode,
338    ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
339        // Plan-aware evaluation: build a plan for this node and execute accordingly.
340        // Provide the planner with a lightweight range-dimension probe and function lookup
341        // so it can select chunked reduction and arg-parallel strategies where appropriate.
342        let current_sheet = self.current_sheet.to_string();
343        let range_probe = |reference: &ReferenceType| -> Option<(u32, u32)> {
344            // Mirror Engine::resolve_range_storage bound normalization without materialising
345            use formualizer_parse::parser::ReferenceType as RT;
346            match reference {
347                RT::Range {
348                    sheet,
349                    start_row,
350                    start_col,
351                    end_row,
352                    end_col,
353                    ..
354                } => {
355                    let sheet_name = sheet.as_deref().unwrap_or(&current_sheet);
356                    // Start with provided values, fill None from used-region or sheet bounds.
357                    let mut sr = *start_row;
358                    let mut sc = *start_col;
359                    let mut er = *end_row;
360                    let mut ec = *end_col;
361
362                    // Column-only: rows are None on both ends
363                    if sr.is_none() && er.is_none() {
364                        // Full-column reference: anchor at row 1 for alignment across columns
365                        let scv = sc.unwrap_or(1);
366                        let ecv = ec.unwrap_or(scv);
367                        sr = Some(1);
368                        if let Some((_, max_r)) =
369                            self.context.used_rows_for_columns(sheet_name, scv, ecv)
370                        {
371                            er = Some(max_r);
372                        } else if let Some((max_rows, _)) = self.context.sheet_bounds(sheet_name) {
373                            er = Some(max_rows);
374                        }
375                    }
376
377                    // Row-only: cols are None on both ends
378                    if sc.is_none() && ec.is_none() {
379                        // Full-row reference: anchor at column 1 for alignment across rows
380                        let srv = sr.unwrap_or(1);
381                        let erv = er.unwrap_or(srv);
382                        sc = Some(1);
383                        if let Some((_, max_c)) =
384                            self.context.used_cols_for_rows(sheet_name, srv, erv)
385                        {
386                            ec = Some(max_c);
387                        } else if let Some((_, max_cols)) = self.context.sheet_bounds(sheet_name) {
388                            ec = Some(max_cols);
389                        }
390                    }
391
392                    // Partially bounded (e.g., A1:A or A:A10)
393                    if sr.is_some() && er.is_none() {
394                        let scv = sc.unwrap_or(1);
395                        let ecv = ec.unwrap_or(scv);
396                        if let Some((_, max_r)) =
397                            self.context.used_rows_for_columns(sheet_name, scv, ecv)
398                        {
399                            er = Some(max_r);
400                        } else if let Some((max_rows, _)) = self.context.sheet_bounds(sheet_name) {
401                            er = Some(max_rows);
402                        }
403                    }
404                    if er.is_some() && sr.is_none() {
405                        // Open start: anchor at row 1
406                        sr = Some(1);
407                    }
408                    if sc.is_some() && ec.is_none() {
409                        let srv = sr.unwrap_or(1);
410                        let erv = er.unwrap_or(srv);
411                        if let Some((_, max_c)) =
412                            self.context.used_cols_for_rows(sheet_name, srv, erv)
413                        {
414                            ec = Some(max_c);
415                        } else if let Some((_, max_cols)) = self.context.sheet_bounds(sheet_name) {
416                            ec = Some(max_cols);
417                        }
418                    }
419                    if ec.is_some() && sc.is_none() {
420                        // Open start: anchor at column 1
421                        sc = Some(1);
422                    }
423
424                    let sr = sr.unwrap_or(1);
425                    let sc = sc.unwrap_or(1);
426                    let er = er.unwrap_or(sr.saturating_sub(1));
427                    let ec = ec.unwrap_or(sc.saturating_sub(1));
428                    if er < sr || ec < sc {
429                        return Some((0, 0));
430                    }
431                    Some((er.saturating_sub(sr) + 1, ec.saturating_sub(sc) + 1))
432                }
433                RT::Cell { .. } => Some((1, 1)),
434                _ => None,
435            }
436        };
437        let fn_lookup = |ns: &str, name: &str| self.context.get_function(ns, name);
438
439        let mut planner = crate::planner::Planner::new(crate::planner::PlanConfig::default())
440            .with_range_probe(&range_probe)
441            .with_function_lookup(&fn_lookup);
442        let plan = planner.plan(node);
443        self.eval_with_plan(node, &plan.root)
444    }
445
446    fn eval_with_plan(
447        &self,
448        node: &ASTNode,
449        plan_node: &crate::planner::PlanNode,
450    ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
451        match &node.node_type {
452            ASTNodeType::Literal(v) => Ok(crate::traits::CalcValue::Scalar(v.clone())),
453            ASTNodeType::Reference { reference, .. } => self.eval_reference_to_calc(reference),
454            ASTNodeType::UnaryOp { op, expr } => {
455                // For now, reuse existing unary implementation (which recurses).
456                // In a later phase, we can map plan_node.children[0].
457                self.eval_unary(op, expr)
458                    .map(crate::traits::CalcValue::Scalar)
459            }
460            ASTNodeType::BinaryOp { op, left, right } => self
461                .eval_binary(op, left, right)
462                .map(crate::traits::CalcValue::Scalar),
463            ASTNodeType::Function { name, args } => {
464                let strategy = plan_node.strategy;
465                if let Some(fun) = self.context.get_function("", name) {
466                    use crate::function::FnCaps;
467                    use crate::planner::ExecStrategy;
468                    let caps = fun.caps();
469
470                    // Short-circuit or volatile: always sequential
471                    if caps.contains(FnCaps::SHORT_CIRCUIT) || caps.contains(FnCaps::VOLATILE) {
472                        return self.eval_function_to_calc(name, args);
473                    }
474
475                    // Windowed/chunked strategies are handled by the unified `eval()` path.
476
477                    // Arg-parallel: prewarm subexpressions and then dispatch
478                    if matches!(strategy, ExecStrategy::ArgParallel)
479                        && caps.contains(FnCaps::PARALLEL_ARGS)
480                    {
481                        // Sequential prewarm of subexpressions (safe without Sync bounds)
482                        for arg in args {
483                            match &arg.node_type {
484                                ASTNodeType::Reference { reference, .. } => {
485                                    let _ = self
486                                        .context
487                                        .resolve_range_view(reference, self.current_sheet);
488                                }
489                                _ => {
490                                    let _ = self.evaluate_ast(arg);
491                                }
492                            }
493                        }
494                        return self.eval_function_to_calc(name, args);
495                    }
496
497                    // Default path
498                    return self.eval_function_to_calc(name, args);
499                }
500                self.eval_function_to_calc(name, args)
501            }
502            ASTNodeType::Array(rows) => self.eval_array_literal_to_calc(rows),
503        }
504    }
505
506    /* ===================  reference  =================== */
507    fn eval_reference_to_calc(
508        &self,
509        reference: &ReferenceType,
510    ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
511        let view = self
512            .context
513            .resolve_range_view(reference, self.current_sheet)?
514            .with_cancel_token(self.context.cancellation_token());
515
516        match reference {
517            ReferenceType::Cell { .. } => {
518                // For a single cell reference, just return the value.
519                Ok(crate::traits::CalcValue::Scalar(
520                    view.as_1x1().unwrap_or(LiteralValue::Empty),
521                ))
522            }
523            _ => Ok(crate::traits::CalcValue::Range(view)),
524        }
525    }
526
527    fn eval_reference(&self, reference: &ReferenceType) -> Result<LiteralValue, ExcelError> {
528        self.eval_reference_to_calc(reference)
529            .map(|cv| cv.into_literal())
530    }
531
532    /* ===================  unary ops  =================== */
533    fn eval_unary(&self, op: &str, expr: &ASTNode) -> Result<LiteralValue, ExcelError> {
534        if op == "@" {
535            if let ASTNodeType::Reference { reference, .. } = &expr.node_type {
536                return Ok(self.implicit_intersection_from_reference(reference));
537            }
538
539            let cv = self.evaluate_ast(expr)?;
540            return Ok(self.eval_implicit_intersection_calc(cv));
541        }
542
543        let v = self.evaluate_ast(expr)?.into_literal();
544        match v {
545            LiteralValue::Array(arr) => {
546                self.map_array(arr, |cell| self.eval_unary_scalar(op, cell))
547            }
548            other => self.eval_unary_scalar(op, other),
549        }
550    }
551
552    fn eval_unary_scalar(&self, op: &str, v: LiteralValue) -> Result<LiteralValue, ExcelError> {
553        match op {
554            "+" => self.apply_number_unary(v, |n| n),
555            "-" => self.apply_number_unary(v, |n| -n),
556            "%" => self.apply_number_unary(v, |n| n / 100.0),
557            _ => {
558                Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!("Unary op '{op}'")))
559            }
560        }
561    }
562
563    fn eval_implicit_intersection_calc(&self, cv: crate::traits::CalcValue<'a>) -> LiteralValue {
564        let (cur_r0, cur_c0) = match self.current_cell {
565            Some(cell) => (cell.coord.row() as usize, cell.coord.col() as usize),
566            None => (0usize, 0usize),
567        };
568
569        match cv {
570            crate::traits::CalcValue::Scalar(v) => match v {
571                LiteralValue::Array(arr) => {
572                    if arr.is_empty() || arr.first().map(|r| r.is_empty()).unwrap_or(true) {
573                        return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
574                    }
575                    arr[0][0].clone()
576                }
577                other => other,
578            },
579            crate::traits::CalcValue::Range(rv) => {
580                if rv.is_empty() {
581                    return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
582                }
583
584                // Array results (array literals and many dynamic-array functions) are materialized
585                // into an owned RangeView with a temporary backing sheet ("__tmp").
586                // For explicit @, interpret these as anchored at the formula cell and select the
587                // top-left element.
588                if rv.sheet_name() == "__tmp" {
589                    return rv.get_cell(0, 0);
590                }
591
592                if let Some(v) = rv.as_1x1() {
593                    return v;
594                }
595
596                let (rows, cols) = rv.dims();
597                let sr = rv.start_row();
598                let sc = rv.start_col();
599                let er = rv.end_row();
600                let ec = rv.end_col();
601
602                // Excel-compatible implicit intersection (simplified):
603                // - Nx1: pick by row
604                // - 1xM: pick by column
605                // - NxM: pick by (row,col)
606                if cols == 1 {
607                    if cur_r0 < sr || cur_r0 > er {
608                        return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
609                    }
610                    let rel_r = cur_r0 - sr;
611                    return rv.get_cell(rel_r, 0);
612                }
613
614                if rows == 1 {
615                    if cur_c0 < sc || cur_c0 > ec {
616                        return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
617                    }
618                    let rel_c = cur_c0 - sc;
619                    return rv.get_cell(0, rel_c);
620                }
621
622                if cur_r0 < sr || cur_r0 > er || cur_c0 < sc || cur_c0 > ec {
623                    return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
624                }
625                let rel_r = cur_r0 - sr;
626                let rel_c = cur_c0 - sc;
627                rv.get_cell(rel_r, rel_c)
628            }
629        }
630    }
631
632    fn implicit_intersection_from_reference(&self, reference: &ReferenceType) -> LiteralValue {
633        let (cur_r1, cur_c1) = match self.current_cell {
634            Some(cell) => (
635                cell.coord.row().saturating_add(1),
636                cell.coord.col().saturating_add(1),
637            ),
638            None => (1u32, 1u32),
639        };
640
641        match reference {
642            ReferenceType::Cell {
643                sheet, row, col, ..
644            } => {
645                let sheet_name = sheet.as_deref().unwrap_or(self.current_sheet);
646                match self
647                    .context
648                    .resolve_cell_reference(Some(sheet_name), *row, *col)
649                {
650                    Ok(v) => v,
651                    Err(e) => LiteralValue::Error(e),
652                }
653            }
654            ReferenceType::Range {
655                sheet,
656                start_row,
657                start_col,
658                end_row,
659                end_col,
660                ..
661            } => {
662                let sheet_name = sheet.as_deref().unwrap_or(self.current_sheet);
663
664                let (sr, sc, er, ec) = match (start_row, start_col, end_row, end_col) {
665                    (Some(sr), Some(sc), Some(er), Some(ec)) => (*sr, *sc, *er, *ec),
666                    _ => {
667                        // For open-ended/infinite ranges, fall back to the RangeView-based path.
668                        // This path may be less precise in minimal test contexts.
669                        let cv = match self.eval_reference_to_calc(reference) {
670                            Ok(cv) => cv,
671                            Err(e) => return LiteralValue::Error(e),
672                        };
673                        return self.eval_implicit_intersection_calc(cv);
674                    }
675                };
676
677                // Normalize bounds (A10:A1 is legal syntax; treat as swapped).
678                let (mut sr, mut er) = (sr, er);
679                let (mut sc, mut ec) = (sc, ec);
680                if sr > er {
681                    std::mem::swap(&mut sr, &mut er);
682                }
683                if sc > ec {
684                    std::mem::swap(&mut sc, &mut ec);
685                }
686
687                let pick = if sc == ec {
688                    // Column vector: intersect by row
689                    if cur_r1 < sr || cur_r1 > er {
690                        return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
691                    }
692                    (cur_r1, sc)
693                } else if sr == er {
694                    // Row vector: intersect by column
695                    if cur_c1 < sc || cur_c1 > ec {
696                        return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
697                    }
698                    (sr, cur_c1)
699                } else {
700                    // 2D: require both axes
701                    if cur_r1 < sr || cur_r1 > er || cur_c1 < sc || cur_c1 > ec {
702                        return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
703                    }
704                    (cur_r1, cur_c1)
705                };
706
707                match self
708                    .context
709                    .resolve_cell_reference(Some(sheet_name), pick.0, pick.1)
710                {
711                    Ok(v) => v,
712                    Err(e) => LiteralValue::Error(e),
713                }
714            }
715            // Named ranges / tables / external: fall back to materializing and intersecting.
716            other => {
717                let cv = match self.eval_reference_to_calc(other) {
718                    Ok(cv) => cv,
719                    Err(e) => return LiteralValue::Error(e),
720                };
721                self.eval_implicit_intersection_calc(cv)
722            }
723        }
724    }
725
726    fn apply_number_unary<F>(&self, v: LiteralValue, f: F) -> Result<LiteralValue, ExcelError>
727    where
728        F: Fn(f64) -> f64,
729    {
730        match crate::coercion::to_number_lenient_with_locale(&v, &self.context.locale()) {
731            Ok(n) => match crate::coercion::sanitize_numeric(f(n)) {
732                Ok(n2) => Ok(LiteralValue::Number(n2)),
733                Err(e) => Ok(LiteralValue::Error(e)),
734            },
735            Err(e) => Ok(LiteralValue::Error(e)),
736        }
737    }
738
739    /* ===================  binary ops  =================== */
740    fn eval_binary(
741        &self,
742        op: &str,
743        left: &ASTNode,
744        right: &ASTNode,
745    ) -> Result<LiteralValue, ExcelError> {
746        // Comparisons use dedicated path.
747        if matches!(op, "=" | "<>" | ">" | "<" | ">=" | "<=") {
748            let l = self.evaluate_ast(left)?.into_literal();
749            let r = self.evaluate_ast(right)?.into_literal();
750            return self.compare(op, l, r);
751        }
752
753        let l_val = self.evaluate_ast(left)?.into_literal();
754        let r_val = self.evaluate_ast(right)?.into_literal();
755
756        match op {
757            "+" => self.add_sub_date_aware('+', l_val, r_val),
758            "-" => self.add_sub_date_aware('-', l_val, r_val),
759            "*" => self.numeric_binary(l_val, r_val, |a, b| a * b),
760            "/" => self.divide(l_val, r_val),
761            "^" => self.power(l_val, r_val),
762            "&" => Ok(LiteralValue::Text(format!(
763                "{}{}",
764                crate::coercion::to_text_invariant(&l_val),
765                crate::coercion::to_text_invariant(&r_val)
766            ))),
767            ":" => {
768                // Compute a combined reference; in value context return #REF! for now.
769                let lref = self.evaluate_ast_as_reference(left)?;
770                let rref = self.evaluate_ast_as_reference(right)?;
771                match crate::reference::combine_references(&lref, &rref) {
772                    Ok(_r) => Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
773                        "Reference produced by ':' cannot be used directly as a value",
774                    )),
775                    Err(e) => Ok(LiteralValue::Error(e)),
776                }
777            }
778            _ => {
779                Err(ExcelError::new(ExcelErrorKind::NImpl)
780                    .with_message(format!("Binary op '{op}'")))
781            }
782        }
783    }
784
785    fn add_sub_date_aware(
786        &self,
787        op: char,
788        left: LiteralValue,
789        right: LiteralValue,
790    ) -> Result<LiteralValue, ExcelError> {
791        debug_assert!(op == '+' || op == '-');
792
793        self.broadcast_apply(left, right, |l, r| {
794            use LiteralValue::*;
795
796            let date_system = self.context.date_system();
797
798            let date_like_serial = |v: &LiteralValue| -> Option<f64> {
799                match v {
800                    Date(d) => Some(crate::builtins::datetime::date_to_serial_for(
801                        date_system,
802                        d,
803                    )),
804                    DateTime(dt) => Some(crate::builtins::datetime::datetime_to_serial_for(
805                        date_system,
806                        dt,
807                    )),
808                    _ => None,
809                }
810            };
811
812            let to_num = |v: &LiteralValue| -> Result<f64, ExcelError> {
813                crate::coercion::to_number_lenient_with_locale(v, &self.context.locale())
814            };
815
816            let serial_to_literal = |serial: f64| -> LiteralValue {
817                match crate::coercion::sanitize_numeric(serial) {
818                    Ok(serial) => {
819                        match crate::builtins::datetime::serial_to_datetime_for(date_system, serial)
820                        {
821                            Ok(dt) => {
822                                if dt.time() == chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap() {
823                                    Date(dt.date())
824                                } else {
825                                    DateTime(dt)
826                                }
827                            }
828                            Err(e) => Error(e),
829                        }
830                    }
831                    Err(e) => Error(e),
832                }
833            };
834
835            // Date +/- number => date (propagate temporal tag)
836            if let Some(ls) = date_like_serial(&l) {
837                match op {
838                    '+' => {
839                        let rn = to_num(&r)?;
840                        return Ok(serial_to_literal(ls + rn));
841                    }
842                    '-' => {
843                        // Date - Date => numeric day delta (Excel-compatible)
844                        if let Some(rs) = date_like_serial(&r) {
845                            return Ok(Number(ls - rs));
846                        }
847                        let rn = to_num(&r)?;
848                        return Ok(serial_to_literal(ls - rn));
849                    }
850                    _ => unreachable!(),
851                }
852            }
853
854            // Number + Date => date (commutative)
855            if op == '+'
856                && let Some(rs) = date_like_serial(&r)
857            {
858                let ln = to_num(&l)?;
859                return Ok(serial_to_literal(ln + rs));
860            }
861
862            // Fallback: regular numeric operation
863            self.numeric_binary(l, r, |a, b| if op == '+' { a + b } else { a - b })
864        })
865    }
866
867    /* ===================  function calls  =================== */
868    fn eval_function_to_calc(
869        &self,
870        name: &str,
871        args: &[ASTNode],
872    ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
873        if let Some(fun) = self.context.get_function("", name) {
874            let handles: Vec<ArgumentHandle> =
875                args.iter().map(|n| ArgumentHandle::new(n, self)).collect();
876            // Use the function's built-in dispatch method with a narrow FunctionContext
877            let fctx = DefaultFunctionContext::new_with_sheet(
878                self.context,
879                self.current_cell,
880                self.current_sheet,
881            );
882            fun.dispatch(&handles, &fctx)
883        } else {
884            // Include the function name in the error message for better debugging
885            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
886                ExcelError::new(ExcelErrorKind::Name)
887                    .with_message(format!("Unknown function: {name}")),
888            )))
889        }
890    }
891
892    fn eval_function(&self, name: &str, args: &[ASTNode]) -> Result<LiteralValue, ExcelError> {
893        self.eval_function_to_calc(name, args)
894            .map(|cv| cv.into_literal())
895    }
896
897    pub fn function_context(&self, cell_ref: Option<&CellRef>) -> DefaultFunctionContext<'_> {
898        DefaultFunctionContext::new_with_sheet(self.context, cell_ref.cloned(), self.current_sheet)
899    }
900
901    /* ===================  array literal  =================== */
902    fn eval_array_literal_to_calc(
903        &self,
904        rows: &[Vec<ASTNode>],
905    ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
906        let mut out = Vec::with_capacity(rows.len());
907        for row in rows {
908            let mut r = Vec::with_capacity(row.len());
909            for cell in row {
910                r.push(self.evaluate_ast(cell)?.into_literal());
911            }
912            out.push(r);
913        }
914        Ok(crate::traits::CalcValue::Range(
915            crate::engine::range_view::RangeView::from_owned_rows(out, self.context.date_system()),
916        ))
917    }
918
919    fn eval_array_literal(&self, rows: &[Vec<ASTNode>]) -> Result<LiteralValue, ExcelError> {
920        self.eval_array_literal_to_calc(rows)
921            .map(|cv| cv.into_literal())
922    }
923
924    /* ===================  helpers  =================== */
925    fn numeric_binary<F>(
926        &self,
927        left: LiteralValue,
928        right: LiteralValue,
929        f: F,
930    ) -> Result<LiteralValue, ExcelError>
931    where
932        F: Fn(f64, f64) -> f64 + Copy,
933    {
934        self.broadcast_apply(left, right, |l, r| {
935            let a = crate::coercion::to_number_lenient_with_locale(&l, &self.context.locale());
936            let b = crate::coercion::to_number_lenient_with_locale(&r, &self.context.locale());
937            match (a, b) {
938                (Ok(a), Ok(b)) => match crate::coercion::sanitize_numeric(f(a, b)) {
939                    Ok(n2) => Ok(LiteralValue::Number(n2)),
940                    Err(e) => Ok(LiteralValue::Error(e)),
941                },
942                (Err(e), _) | (_, Err(e)) => Ok(LiteralValue::Error(e)),
943            }
944        })
945    }
946
947    fn divide(&self, left: LiteralValue, right: LiteralValue) -> Result<LiteralValue, ExcelError> {
948        self.broadcast_apply(left, right, |l, r| {
949            let ln = crate::coercion::to_number_lenient_with_locale(&l, &self.context.locale());
950            let rn = crate::coercion::to_number_lenient_with_locale(&r, &self.context.locale());
951            let (a, b) = match (ln, rn) {
952                (Ok(a), Ok(b)) => (a, b),
953                (Err(e), _) | (_, Err(e)) => return Ok(LiteralValue::Error(e)),
954            };
955            if b == 0.0 {
956                return Ok(LiteralValue::Error(ExcelError::from_error_string(
957                    "#DIV/0!",
958                )));
959            }
960            match crate::coercion::sanitize_numeric(a / b) {
961                Ok(n) => Ok(LiteralValue::Number(n)),
962                Err(e) => Ok(LiteralValue::Error(e)),
963            }
964        })
965    }
966
967    fn power(&self, left: LiteralValue, right: LiteralValue) -> Result<LiteralValue, ExcelError> {
968        self.broadcast_apply(left, right, |l, r| {
969            let ln = crate::coercion::to_number_lenient_with_locale(&l, &self.context.locale());
970            let rn = crate::coercion::to_number_lenient_with_locale(&r, &self.context.locale());
971            let (a, b) = match (ln, rn) {
972                (Ok(a), Ok(b)) => (a, b),
973                (Err(e), _) | (_, Err(e)) => return Ok(LiteralValue::Error(e)),
974            };
975            // Excel domain: negative base with non-integer exponent -> #NUM!
976            if a < 0.0 && b.fract() != 0.0 {
977                return Ok(LiteralValue::Error(ExcelError::new_num()));
978            }
979            match crate::coercion::sanitize_numeric(a.powf(b)) {
980                Ok(n) => Ok(LiteralValue::Number(n)),
981                Err(e) => Ok(LiteralValue::Error(e)),
982            }
983        })
984    }
985
986    fn map_array<F>(&self, arr: Vec<Vec<LiteralValue>>, f: F) -> Result<LiteralValue, ExcelError>
987    where
988        F: Fn(LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
989    {
990        let mut out = Vec::with_capacity(arr.len());
991        for row in arr {
992            let mut new_row = Vec::with_capacity(row.len());
993            for cell in row {
994                new_row.push(match f(cell) {
995                    Ok(v) => v,
996                    Err(e) => LiteralValue::Error(e),
997                });
998            }
999            out.push(new_row);
1000        }
1001        Ok(LiteralValue::Array(out))
1002    }
1003
1004    fn combine_arrays<F>(
1005        &self,
1006        l: Vec<Vec<LiteralValue>>,
1007        r: Vec<Vec<LiteralValue>>,
1008        f: F,
1009    ) -> Result<LiteralValue, ExcelError>
1010    where
1011        F: Fn(LiteralValue, LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
1012    {
1013        // Use strict broadcasting across dimensions
1014        let l_shape = (l.len(), l.first().map(|r| r.len()).unwrap_or(0));
1015        let r_shape = (r.len(), r.first().map(|r| r.len()).unwrap_or(0));
1016        let target = match broadcast_shape(&[l_shape, r_shape]) {
1017            Ok(s) => s,
1018            Err(e) => return Ok(LiteralValue::Error(e)),
1019        };
1020
1021        let mut out = Vec::with_capacity(target.0);
1022        for i in 0..target.0 {
1023            let mut row = Vec::with_capacity(target.1);
1024            for j in 0..target.1 {
1025                let (li, lj) = project_index((i, j), l_shape);
1026                let (ri, rj) = project_index((i, j), r_shape);
1027                let lv = l
1028                    .get(li)
1029                    .and_then(|r| r.get(lj))
1030                    .cloned()
1031                    .unwrap_or(LiteralValue::Empty);
1032                let rv = r
1033                    .get(ri)
1034                    .and_then(|r| r.get(rj))
1035                    .cloned()
1036                    .unwrap_or(LiteralValue::Empty);
1037                row.push(match f(lv, rv) {
1038                    Ok(v) => v,
1039                    Err(e) => LiteralValue::Error(e),
1040                });
1041            }
1042            out.push(row);
1043        }
1044        Ok(LiteralValue::Array(out))
1045    }
1046
1047    fn broadcast_apply<F>(
1048        &self,
1049        left: LiteralValue,
1050        right: LiteralValue,
1051        f: F,
1052    ) -> Result<LiteralValue, ExcelError>
1053    where
1054        F: Fn(LiteralValue, LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
1055    {
1056        use LiteralValue::*;
1057        match (left, right) {
1058            (Array(l), Array(r)) => self.combine_arrays(l, r, f),
1059            (Array(arr), v) => {
1060                let shape_l = (arr.len(), arr.first().map(|r| r.len()).unwrap_or(0));
1061                let shape_r = (1usize, 1usize);
1062                let target = match broadcast_shape(&[shape_l, shape_r]) {
1063                    Ok(s) => s,
1064                    Err(e) => return Ok(LiteralValue::Error(e)),
1065                };
1066                let mut out = Vec::with_capacity(target.0);
1067                for i in 0..target.0 {
1068                    let mut row = Vec::with_capacity(target.1);
1069                    for j in 0..target.1 {
1070                        let (li, lj) = project_index((i, j), shape_l);
1071                        let lv = arr
1072                            .get(li)
1073                            .and_then(|r| r.get(lj))
1074                            .cloned()
1075                            .unwrap_or(LiteralValue::Empty);
1076                        row.push(match f(lv, v.clone()) {
1077                            Ok(vv) => vv,
1078                            Err(e) => LiteralValue::Error(e),
1079                        });
1080                    }
1081                    out.push(row);
1082                }
1083                Ok(LiteralValue::Array(out))
1084            }
1085            (v, Array(arr)) => {
1086                let shape_l = (1usize, 1usize);
1087                let shape_r = (arr.len(), arr.first().map(|r| r.len()).unwrap_or(0));
1088                let target = match broadcast_shape(&[shape_l, shape_r]) {
1089                    Ok(s) => s,
1090                    Err(e) => return Ok(LiteralValue::Error(e)),
1091                };
1092                let mut out = Vec::with_capacity(target.0);
1093                for i in 0..target.0 {
1094                    let mut row = Vec::with_capacity(target.1);
1095                    for j in 0..target.1 {
1096                        let (ri, rj) = project_index((i, j), shape_r);
1097                        let rv = arr
1098                            .get(ri)
1099                            .and_then(|r| r.get(rj))
1100                            .cloned()
1101                            .unwrap_or(LiteralValue::Empty);
1102                        row.push(match f(v.clone(), rv) {
1103                            Ok(vv) => vv,
1104                            Err(e) => LiteralValue::Error(e),
1105                        });
1106                    }
1107                    out.push(row);
1108                }
1109                Ok(LiteralValue::Array(out))
1110            }
1111            (l, r) => f(l, r),
1112        }
1113    }
1114
1115    /* ---------- coercion helpers ---------- */
1116    fn coerce_number(&self, v: &LiteralValue) -> Result<f64, ExcelError> {
1117        coercion::to_number_lenient(v)
1118    }
1119
1120    fn coerce_text(&self, v: &LiteralValue) -> String {
1121        coercion::to_text_invariant(v)
1122    }
1123
1124    /* ---------- comparison ---------- */
1125    fn compare(
1126        &self,
1127        op: &str,
1128        left: LiteralValue,
1129        right: LiteralValue,
1130    ) -> Result<LiteralValue, ExcelError> {
1131        use LiteralValue::*;
1132        if matches!(left, Error(_)) {
1133            return Ok(left);
1134        }
1135        if matches!(right, Error(_)) {
1136            return Ok(right);
1137        }
1138
1139        // arrays: element‑wise with broadcasting
1140        match (left, right) {
1141            (Array(l), Array(r)) => self.combine_arrays(l, r, |a, b| self.compare(op, a, b)),
1142            (Array(arr), v) => self.broadcast_apply(Array(arr), v, |a, b| self.compare(op, a, b)),
1143            (v, Array(arr)) => self.broadcast_apply(v, Array(arr), |a, b| self.compare(op, a, b)),
1144            (l, r) => {
1145                let res = match (l, r) {
1146                    (Number(a), Number(b)) => self.cmp_f64(a, b, op),
1147                    (Int(a), Number(b)) => self.cmp_f64(a as f64, b, op),
1148                    (Number(a), Int(b)) => self.cmp_f64(a, b as f64, op),
1149                    (Boolean(a), Boolean(b)) => {
1150                        self.cmp_f64(if a { 1.0 } else { 0.0 }, if b { 1.0 } else { 0.0 }, op)
1151                    }
1152                    (Text(a), Text(b)) => self.cmp_text(&a, &b, op),
1153                    (a, b) => {
1154                        // fallback to numeric coercion or text compare
1155                        let an = crate::coercion::to_number_lenient_with_locale(
1156                            &a,
1157                            &self.context.locale(),
1158                        )
1159                        .ok();
1160                        let bn = crate::coercion::to_number_lenient_with_locale(
1161                            &b,
1162                            &self.context.locale(),
1163                        )
1164                        .ok();
1165                        if let (Some(a), Some(b)) = (an, bn) {
1166                            self.cmp_f64(a, b, op)
1167                        } else {
1168                            self.cmp_text(
1169                                &crate::coercion::to_text_invariant(&a),
1170                                &crate::coercion::to_text_invariant(&b),
1171                                op,
1172                            )
1173                        }
1174                    }
1175                };
1176                Ok(LiteralValue::Boolean(res))
1177            }
1178        }
1179    }
1180
1181    fn cmp_f64(&self, a: f64, b: f64, op: &str) -> bool {
1182        match op {
1183            "=" => a == b,
1184            "<>" => a != b,
1185            ">" => a > b,
1186            "<" => a < b,
1187            ">=" => a >= b,
1188            "<=" => a <= b,
1189            _ => unreachable!(),
1190        }
1191    }
1192    fn cmp_text(&self, a: &str, b: &str, op: &str) -> bool {
1193        let loc = self.context.locale();
1194        let (a, b) = (loc.fold_case_invariant(a), loc.fold_case_invariant(b));
1195        self.cmp_f64(
1196            a.cmp(&b) as i32 as f64,
1197            0.0,
1198            match op {
1199                "=" => "=",
1200                "<>" => "<>",
1201                ">" => ">",
1202                "<" => "<",
1203                ">=" => ">=",
1204                "<=" => "<=",
1205                _ => unreachable!(),
1206            },
1207        )
1208    }
1209}