Skip to main content

formualizer_eval/
traits.rs

1use crate::engine::lookup_index_cache::{LookupAxis, LookupIndex};
2use crate::engine::range_view::RangeView;
3use crate::engine::row_visibility::VisibilityMaskMode;
4pub use crate::function::Function;
5use crate::interpreter::Interpreter;
6use crate::reference::CellRef;
7use formualizer_common::{
8    LiteralValue,
9    error::{ExcelError, ExcelErrorKind},
10};
11use std::any::Any;
12use std::borrow::Cow;
13use std::fmt::Debug;
14use std::sync::Arc;
15
16use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType, TableSpecifier};
17
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct ReferenceInfo {
20    /// Excel-style 1-based index of the first sheet covered by the reference.
21    pub first_sheet_index: Option<usize>,
22    /// Number of sheets covered by the reference (`1` for ordinary references, `N` for 3D refs).
23    pub sheet_count: Option<usize>,
24    /// Top-left / first cell addressed by the reference, when it resolves to a concrete cell.
25    pub first_cell: Option<CellRef>,
26}
27
28/* ───────────────────────────── Range ───────────────────────────── */
29
30pub trait Range: Debug + Send + Sync {
31    fn get(&self, row: usize, col: usize) -> Result<LiteralValue, ExcelError>;
32    fn dimensions(&self) -> (usize, usize);
33
34    fn is_sparse(&self) -> bool {
35        false
36    }
37
38    // Handle infinite ranges (A:A, 1:1)
39    fn is_infinite(&self) -> bool {
40        false
41    }
42
43    fn materialise(&self) -> Cow<'_, [Vec<LiteralValue>]> {
44        Cow::Owned(
45            (0..self.dimensions().0)
46                .map(|r| {
47                    (0..self.dimensions().1)
48                        .map(|c| self.get(r, c).unwrap_or(LiteralValue::Empty))
49                        .collect()
50                })
51                .collect(),
52        )
53    }
54
55    fn iter_cells<'a>(&'a self) -> Box<dyn Iterator<Item = LiteralValue> + 'a> {
56        let (rows, cols) = self.dimensions();
57        Box::new((0..rows).flat_map(move |r| (0..cols).map(move |c| self.get(r, c).unwrap())))
58    }
59    fn iter_rows<'a>(&'a self) -> Box<dyn Iterator<Item = Vec<LiteralValue>> + 'a> {
60        let (rows, cols) = self.dimensions();
61        Box::new((0..rows).map(move |r| (0..cols).map(|c| self.get(r, c).unwrap()).collect()))
62    }
63
64    /* down-cast hook for SIMD back-ends */
65    fn as_any(&self) -> &dyn Any;
66}
67
68/* blanket dyn passthrough */
69impl Range for Box<dyn Range> {
70    fn get(&self, r: usize, c: usize) -> Result<LiteralValue, ExcelError> {
71        (**self).get(r, c)
72    }
73    fn dimensions(&self) -> (usize, usize) {
74        (**self).dimensions()
75    }
76    fn is_sparse(&self) -> bool {
77        (**self).is_sparse()
78    }
79    fn materialise(&self) -> Cow<'_, [Vec<LiteralValue>]> {
80        (**self).materialise()
81    }
82    fn iter_cells<'a>(&'a self) -> Box<dyn Iterator<Item = LiteralValue> + 'a> {
83        (**self).iter_cells()
84    }
85    fn iter_rows<'a>(&'a self) -> Box<dyn Iterator<Item = Vec<LiteralValue>> + 'a> {
86        (**self).iter_rows()
87    }
88    fn as_any(&self) -> &dyn Any {
89        (**self).as_any()
90    }
91}
92
93/* ────────────────────── ArgumentHandle helpers ───────────────────── */
94
95pub type CowValue<'a> = Cow<'a, LiteralValue>;
96
97pub trait CustomCallable: Send + Sync {
98    fn arity(&self) -> usize;
99
100    fn invoke<'ctx>(
101        &self,
102        interp: &Interpreter<'ctx>,
103        args: &[LiteralValue],
104    ) -> Result<CalcValue<'ctx>, ExcelError>;
105}
106
107#[derive(Clone)]
108pub enum CalcValue<'a> {
109    Scalar(LiteralValue),
110    Range(RangeView<'a>),
111    Callable(Arc<dyn CustomCallable>),
112}
113
114/// The result of resolving an argument where either a reference or a value is accepted.
115///
116/// Reference-shaped syntax is resolved without first evaluating it as a value.
117/// All other syntax is evaluated through [`ArgumentHandle::value`] and retains
118/// its `CalcValue` discriminant.
119#[derive(Clone)]
120pub(crate) enum ResolvedArgument<'a> {
121    Range(RangeView<'a>),
122    ReferenceError(ExcelError),
123    Value(CalcValue<'a>),
124}
125
126impl std::fmt::Debug for ResolvedArgument<'_> {
127    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128        match self {
129            Self::Range(view) => f.debug_tuple("Range").field(view).finish(),
130            Self::ReferenceError(error) => f.debug_tuple("ReferenceError").field(error).finish(),
131            Self::Value(value) => f.debug_tuple("Value").field(value).finish(),
132        }
133    }
134}
135
136impl<'a> std::fmt::Debug for CalcValue<'a> {
137    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138        match self {
139            CalcValue::Scalar(v) => f.debug_tuple("Scalar").field(v).finish(),
140            CalcValue::Range(rv) => {
141                let (r, c) = rv.dims();
142                f.debug_tuple("Range").field(&(r, c)).finish()
143            }
144            CalcValue::Callable(_) => f.write_str("Callable(<opaque>)"),
145        }
146    }
147}
148
149impl<'a> CalcValue<'a> {
150    pub fn into_literal(self) -> LiteralValue {
151        match self {
152            CalcValue::Scalar(s) => s,
153            CalcValue::Range(rv) => {
154                let (rows, cols) = rv.dims();
155                if rows == 1 && cols == 1 {
156                    rv.get_cell(0, 0)
157                } else {
158                    let mut data = Vec::with_capacity(rows);
159                    for row_idx in 0..rows {
160                        let mut row = Vec::with_capacity(cols);
161                        for col_idx in 0..cols {
162                            row.push(rv.get_cell(row_idx, col_idx));
163                        }
164                        data.push(row);
165                    }
166                    LiteralValue::Array(data)
167                }
168            }
169            CalcValue::Callable(_) => LiteralValue::Error(
170                ExcelError::new(ExcelErrorKind::Calc).with_message("LAMBDA value must be invoked"),
171            ),
172        }
173    }
174
175    pub fn as_scalar(&self) -> Option<&LiteralValue> {
176        match self {
177            CalcValue::Scalar(s) => Some(s),
178            _ => None,
179        }
180    }
181
182    pub fn as_range(&self) -> Option<&RangeView<'a>> {
183        match self {
184            CalcValue::Range(r) => Some(r),
185            _ => None,
186        }
187    }
188
189    pub fn as_callable(&self) -> Option<&Arc<dyn CustomCallable>> {
190        match self {
191            CalcValue::Callable(c) => Some(c),
192            _ => None,
193        }
194    }
195
196    pub fn into_owned(self) -> LiteralValue {
197        self.into_literal()
198    }
199}
200
201impl From<CalcValue<'_>> for LiteralValue {
202    fn from(val: CalcValue<'_>) -> Self {
203        val.into_literal()
204    }
205}
206
207impl<'a> PartialEq<LiteralValue> for CalcValue<'a> {
208    fn eq(&self, other: &LiteralValue) -> bool {
209        match self {
210            CalcValue::Scalar(s) => s == other,
211            CalcValue::Range(rv) => match other {
212                LiteralValue::Array(arr) => {
213                    let (rows, cols) = rv.dims();
214                    if arr.len() != rows {
215                        return false;
216                    }
217                    for (r, row) in arr.iter().enumerate() {
218                        if row.len() != cols {
219                            return false;
220                        }
221                        for (c, cell) in row.iter().enumerate() {
222                            if &rv.get_cell(r, c) != cell {
223                                return false;
224                            }
225                        }
226                    }
227                    true
228                }
229                _ => {
230                    let (rows, cols) = rv.dims();
231                    rows == 1 && cols == 1 && &rv.get_cell(0, 0) == other
232                }
233            },
234            CalcValue::Callable(_) => false,
235        }
236    }
237}
238
239impl<'a> PartialEq<CalcValue<'a>> for LiteralValue {
240    fn eq(&self, other: &CalcValue<'a>) -> bool {
241        other == self
242    }
243}
244
245pub enum EvaluatedArg<'a> {
246    LiteralValue(CowValue<'a>),
247    Range(Box<dyn Range>),
248}
249
250enum ArgumentExpr<'a> {
251    Ast(&'a ASTNode),
252    Arena {
253        id: crate::engine::arena::AstNodeId,
254        data_store: &'a crate::engine::arena::DataStore,
255        sheet_registry: &'a crate::engine::sheet_registry::SheetRegistry,
256    },
257}
258
259pub struct ArgumentHandle<'a, 'b> {
260    expr: ArgumentExpr<'a>,
261    interp: &'a Interpreter<'b>,
262    cached_ast: std::cell::OnceCell<ASTNode>,
263    cached_ref: std::cell::OnceCell<ReferenceType>,
264    cached_resolved: std::cell::OnceCell<Result<ResolvedArgument<'b>, ExcelError>>,
265    /// Memoized result of [`Self::value`]. `Function::dispatch` evaluates
266    /// every argument once during schema validation and the function's `eval`
267    /// evaluates it again — without this cache that re-entry compounds to
268    /// 2^depth evaluations of the innermost node for nested non-short-circuit
269    /// calls (measured: depth 12 ⇒ 4096 evaluations). The handle is created
270    /// per call site and per evaluation, so the memo can never go stale
271    /// across recalcs. `value_with_env` is intentionally NOT memoized (the
272    /// local env changes the result).
273    cached_value: std::cell::OnceCell<Result<crate::traits::CalcValue<'b>, ExcelError>>,
274}
275
276impl<'a, 'b> ArgumentHandle<'a, 'b> {
277    pub(crate) fn new(node: &'a ASTNode, interp: &'a Interpreter<'b>) -> Self {
278        Self {
279            expr: ArgumentExpr::Ast(node),
280            interp,
281            cached_ast: std::cell::OnceCell::new(),
282            cached_ref: std::cell::OnceCell::new(),
283            cached_resolved: std::cell::OnceCell::new(),
284            cached_value: std::cell::OnceCell::new(),
285        }
286    }
287
288    pub(crate) fn new_arena(
289        id: crate::engine::arena::AstNodeId,
290        interp: &'a Interpreter<'b>,
291        data_store: &'a crate::engine::arena::DataStore,
292        sheet_registry: &'a crate::engine::sheet_registry::SheetRegistry,
293    ) -> Self {
294        Self {
295            expr: ArgumentExpr::Arena {
296                id,
297                data_store,
298                sheet_registry,
299            },
300            interp,
301            cached_ast: std::cell::OnceCell::new(),
302            cached_ref: std::cell::OnceCell::new(),
303            cached_resolved: std::cell::OnceCell::new(),
304            cached_value: std::cell::OnceCell::new(),
305        }
306    }
307
308    /// Workbook date system in force for the evaluation this argument belongs to.
309    ///
310    /// Lets value-collecting helpers resolve date literals to serials without
311    /// threading a `DateSystem` (or the whole `FunctionContext`) through every
312    /// call site.
313    pub(crate) fn date_system(&self) -> crate::engine::DateSystem {
314        self.interp.context.date_system()
315    }
316
317    /// Returns whether this handle represents an explicitly omitted argument slot.
318    ///
319    /// This is false for absent arguments, explicit empty text, and blank references.
320    pub fn is_omitted(&self) -> bool {
321        match &self.expr {
322            ArgumentExpr::Ast(node) => matches!(node.node_type, ASTNodeType::Omitted),
323            ArgumentExpr::Arena { id, data_store, .. } => matches!(
324                data_store.get_node(*id),
325                Some(crate::engine::arena::AstNodeData::Omitted)
326            ),
327        }
328    }
329
330    /// Returns whether this argument resolves as a spreadsheet reference rather than a value.
331    ///
332    /// This uses the interpreter's reference-resolution path, so reference-returning functions
333    /// are included only when they actually produce a reference. A computed array remains a value
334    /// even though both it and a cell range are represented by [`CalcValue::Range`].
335    pub(crate) fn has_reference_semantics(&self) -> bool {
336        self.reference_attempt().is_some()
337    }
338
339    pub fn value(&self) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
340        self.cached_value
341            .get_or_init(|| self.compute_value())
342            .clone()
343    }
344
345    /// Resolves a scalar that is about to be coerced to text.
346    ///
347    /// Omitted arguments materialize as numeric zero through `value()`, which is
348    /// correct for Any/numeric consumers and aggregates. Text consumers must use
349    /// this boundary so omission becomes empty text without changing explicit 0.
350    pub(crate) fn value_for_text(&self) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
351        if self.is_omitted() {
352            Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
353                String::new(),
354            )))
355        } else {
356            self.value()
357        }
358    }
359
360    pub(crate) fn resolve_once_for_text(&self) -> Result<ResolvedArgument<'b>, ExcelError> {
361        if self.is_omitted() {
362            Ok(ResolvedArgument::Value(crate::traits::CalcValue::Scalar(
363                LiteralValue::Text(String::new()),
364            )))
365        } else {
366            self.resolve_once()
367        }
368    }
369
370    fn compute_value(&self) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
371        match &self.expr {
372            ArgumentExpr::Ast(node) => match &node.node_type {
373                ASTNodeType::Literal(v) => Ok(crate::traits::CalcValue::Scalar(v.clone())),
374                // With no schema-level text policy, Number(0) is the neutral Any-policy
375                // materialization. Text consumers resolve through `value_for_text`.
376                ASTNodeType::Omitted => {
377                    Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0)))
378                }
379                _ => self.interp.evaluate_ast(node),
380            },
381            ArgumentExpr::Arena {
382                id,
383                data_store,
384                sheet_registry,
385            } => {
386                if matches!(
387                    data_store.get_node(*id),
388                    Some(crate::engine::arena::AstNodeData::Omitted)
389                ) {
390                    Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0)))
391                } else {
392                    self.interp
393                        .evaluate_arena_ast(*id, data_store, sheet_registry)
394                }
395            }
396        }
397    }
398
399    pub fn value_with_env(
400        &self,
401        env: crate::interpreter::LocalEnv,
402    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
403        let scoped = self.interp.with_local_env(env);
404        match &self.expr {
405            ArgumentExpr::Ast(node) => match &node.node_type {
406                ASTNodeType::Literal(v) => Ok(crate::traits::CalcValue::Scalar(v.clone())),
407                ASTNodeType::Omitted => {
408                    Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0)))
409                }
410                _ => scoped.evaluate_ast(node),
411            },
412            ArgumentExpr::Arena {
413                id,
414                data_store,
415                sheet_registry,
416            } => {
417                if matches!(
418                    data_store.get_node(*id),
419                    Some(crate::engine::arena::AstNodeData::Omitted)
420                ) {
421                    Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0)))
422                } else {
423                    scoped.evaluate_arena_ast(*id, data_store, sheet_registry)
424                }
425            }
426        }
427    }
428
429    pub fn current_env(&self) -> crate::interpreter::LocalEnv {
430        self.interp.local_env().clone()
431    }
432
433    pub fn inline_array_literal(&self) -> Result<Option<Vec<Vec<LiteralValue>>>, ExcelError> {
434        match &self.expr {
435            ArgumentExpr::Ast(node) => match &node.node_type {
436                ASTNodeType::Literal(LiteralValue::Array(arr)) => Ok(Some(arr.clone())),
437                _ => Ok(None),
438            },
439            ArgumentExpr::Arena {
440                id,
441                data_store,
442                sheet_registry,
443            } => {
444                let node = data_store.get_node(*id).ok_or_else(|| {
445                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
446                })?;
447                match node {
448                    crate::engine::arena::AstNodeData::Literal(vref) => {
449                        match data_store.retrieve_value(*vref) {
450                            LiteralValue::Array(arr) => Ok(Some(arr)),
451                            _ => Ok(None),
452                        }
453                    }
454                    _ => {
455                        // preserve existing behavior: only a literal array (not a computed array)
456                        // is treated as "inline array literal".
457                        let _ = sheet_registry;
458                        Ok(None)
459                    }
460                }
461            }
462        }
463    }
464
465    fn reference_for_eval(&self) -> Result<ReferenceType, ExcelError> {
466        match &self.expr {
467            ArgumentExpr::Ast(node) => match &node.node_type {
468                ASTNodeType::Reference { reference, .. } => {
469                    self.interp.reference_for_current_offset(reference)
470                }
471                ASTNodeType::Function { .. } | ASTNodeType::BinaryOp { .. } => {
472                    self.interp.evaluate_ast_as_reference(node)
473                }
474                _ => Err(ExcelError::new(ExcelErrorKind::Ref)
475                    .with_message("Expected a reference (by-ref argument)")),
476            },
477            ArgumentExpr::Arena {
478                id,
479                data_store,
480                sheet_registry,
481            } => {
482                let node = data_store.get_node(*id).ok_or_else(|| {
483                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
484                })?;
485                match node {
486                    crate::engine::arena::AstNodeData::Reference { ref_type, .. } => {
487                        let reference = data_store
488                            .reconstruct_reference_type_for_eval(ref_type, sheet_registry);
489                        self.interp.reference_for_current_offset(&reference)
490                    }
491                    crate::engine::arena::AstNodeData::Function { .. }
492                    | crate::engine::arena::AstNodeData::BinaryOp { .. } => self
493                        .interp
494                        .evaluate_arena_ast_as_reference(*id, data_store, sheet_registry),
495                    _ => Err(ExcelError::new(ExcelErrorKind::Ref)
496                        .with_message("Expected a reference (by-ref argument)")),
497                }
498            }
499        }
500    }
501
502    fn reference_attempt(&self) -> Option<Result<ReferenceType, ExcelError>> {
503        match &self.expr {
504            ArgumentExpr::Ast(node) => match &node.node_type {
505                ASTNodeType::Reference { reference, .. } => {
506                    Some(self.interp.reference_for_current_offset(reference))
507                }
508                ASTNodeType::BinaryOp { op, .. } if op == ":" => {
509                    Some(self.interp.evaluate_ast_as_reference(node))
510                }
511                ASTNodeType::Function { name, .. }
512                    if self
513                        .interp
514                        .context
515                        .function_capabilities("", name)
516                        .is_some_and(|caps| {
517                            caps.contains(crate::function::FnCaps::RETURNS_REFERENCE)
518                        }) =>
519                {
520                    self.interp.try_evaluate_ast_as_reference(node)
521                }
522                _ => None,
523            },
524            ArgumentExpr::Arena {
525                id,
526                data_store,
527                sheet_registry,
528            } => {
529                let node = match data_store.get_node(*id) {
530                    Some(node) => node,
531                    None => {
532                        return Some(Err(
533                            ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
534                        ));
535                    }
536                };
537                match node {
538                    crate::engine::arena::AstNodeData::Reference { ref_type, .. } => {
539                        let reference = data_store
540                            .reconstruct_reference_type_for_eval(ref_type, sheet_registry);
541                        Some(self.interp.reference_for_current_offset(&reference))
542                    }
543                    crate::engine::arena::AstNodeData::BinaryOp { op_id, .. }
544                        if data_store.resolve_ast_string(*op_id) == ":" =>
545                    {
546                        Some(self.interp.evaluate_arena_ast_as_reference(
547                            *id,
548                            data_store,
549                            sheet_registry,
550                        ))
551                    }
552                    crate::engine::arena::AstNodeData::Function { name_id, .. } => {
553                        let name = data_store.resolve_ast_string(*name_id);
554                        if self
555                            .interp
556                            .context
557                            .function_capabilities("", name)
558                            .is_some_and(|caps| {
559                                caps.contains(crate::function::FnCaps::RETURNS_REFERENCE)
560                            })
561                        {
562                            self.interp.try_evaluate_arena_ast_as_reference(
563                                *id,
564                                data_store,
565                                sheet_registry,
566                            )
567                        } else {
568                            None
569                        }
570                    }
571                    _ => None,
572                }
573            }
574        }
575    }
576
577    /// Resolve this argument once without using a failed range conversion as type dispatch.
578    ///
579    /// Direct references and the `:` operator take the reference path. Functions
580    /// with `RETURNS_REFERENCE` first attempt reference evaluation, but fall back
581    /// to their cached value when `eval_reference` returns `None`.
582    pub(crate) fn resolve_once(&self) -> Result<ResolvedArgument<'b>, ExcelError> {
583        self.cached_resolved
584            .get_or_init(|| self.compute_resolved_argument())
585            .clone()
586    }
587
588    fn with_context_cancel_token(&self, view: RangeView<'b>) -> RangeView<'b> {
589        match self.interp.context.cancellation_token() {
590            Some(token) => view.with_cancel_token(Some(token)),
591            None => view,
592        }
593    }
594
595    fn compute_resolved_argument(&self) -> Result<ResolvedArgument<'b>, ExcelError> {
596        if let Some(attempt) = self.reference_attempt() {
597            let reference = match attempt {
598                Ok(reference) => reference,
599                Err(error) if error.kind == ExcelErrorKind::Cancelled => return Err(error),
600                Err(error) => return Ok(ResolvedArgument::ReferenceError(error)),
601            };
602            return match self
603                .interp
604                .context
605                .resolve_range_view(&reference, self.interp.current_sheet())
606            {
607                Ok(view) => Ok(ResolvedArgument::Range(
608                    self.with_context_cancel_token(view),
609                )),
610                Err(error) if error.kind == ExcelErrorKind::Cancelled => Err(error),
611                Err(error) => Ok(ResolvedArgument::ReferenceError(error)),
612            };
613        }
614
615        match self.value()? {
616            CalcValue::Range(view) => Ok(ResolvedArgument::Range(
617                self.with_context_cancel_token(view),
618            )),
619            CalcValue::Scalar(LiteralValue::Array(rows)) => {
620                let view = RangeView::try_from_owned_rows(
621                    rows,
622                    self.interp.context.date_system(),
623                    self.interp.context.cancellation_token(),
624                )?;
625                Ok(ResolvedArgument::Range(view))
626            }
627            other => Ok(ResolvedArgument::Value(other)),
628        }
629    }
630
631    pub fn range(&self) -> Result<Box<dyn Range>, ExcelError> {
632        match &self.expr {
633            ArgumentExpr::Ast(node) => match &node.node_type {
634                ASTNodeType::Reference { reference, .. } => {
635                    // Prefer RangeView since it has explicit current-sheet context.
636                    let reference = self.interp.reference_for_current_offset(reference)?;
637                    let view = self
638                        .interp
639                        .context
640                        .resolve_range_view(&reference, self.interp.current_sheet())?;
641                    let (rows, cols) = view.dims();
642                    let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
643                    view.for_each_row(&mut |row| {
644                        let row_data: Vec<LiteralValue> = (0..cols)
645                            .map(|c| row.get(c).cloned().unwrap_or(LiteralValue::Empty))
646                            .collect();
647                        out.push(row_data);
648                        Ok(())
649                    })?;
650                    Ok(Box::new(InMemoryRange::new(out)))
651                }
652                ASTNodeType::Function { .. } | ASTNodeType::BinaryOp { .. } => {
653                    let reference = self.reference_for_eval()?;
654                    let view = self
655                        .interp
656                        .context
657                        .resolve_range_view(&reference, self.interp.current_sheet())?;
658                    let (rows, cols) = view.dims();
659                    let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
660                    view.for_each_row(&mut |row| {
661                        let row_data: Vec<LiteralValue> = (0..cols)
662                            .map(|c| row.get(c).cloned().unwrap_or(LiteralValue::Empty))
663                            .collect();
664                        out.push(row_data);
665                        Ok(())
666                    })?;
667                    Ok(Box::new(InMemoryRange::new(out)))
668                }
669                ASTNodeType::Array(rows) => {
670                    let mut materialized = Vec::new();
671                    for row in rows {
672                        let mut materialized_row = Vec::new();
673                        for cell in row {
674                            materialized_row.push(self.interp.evaluate_ast(cell)?.into_literal());
675                        }
676                        materialized.push(materialized_row);
677                    }
678                    Ok(Box::new(InMemoryRange::new(materialized)))
679                }
680                _ => Err(ExcelError::new(ExcelErrorKind::Ref)
681                    .with_message(format!("Expected a range, got {:?}", node.node_type))),
682            },
683            ArgumentExpr::Arena { id, data_store, .. } => {
684                let node = data_store.get_node(*id).ok_or_else(|| {
685                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
686                })?;
687
688                match node {
689                    crate::engine::arena::AstNodeData::Reference { .. }
690                    | crate::engine::arena::AstNodeData::Function { .. }
691                    | crate::engine::arena::AstNodeData::BinaryOp { .. } => {
692                        let reference = self.reference_for_eval()?;
693                        let view = self
694                            .interp
695                            .context
696                            .resolve_range_view(&reference, self.interp.current_sheet())?;
697                        let (rows, cols) = view.dims();
698                        let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
699                        view.for_each_row(&mut |row| {
700                            let row_data: Vec<LiteralValue> = (0..cols)
701                                .map(|c| row.get(c).cloned().unwrap_or(LiteralValue::Empty))
702                                .collect();
703                            out.push(row_data);
704                            Ok(())
705                        })?;
706                        Ok(Box::new(InMemoryRange::new(out)))
707                    }
708                    crate::engine::arena::AstNodeData::Array { .. } => {
709                        let (rows, cols, elements) =
710                            data_store.get_array_elems(*id).ok_or_else(|| {
711                                ExcelError::new(ExcelErrorKind::Value).with_message("Invalid array")
712                            })?;
713                        let rows_usize = rows as usize;
714                        let cols_usize = cols as usize;
715                        let mut materialized: Vec<Vec<LiteralValue>> =
716                            Vec::with_capacity(rows_usize);
717                        for r in 0..rows_usize {
718                            let mut row = Vec::with_capacity(cols_usize);
719                            for c in 0..cols_usize {
720                                let idx = r * cols_usize + c;
721                                let elem_id = elements.get(idx).copied().ok_or_else(|| {
722                                    ExcelError::new(ExcelErrorKind::Value)
723                                        .with_message("Invalid array")
724                                })?;
725                                let v = self.interp.evaluate_arena_ast(
726                                    elem_id,
727                                    data_store,
728                                    self.sheet_registry(),
729                                )?;
730                                row.push(v.into_literal());
731                            }
732                            materialized.push(row);
733                        }
734                        Ok(Box::new(InMemoryRange::new(materialized)))
735                    }
736                    _ => Err(ExcelError::new(ExcelErrorKind::Ref)
737                        .with_message("Argument cannot be interpreted as a range.")),
738                }
739            }
740        }
741    }
742
743    fn sheet_registry(&self) -> &crate::engine::sheet_registry::SheetRegistry {
744        match &self.expr {
745            ArgumentExpr::Ast(_) => {
746                // Not needed; used only in arena flows.
747                unreachable!("sheet_registry only used for arena ArgumentHandle")
748            }
749            ArgumentExpr::Arena { sheet_registry, .. } => sheet_registry,
750        }
751    }
752
753    /// Resolve this argument to a [`RangeView`].
754    ///
755    /// Delegates to [`Self::resolve_once`] so reference-shaped and computed
756    /// arguments share one cached resolution path. A reference keeps its lazy
757    /// view, while a computed argument (`B1:B3="x"`, `SEQUENCE(3)`, `{1,2}`)
758    /// resolves through the same single evaluation the rest of argument
759    /// preparation uses instead of being rejected for not being a reference.
760    pub fn range_view(&self) -> Result<RangeView<'b>, ExcelError> {
761        match self.resolve_once()? {
762            ResolvedArgument::Range(view) => Ok(view),
763            // A genuine reference failure (`OFFSET(A1,-1,0)`) stays an error
764            // rather than being masked by re-evaluating the node as a value.
765            ResolvedArgument::ReferenceError(error) => Err(error),
766            // `resolve_once` already folds range-shaped and array values into
767            // `Range`, so these two arms are defensive. They still apply the
768            // cancellation token so the invariant changing could never silently
769            // drop cancellation on this path.
770            ResolvedArgument::Value(CalcValue::Range(view)) => {
771                Ok(self.with_context_cancel_token(view))
772            }
773            ResolvedArgument::Value(CalcValue::Scalar(LiteralValue::Array(rows))) => {
774                RangeView::try_from_owned_rows(
775                    rows,
776                    self.interp.context.date_system(),
777                    self.interp.context.cancellation_token(),
778                )
779            }
780            ResolvedArgument::Value(_) => Err(ExcelError::new(ExcelErrorKind::Ref)
781                .with_message("Argument cannot be interpreted as a range.")),
782        }
783    }
784
785    /// Resolve this argument to a [`RangeView`], promoting a scalar to a 1x1 view.
786    ///
787    /// Excel treats a scalar handed to a range-consuming function as a 1x1 array,
788    /// so `=TRANSPOSE(2)` is `2` rather than an error. [`Self::range_view`] rejects
789    /// scalars, and a function that wants the Excel behaviour opts in here.
790    ///
791    /// This is deliberately *not* the behaviour of `range_view` itself. Several
792    /// builtins use a `range_view` failure as type dispatch, where "scalar" and
793    /// "1x1 range" mean genuinely different things -- `MEDIAN(TRUE)` is `1`
794    /// because a direct scalar is coerced while a range cell of the same type is
795    /// skipped, and a D-function's scalar criteria argument is an error rather
796    /// than an empty criteria block that matches every row. Promoting inside
797    /// `range_view` would silently change those answers. The rule for choosing
798    /// between the two: a function that distinguishes a scalar argument from a
799    /// 1x1 range keeps `range_view`.
800    ///
801    /// An error scalar propagates as an error rather than becoming a 1x1 view
802    /// containing it, so `=TRANSPOSE(NA())` is `#N/A` instead of being masked as
803    /// `#REF!`.
804    pub fn range_view_or_scalar(&self) -> Result<RangeView<'b>, ExcelError> {
805        match self.resolve_once()? {
806            ResolvedArgument::Range(view) => Ok(view),
807            ResolvedArgument::ReferenceError(error) => Err(error),
808            ResolvedArgument::Value(CalcValue::Range(view)) => {
809                Ok(self.with_context_cancel_token(view))
810            }
811            ResolvedArgument::Value(CalcValue::Scalar(LiteralValue::Array(rows))) => {
812                RangeView::try_from_owned_rows(
813                    rows,
814                    self.interp.context.date_system(),
815                    self.interp.context.cancellation_token(),
816                )
817            }
818            // Preserve the argument's own error instead of reporting the shape
819            // mismatch that rejecting it would produce.
820            ResolvedArgument::Value(CalcValue::Scalar(LiteralValue::Error(error))) => Err(error),
821            ResolvedArgument::Value(CalcValue::Scalar(scalar)) => RangeView::try_from_owned_rows(
822                vec![vec![scalar]],
823                self.interp.context.date_system(),
824                self.interp.context.cancellation_token(),
825            ),
826            // A lambda is not a value that can stand in for a 1x1 array.
827            ResolvedArgument::Value(CalcValue::Callable(_)) => {
828                Err(ExcelError::new(ExcelErrorKind::Ref)
829                    .with_message("Argument cannot be interpreted as a range."))
830            }
831        }
832    }
833
834    pub fn value_or_range(&self) -> Result<EvaluatedArg<'_>, ExcelError> {
835        self.range().map(EvaluatedArg::Range).or_else(|_| {
836            self.value()
837                .map(|cv| EvaluatedArg::LiteralValue(Cow::Owned(cv.into_literal())))
838        })
839    }
840
841    /// Lazily iterate values for this argument in row-major expansion order.
842    /// - Reference: stream via RangeView (row-major)
843    /// - Array literal: evaluate each element lazily per cell
844    /// - Scalar/other expressions: a single value
845    pub fn lazy_values_owned(
846        &'a self,
847    ) -> Result<Box<dyn Iterator<Item = LiteralValue> + 'a>, ExcelError> {
848        match &self.expr {
849            ArgumentExpr::Ast(node) => match &node.node_type {
850                ASTNodeType::Reference { .. } => {
851                    let view = self.range_view()?;
852                    let mut values: Vec<LiteralValue> = Vec::new();
853                    view.for_each_cell(&mut |v| {
854                        values.push(v.clone());
855                        Ok(())
856                    })?;
857                    Ok(Box::new(values.into_iter()))
858                }
859                ASTNodeType::Array(rows) => {
860                    struct ArrayEvalIter<'a, 'b> {
861                        rows: &'a [Vec<ASTNode>],
862                        r: usize,
863                        c: usize,
864                        interp: &'a Interpreter<'b>,
865                    }
866                    impl<'a, 'b> Iterator for ArrayEvalIter<'a, 'b> {
867                        type Item = LiteralValue;
868                        fn next(&mut self) -> Option<Self::Item> {
869                            if self.rows.is_empty() {
870                                return None;
871                            }
872                            let rows = self.rows;
873                            let mut r = self.r;
874                            let mut c = self.c;
875                            if r >= rows.len() {
876                                return None;
877                            }
878                            let node = &rows[r][c];
879                            // advance indices
880                            c += 1;
881                            if c >= rows[r].len() {
882                                r += 1;
883                                c = 0;
884                            }
885                            self.r = r;
886                            self.c = c;
887                            match self.interp.evaluate_ast(node) {
888                                Ok(cv) => Some(cv.into_literal()),
889                                Err(e) => Some(LiteralValue::Error(e)),
890                            }
891                        }
892                    }
893                    let it = ArrayEvalIter {
894                        rows,
895                        r: 0,
896                        c: 0,
897                        interp: self.interp,
898                    };
899                    Ok(Box::new(it))
900                }
901                _ => {
902                    // Single value expression
903                    let v = self.value()?.into_literal();
904                    Ok(Box::new(std::iter::once(v)))
905                }
906            },
907            ArgumentExpr::Arena {
908                id,
909                data_store,
910                sheet_registry,
911            } => {
912                let node = data_store.get_node(*id).ok_or_else(|| {
913                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
914                })?;
915
916                match node {
917                    crate::engine::arena::AstNodeData::Reference { .. } => {
918                        let view = self.range_view()?;
919                        let mut values: Vec<LiteralValue> = Vec::new();
920                        view.for_each_cell(&mut |v| {
921                            values.push(v.clone());
922                            Ok(())
923                        })?;
924                        Ok(Box::new(values.into_iter()))
925                    }
926                    crate::engine::arena::AstNodeData::Array { .. } => {
927                        let (rows, cols, elements) =
928                            data_store.get_array_elems(*id).ok_or_else(|| {
929                                ExcelError::new(ExcelErrorKind::Value).with_message("Invalid array")
930                            })?;
931
932                        struct ArenaArrayEvalIter<'a, 'b> {
933                            elements: &'a [crate::engine::arena::AstNodeId],
934                            idx: usize,
935                            interp: &'a Interpreter<'b>,
936                            data_store: &'a crate::engine::arena::DataStore,
937                            sheet_registry: &'a crate::engine::sheet_registry::SheetRegistry,
938                        }
939
940                        impl<'a, 'b> Iterator for ArenaArrayEvalIter<'a, 'b> {
941                            type Item = LiteralValue;
942
943                            fn next(&mut self) -> Option<Self::Item> {
944                                let id = self.elements.get(self.idx).copied()?;
945                                self.idx += 1;
946                                match self.interp.evaluate_arena_ast(
947                                    id,
948                                    self.data_store,
949                                    self.sheet_registry,
950                                ) {
951                                    Ok(cv) => Some(cv.into_literal()),
952                                    Err(e) => Some(LiteralValue::Error(e)),
953                                }
954                            }
955                        }
956
957                        let _ = (rows, cols);
958                        let it = ArenaArrayEvalIter {
959                            elements,
960                            idx: 0,
961                            interp: self.interp,
962                            data_store,
963                            sheet_registry,
964                        };
965                        Ok(Box::new(it))
966                    }
967                    _ => {
968                        let v = self
969                            .interp
970                            .evaluate_arena_ast(*id, data_store, sheet_registry)?;
971                        Ok(Box::new(std::iter::once(v.into_literal())))
972                    }
973                }
974            }
975        }
976    }
977
978    pub fn ast(&self) -> &ASTNode {
979        match &self.expr {
980            ArgumentExpr::Ast(node) => node,
981            ArgumentExpr::Arena {
982                id,
983                data_store,
984                sheet_registry,
985            } => self.cached_ast.get_or_init(|| {
986                data_store
987                    .retrieve_ast(*id, sheet_registry)
988                    .unwrap_or_else(|| ASTNode {
989                        node_type: ASTNodeType::Literal(LiteralValue::Error(
990                            ExcelError::new(ExcelErrorKind::Value)
991                                .with_message("Missing formula AST"),
992                        )),
993                        source_token: None,
994                        contains_volatile: false,
995                    })
996            }),
997        }
998    }
999
1000    /// Returns the raw reference from the AST when this argument is a reference.
1001    /// This does not evaluate the reference or materialize values.
1002    pub fn as_reference(&self) -> Result<&ReferenceType, ExcelError> {
1003        match &self.expr {
1004            ArgumentExpr::Ast(node) => match &node.node_type {
1005                ASTNodeType::Reference { reference, .. } => Ok(reference),
1006                _ => Err(ExcelError::new(ExcelErrorKind::Ref)
1007                    .with_message("Expected a reference (by-ref argument)")),
1008            },
1009            ArgumentExpr::Arena { .. } => {
1010                let reference = self.reference_for_eval()?;
1011                Ok(self.cached_ref.get_or_init(|| reference))
1012            }
1013        }
1014    }
1015
1016    /// Returns a `ReferenceType` if this argument is a reference or a function that
1017    /// can yield a reference via `eval_reference`. Materializes no values.
1018    pub fn as_reference_or_eval(&self) -> Result<ReferenceType, ExcelError> {
1019        match &self.expr {
1020            ArgumentExpr::Ast(node) => match &node.node_type {
1021                ASTNodeType::Reference { reference, .. } => {
1022                    self.interp.reference_for_current_offset(reference)
1023                }
1024                ASTNodeType::Function { .. } | ASTNodeType::BinaryOp { .. } => {
1025                    self.interp.evaluate_ast_as_reference(node)
1026                }
1027                _ => Err(ExcelError::new(ExcelErrorKind::Ref)
1028                    .with_message("Argument is not a reference")),
1029            },
1030            ArgumentExpr::Arena {
1031                id,
1032                data_store,
1033                sheet_registry,
1034            } => {
1035                let node = data_store.get_node(*id).ok_or_else(|| {
1036                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
1037                })?;
1038
1039                match node {
1040                    crate::engine::arena::AstNodeData::Reference { .. } => {
1041                        self.reference_for_eval()
1042                    }
1043                    crate::engine::arena::AstNodeData::Function { .. }
1044                    | crate::engine::arena::AstNodeData::BinaryOp { .. } => self
1045                        .interp
1046                        .evaluate_arena_ast_as_reference(*id, data_store, sheet_registry),
1047                    _ => Err(ExcelError::new(ExcelErrorKind::Ref)
1048                        .with_message("Argument is not a reference")),
1049                }
1050            }
1051        }
1052    }
1053
1054    /* tiny validator helper for macro */
1055    pub fn matches_kind(&self, k: formualizer_common::ArgKind) -> Result<bool, ExcelError> {
1056        Ok(match k {
1057            formualizer_common::ArgKind::Any => true,
1058            formualizer_common::ArgKind::Range => self.range().is_ok(),
1059            formualizer_common::ArgKind::Number => matches!(
1060                self.value()?.into_literal(),
1061                LiteralValue::Number(_) | LiteralValue::Int(_)
1062            ),
1063            formualizer_common::ArgKind::Text => {
1064                matches!(self.value()?.into_literal(), LiteralValue::Text(_))
1065            }
1066            formualizer_common::ArgKind::Logical => {
1067                matches!(self.value()?.into_literal(), LiteralValue::Boolean(_))
1068            }
1069        })
1070    }
1071}
1072
1073/* simple Vec-backed range */
1074#[derive(Debug, Clone)]
1075pub struct InMemoryRange {
1076    data: Vec<Vec<LiteralValue>>,
1077}
1078impl InMemoryRange {
1079    pub fn new(d: Vec<Vec<LiteralValue>>) -> Self {
1080        Self { data: d }
1081    }
1082}
1083impl Range for InMemoryRange {
1084    fn get(&self, r: usize, c: usize) -> Result<LiteralValue, ExcelError> {
1085        Ok(self
1086            .data
1087            .get(r)
1088            .and_then(|row| row.get(c))
1089            .cloned()
1090            .unwrap_or(LiteralValue::Empty))
1091    }
1092    fn dimensions(&self) -> (usize, usize) {
1093        (self.data.len(), self.data.first().map_or(0, |r| r.len()))
1094    }
1095    fn as_any(&self) -> &dyn Any {
1096        self
1097    }
1098}
1099
1100/* ───────────────────────── Table abstraction ───────────────────────── */
1101
1102pub trait Table: Debug + Send + Sync {
1103    fn get_cell(&self, row: usize, column: &str) -> Result<LiteralValue, ExcelError>;
1104    fn get_column(&self, column: &str) -> Result<Box<dyn Range>, ExcelError>;
1105    /// Ordered list of column names
1106    fn columns(&self) -> Vec<String> {
1107        vec![]
1108    }
1109    /// Number of data rows (excluding headers/totals)
1110    fn data_height(&self) -> usize {
1111        0
1112    }
1113    /// Whether the table has a header row
1114    fn has_headers(&self) -> bool {
1115        false
1116    }
1117    /// Whether the table has a totals row
1118    fn has_totals(&self) -> bool {
1119        false
1120    }
1121    /// Headers row as a 1xW range
1122    fn headers_row(&self) -> Option<Box<dyn Range>> {
1123        None
1124    }
1125    /// Totals row as a 1xW range, if present
1126    fn totals_row(&self) -> Option<Box<dyn Range>> {
1127        None
1128    }
1129    /// Entire data body as HxW range
1130    fn data_body(&self) -> Option<Box<dyn Range>> {
1131        None
1132    }
1133    fn clone_box(&self) -> Box<dyn Table>;
1134}
1135impl Table for Box<dyn Table> {
1136    fn get_cell(&self, r: usize, c: &str) -> Result<LiteralValue, ExcelError> {
1137        (**self).get_cell(r, c)
1138    }
1139    fn get_column(&self, c: &str) -> Result<Box<dyn Range>, ExcelError> {
1140        (**self).get_column(c)
1141    }
1142    fn columns(&self) -> Vec<String> {
1143        (**self).columns()
1144    }
1145    fn data_height(&self) -> usize {
1146        (**self).data_height()
1147    }
1148    fn has_headers(&self) -> bool {
1149        (**self).has_headers()
1150    }
1151    fn has_totals(&self) -> bool {
1152        (**self).has_totals()
1153    }
1154    fn headers_row(&self) -> Option<Box<dyn Range>> {
1155        (**self).headers_row()
1156    }
1157    fn totals_row(&self) -> Option<Box<dyn Range>> {
1158        (**self).totals_row()
1159    }
1160    fn data_body(&self) -> Option<Box<dyn Range>> {
1161        (**self).data_body()
1162    }
1163    fn clone_box(&self) -> Box<dyn Table> {
1164        (**self).clone_box()
1165    }
1166}
1167
1168/* ─────────────────────── Resolver super-trait ─────────────────────── */
1169
1170pub trait ReferenceResolver: Send + Sync {
1171    fn resolve_cell_reference(
1172        &self,
1173        sheet: Option<&str>,
1174        row: u32,
1175        col: u32,
1176    ) -> Result<LiteralValue, ExcelError>;
1177}
1178pub trait RangeResolver: Send + Sync {
1179    fn resolve_range_reference(
1180        &self,
1181        sheet: Option<&str>,
1182        sr: Option<u32>,
1183        sc: Option<u32>,
1184        er: Option<u32>,
1185        ec: Option<u32>,
1186    ) -> Result<Box<dyn Range>, ExcelError>;
1187}
1188pub trait NamedRangeResolver: Send + Sync {
1189    fn resolve_named_range_reference(
1190        &self,
1191        name: &str,
1192    ) -> Result<Vec<Vec<LiteralValue>>, ExcelError>;
1193}
1194pub trait TableResolver: Send + Sync {
1195    fn resolve_table_reference(
1196        &self,
1197        tref: &formualizer_parse::parser::TableReference,
1198    ) -> Result<Box<dyn Table>, ExcelError>;
1199}
1200
1201pub trait SourceResolver: Send + Sync {
1202    fn source_scalar_version(&self, _name: &str) -> Option<u64> {
1203        None
1204    }
1205
1206    fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
1207        Err(ExcelError::new(ExcelErrorKind::NImpl)
1208            .with_message(format!("Source scalar not supported: {name}")))
1209    }
1210
1211    fn source_table_version(&self, _name: &str) -> Option<u64> {
1212        None
1213    }
1214
1215    fn resolve_source_table(&self, name: &str) -> Result<Box<dyn Table>, ExcelError> {
1216        Err(ExcelError::new(ExcelErrorKind::NImpl)
1217            .with_message(format!("Source table not supported: {name}")))
1218    }
1219}
1220
1221pub trait Resolver: ReferenceResolver + RangeResolver + NamedRangeResolver + TableResolver {
1222    fn resolve_range_like(&self, r: &ReferenceType) -> Result<Box<dyn Range>, ExcelError> {
1223        match r {
1224            ReferenceType::Range {
1225                sheet,
1226                start_row,
1227                start_col,
1228                end_row,
1229                end_col,
1230                ..
1231            } => self.resolve_range_reference(
1232                sheet.as_deref(),
1233                *start_row,
1234                *start_col,
1235                *end_row,
1236                *end_col,
1237            ),
1238            ReferenceType::External(_) => Err(ExcelError::new(ExcelErrorKind::NImpl)
1239                .with_message("External references are not supported by Resolver".to_string())),
1240            ReferenceType::Table(tref) => {
1241                let t = self.resolve_table_reference(tref)?;
1242                match &tref.specifier {
1243                    Some(TableSpecifier::Column(c)) => t.get_column(c),
1244                    Some(TableSpecifier::ColumnRange(start, end)) => {
1245                        // Build a rectangular range from start..=end columns in table order
1246                        let cols = t.columns();
1247                        let start_key = start.to_lowercase();
1248                        let end_key = end.to_lowercase();
1249                        let start_idx = cols.iter().position(|n| n.to_lowercase() == start_key);
1250                        let end_idx = cols.iter().position(|n| n.to_lowercase() == end_key);
1251                        if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
1252                            if si > ei {
1253                                std::mem::swap(&mut si, &mut ei);
1254                            }
1255                            // Materialize by stacking columns into a 2D array
1256                            let h = t.data_height();
1257                            let w = ei - si + 1;
1258                            let mut rows = vec![vec![LiteralValue::Empty; w]; h];
1259                            for (offset, ci) in (si..=ei).enumerate() {
1260                                let cname = &cols[ci];
1261                                let col_range = t.get_column(cname)?;
1262                                let (rh, _) = col_range.dimensions();
1263                                for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
1264                                    row[offset] = col_range.get(r, 0)?;
1265                                }
1266                            }
1267                            Ok(Box::new(InMemoryRange::new(rows)))
1268                        } else {
1269                            Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
1270                                "Column range refers to unknown column(s)".to_string(),
1271                            ))
1272                        }
1273                    }
1274                    Some(TableSpecifier::SpecialItem(
1275                        formualizer_parse::parser::SpecialItem::Headers,
1276                    )) => {
1277                        if let Some(h) = t.headers_row() {
1278                            Ok(h)
1279                        } else {
1280                            Ok(Box::new(InMemoryRange::new(vec![])))
1281                        }
1282                    }
1283                    Some(TableSpecifier::SpecialItem(
1284                        formualizer_parse::parser::SpecialItem::Totals,
1285                    )) => {
1286                        if let Some(tr) = t.totals_row() {
1287                            Ok(tr)
1288                        } else {
1289                            Ok(Box::new(InMemoryRange::new(vec![])))
1290                        }
1291                    }
1292                    Some(TableSpecifier::SpecialItem(
1293                        formualizer_parse::parser::SpecialItem::Data,
1294                    )) => {
1295                        if let Some(body) = t.data_body() {
1296                            Ok(body)
1297                        } else {
1298                            Ok(Box::new(InMemoryRange::new(vec![])))
1299                        }
1300                    }
1301                    Some(TableSpecifier::SpecialItem(
1302                        formualizer_parse::parser::SpecialItem::All,
1303                    )) => {
1304                        // Equivalent to TableSpecifier::All handling
1305                        let mut out: Vec<Vec<LiteralValue>> = Vec::new();
1306                        if let Some(h) = t.headers_row() {
1307                            out.extend(h.iter_rows());
1308                        }
1309                        if let Some(body) = t.data_body() {
1310                            out.extend(body.iter_rows());
1311                        }
1312                        if let Some(tr) = t.totals_row() {
1313                            out.extend(tr.iter_rows());
1314                        }
1315                        Ok(Box::new(InMemoryRange::new(out)))
1316                    }
1317                    Some(TableSpecifier::SpecialItem(
1318                        formualizer_parse::parser::SpecialItem::ThisRow,
1319                    )) => Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1320                        "@ (This Row) requires table-aware context; not yet supported".to_string(),
1321                    )),
1322                    Some(TableSpecifier::All) => {
1323                        // Concatenate headers (if any), data, totals (if any)
1324                        let mut out: Vec<Vec<LiteralValue>> = Vec::new();
1325                        if let Some(h) = t.headers_row() {
1326                            out.extend(h.iter_rows());
1327                        }
1328                        if let Some(body) = t.data_body() {
1329                            out.extend(body.iter_rows());
1330                        }
1331                        if let Some(tr) = t.totals_row() {
1332                            out.extend(tr.iter_rows());
1333                        }
1334                        Ok(Box::new(InMemoryRange::new(out)))
1335                    }
1336                    Some(TableSpecifier::Data) => {
1337                        if let Some(body) = t.data_body() {
1338                            Ok(body)
1339                        } else {
1340                            Ok(Box::new(InMemoryRange::new(vec![])))
1341                        }
1342                    }
1343                    // Defer complex combinations and row selectors for tranche 1
1344                    Some(TableSpecifier::Combination(_)) => Err(ExcelError::new(
1345                        ExcelErrorKind::NImpl,
1346                    )
1347                    .with_message("Complex structured references not yet supported".to_string())),
1348                    Some(TableSpecifier::Row(_)) => Err(ExcelError::new(ExcelErrorKind::NImpl)
1349                        .with_message("Row selectors (@/index) not yet supported".to_string())),
1350                    Some(TableSpecifier::Headers) | Some(TableSpecifier::Totals) => {
1351                        Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1352                            "Legacy Headers/Totals variants not used; use SpecialItem".to_string(),
1353                        ))
1354                    }
1355                    None => Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
1356                        "Table reference without specifier is unsupported".to_string(),
1357                    )),
1358                }
1359            }
1360            ReferenceType::NamedRange(n) => {
1361                let v = self.resolve_named_range_reference(n)?;
1362                Ok(Box::new(InMemoryRange::new(v)))
1363            }
1364            ReferenceType::Cell {
1365                sheet, row, col, ..
1366            } => {
1367                let v = self.resolve_cell_reference(sheet.as_deref(), *row, *col)?;
1368                Ok(Box::new(InMemoryRange::new(vec![vec![v]])))
1369            }
1370            ReferenceType::Cell3D { .. } | ReferenceType::Range3D { .. } => {
1371                Err(ExcelError::new(ExcelErrorKind::NImpl)
1372                    .with_message("3D references are not yet supported".to_string()))
1373            }
1374        }
1375    }
1376}
1377
1378/* ───────────────────── EvaluationContext = Resolver+Fns ───────────── */
1379
1380pub trait FunctionProvider: Send + Sync {
1381    fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>>;
1382
1383    #[doc(hidden)]
1384    fn get_function_for_planning(&self, _ns: &str, _name: &str) -> Option<Arc<dyn Function>> {
1385        None
1386    }
1387
1388    /// Monotonic revision for runtime function resolution and semantics used by
1389    /// compressed planning. Providers that cannot supply one fail closed.
1390    #[doc(hidden)]
1391    fn planning_semantic_revision(&self) -> Option<u64> {
1392        None
1393    }
1394
1395    #[doc(hidden)]
1396    fn function_capabilities(&self, ns: &str, name: &str) -> Option<crate::function::FnCaps> {
1397        self.get_function(ns, name).map(|function| function.caps())
1398    }
1399
1400    fn function_semantic_identity(
1401        &self,
1402        ns: &str,
1403        name: &str,
1404        arity: usize,
1405    ) -> Option<crate::function_contract::FunctionSemanticIdentity> {
1406        crate::function_registry::resolve_semantic_identity(self, ns, name, arity)
1407    }
1408}
1409
1410pub trait EvaluationContext: Resolver + FunctionProvider + SourceResolver {
1411    /// Get access to the shared thread pool for parallel evaluation
1412    /// Returns None if parallel evaluation is disabled or unavailable
1413    fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
1414        None
1415    }
1416
1417    /// Returns the optional shared cancellation handle for this evaluation.
1418    ///
1419    /// Custom context authors may return a clone: clones share the same signal
1420    /// without allocating. Consumers should retrieve the handle once before a
1421    /// hot loop and poll [`crate::engine::CancelToken::is_cancelled`]
1422    /// periodically.
1423    fn cancellation_token(&self) -> Option<crate::engine::CancelToken> {
1424        None
1425    }
1426
1427    /// Optional chunk size hint for streaming visitors.
1428    fn chunk_hint(&self) -> Option<usize> {
1429        None
1430    }
1431
1432    /// Resolve a reference into a `RangeView` with clear bounds.
1433    /// Implementations should resolve un/partially bounded references using used-region.
1434    fn resolve_range_view<'c>(
1435        &'c self,
1436        _reference: &ReferenceType,
1437        _current_sheet: &str,
1438    ) -> Result<RangeView<'c>, ExcelError> {
1439        Err(ExcelError::new(ExcelErrorKind::NImpl))
1440    }
1441
1442    /// Resolve a single-cell reference as a scalar value.
1443    ///
1444    /// Default implementation preserves existing reference semantics by routing through
1445    /// `resolve_range_view` and extracting a 1x1 value.
1446    fn resolve_cell_reference_value(
1447        &self,
1448        sheet: Option<&str>,
1449        row: u32,
1450        col: u32,
1451        current_sheet: &str,
1452    ) -> Result<LiteralValue, ExcelError> {
1453        let reference = ReferenceType::Cell {
1454            sheet: sheet.map(str::to_string),
1455            row,
1456            col,
1457            row_abs: true,
1458            col_abs: true,
1459        };
1460        let view = self.resolve_range_view(&reference, current_sheet)?;
1461        Ok(view.as_1x1().unwrap_or(LiteralValue::Empty))
1462    }
1463
1464    /// Locale provider: invariant by default
1465    fn locale(&self) -> crate::locale::Locale {
1466        crate::locale::Locale::invariant()
1467    }
1468
1469    /// Number of active sheets in the workbook, if known.
1470    fn workbook_sheet_count(&self) -> Option<usize> {
1471        None
1472    }
1473
1474    /// Excel-style 1-based active-sheet index for a sheet name, if known.
1475    fn sheet_index_by_name(&self, _sheet: &str) -> Option<usize> {
1476        None
1477    }
1478
1479    /// Excel-style 1-based active-sheet index for the current formula sheet, if known.
1480    fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
1481        self.sheet_index_by_name(current_sheet)
1482    }
1483
1484    /// Inspect reference metadata without materializing referenced values.
1485    fn inspect_reference(
1486        &self,
1487        _reference: &ReferenceType,
1488        _current_sheet: &str,
1489    ) -> Result<Option<ReferenceInfo>, ExcelError> {
1490        Ok(None)
1491    }
1492
1493    /// Retrieve formula text for a concrete cell, if that cell stores a formula.
1494    fn formula_text_at_cell(&self, _cell: CellRef) -> Result<Option<String>, ExcelError> {
1495        Ok(None)
1496    }
1497
1498    /// Clock provider for volatile date/time builtins.
1499    ///
1500    /// Default when `system-clock` feature is enabled: `SystemClock(Local)` for
1501    /// Excel-compatible wall-clock behaviour.
1502    ///
1503    /// Default when `system-clock` is **disabled** (portable wasm profile): a
1504    /// UTC epoch `FixedClock`. Implementors that need real wall-clock time should
1505    /// override this method and inject an appropriate `ClockProvider`.
1506    fn clock(&self) -> &dyn crate::timezone::ClockProvider {
1507        #[cfg(feature = "system-clock")]
1508        {
1509            static DEFAULT_CLOCK: std::sync::OnceLock<crate::timezone::SystemClock> =
1510                std::sync::OnceLock::new();
1511            DEFAULT_CLOCK.get_or_init(|| {
1512                crate::timezone::SystemClock::new(crate::timezone::TimeZoneSpec::default())
1513            })
1514        }
1515        #[cfg(not(feature = "system-clock"))]
1516        {
1517            static DEFAULT_CLOCK: std::sync::OnceLock<crate::timezone::FixedClock> =
1518                std::sync::OnceLock::new();
1519            DEFAULT_CLOCK.get_or_init(|| {
1520                crate::timezone::FixedClock::new(
1521                    chrono::DateTime::UNIX_EPOCH,
1522                    crate::timezone::TimeZoneSpec::Utc,
1523                )
1524            })
1525        }
1526    }
1527
1528    /// Timezone spec for date/time functions.
1529    ///
1530    /// Default: derived from `clock()`.
1531    fn timezone(&self) -> &crate::timezone::TimeZoneSpec {
1532        self.clock().timezone()
1533    }
1534
1535    /// Volatile granularity. Default Always for backwards compatibility.
1536    fn volatile_level(&self) -> VolatileLevel {
1537        VolatileLevel::Always
1538    }
1539
1540    /// A stable workbook seed for RNG composition.
1541    fn workbook_seed(&self) -> u64 {
1542        0xF0F0_D0D0_AAAA_5555
1543    }
1544
1545    /// Recalc epoch that increments on each full recalc when appropriate.
1546    fn recalc_epoch(&self) -> u64 {
1547        0
1548    }
1549
1550    /* ─────────────── Future-proof IO/backends hooks (default no-op) ─────────────── */
1551
1552    /// Optional: Return the min/max used rows for a set of columns on a sheet.
1553    /// When None, the backend does not provide used-region hints.
1554    fn used_rows_for_columns(
1555        &self,
1556        _sheet: &str,
1557        _start_col: u32,
1558        _end_col: u32,
1559    ) -> Option<(u32, u32)> {
1560        None
1561    }
1562
1563    /// Optional: Return the min/max used columns for a set of rows on a sheet.
1564    /// When None, the backend does not provide used-region hints.
1565    fn used_cols_for_rows(
1566        &self,
1567        _sheet: &str,
1568        _start_row: u32,
1569        _end_row: u32,
1570    ) -> Option<(u32, u32)> {
1571        None
1572    }
1573
1574    /// Optional: Physical sheet bounds (max rows, max cols) if known.
1575    fn sheet_bounds(&self, _sheet: &str) -> Option<(u32, u32)> {
1576        None
1577    }
1578
1579    /// Monotonic identifier for the current data snapshot; increments on mutation.
1580    fn data_snapshot_id(&self) -> u64 {
1581        0
1582    }
1583
1584    /// Backend capability advertisement for IO/adapters.
1585    fn backend_caps(&self) -> BackendCaps {
1586        BackendCaps::default()
1587    }
1588
1589    // Flats removed
1590
1591    /// Workbook date system selection (1900 vs 1904).
1592    /// Defaults to 1900 for compatibility.
1593    fn date_system(&self) -> crate::engine::DateSystem {
1594        crate::engine::DateSystem::Excel1900
1595    }
1596
1597    /// Optional: Build or fetch an exact-match lookup index over an Arrow-backed view.
1598    /// Implementations should return None if not supported or unsafe.
1599    fn build_lookup_index(
1600        &self,
1601        _view: &RangeView<'_>,
1602        _axis: LookupAxis,
1603    ) -> Option<std::sync::Arc<LookupIndex>> {
1604        None
1605    }
1606
1607    /// Optional: Build or fetch a cached boolean mask for a criterion over an Arrow-backed view.
1608    /// Implementations should return None if not supported.
1609    fn build_criteria_mask(
1610        &self,
1611        _view: &RangeView<'_>,
1612        _col_in_view: usize,
1613        _pred: &crate::args::CriteriaPredicate,
1614    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1615        None
1616    }
1617
1618    /// Optional: Build row-visibility mask aligned to `view` rows.
1619    /// Returns None if not supported by the underlying context.
1620    fn build_row_visibility_mask(
1621        &self,
1622        _view: &RangeView<'_>,
1623        _mode: VisibilityMaskMode,
1624    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1625        None
1626    }
1627}
1628
1629/// Minimal backend capability descriptor for planning and adapters.
1630#[derive(Copy, Clone, Debug, Default)]
1631pub struct BackendCaps {
1632    /// Provides lazy access (// TODO REMOVE?)
1633    pub streaming: bool,
1634    /// Can compute used-region for rows/columns
1635    pub used_region: bool,
1636    /// Supports write-back mutations via external sink
1637    pub write: bool,
1638    /// Provides table metadata/streaming beyond basic column access
1639    pub tables: bool,
1640    /// May provide asynchronous/lazy remote streams (reserved)
1641    pub async_stream: bool,
1642}
1643
1644/* ───────────────────── FunctionContext (narrow) ───────────────────── */
1645
1646#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1647pub enum VolatileLevel {
1648    /// Value can change at any edit; seed excludes recalc_epoch by default.
1649    Always,
1650    /// Value changes per recalculation; seed should include recalc_epoch.
1651    OnRecalc,
1652    /// Value changes per open; seed uses only workbook_seed.
1653    OnOpen,
1654}
1655
1656/// Minimal context exposed to functions (no engine/graph APIs)
1657pub trait FunctionContext<'ctx> {
1658    fn locale(&self) -> crate::locale::Locale;
1659    fn timezone(&self) -> &crate::timezone::TimeZoneSpec;
1660    fn clock(&self) -> &dyn crate::timezone::ClockProvider;
1661    fn thread_pool(&self) -> Option<&std::sync::Arc<rayon::ThreadPool>>;
1662    /// Returns the optional shared cancellation handle for this evaluation.
1663    ///
1664    /// Custom function authors should retrieve this once before a hot loop and
1665    /// poll [`crate::engine::CancelToken::is_cancelled`] periodically. Cloning
1666    /// the handle shares the same signal without allocating.
1667    fn cancellation_token(&self) -> Option<crate::engine::CancelToken>;
1668    fn chunk_hint(&self) -> Option<usize>;
1669
1670    /// Current formula sheet name.
1671    fn current_sheet(&self) -> &str;
1672
1673    fn workbook_sheet_count(&self) -> Option<usize> {
1674        None
1675    }
1676
1677    fn sheet_index_by_name(&self, _sheet: &str) -> Option<usize> {
1678        None
1679    }
1680
1681    fn current_sheet_index(&self) -> Option<usize> {
1682        self.sheet_index_by_name(self.current_sheet())
1683    }
1684
1685    fn inspect_reference(
1686        &self,
1687        _reference: &ReferenceType,
1688    ) -> Result<Option<ReferenceInfo>, ExcelError> {
1689        Ok(None)
1690    }
1691
1692    fn formula_text_at_cell(&self, _cell: CellRef) -> Result<Option<String>, ExcelError> {
1693        Ok(None)
1694    }
1695
1696    fn volatile_level(&self) -> VolatileLevel;
1697    fn workbook_seed(&self) -> u64;
1698    fn recalc_epoch(&self) -> u64;
1699    fn current_cell(&self) -> Option<CellRef>;
1700
1701    /// Resolve a reference into a RangeView using the underlying engine context.
1702    fn resolve_range_view(
1703        &self,
1704        _reference: &ReferenceType,
1705        _current_sheet: &str,
1706    ) -> Result<RangeView<'ctx>, ExcelError>;
1707
1708    // Flats removed
1709
1710    /// Deterministic RNG seeded for the current evaluation site and function salt.
1711    fn rng_for_current(&self, fn_salt: u64) -> rand::rngs::SmallRng {
1712        use crate::rng::{compose_seed, small_rng_from_lanes};
1713        let (sheet_id, row, col) = self
1714            .current_cell()
1715            .map(|c| (c.sheet_id as u32, c.coord.row(), c.coord.col()))
1716            .unwrap_or((0, 0, 0));
1717        // Include epoch only for OnRecalc
1718        let epoch = match self.volatile_level() {
1719            VolatileLevel::OnRecalc => self.recalc_epoch(),
1720            _ => 0,
1721        };
1722        let (l0, l1) = compose_seed(self.workbook_seed(), sheet_id, row, col, fn_salt, epoch);
1723        small_rng_from_lanes(l0, l1)
1724    }
1725
1726    /// Workbook date system selection (1900 vs 1904).
1727    fn date_system(&self) -> crate::engine::DateSystem {
1728        crate::engine::DateSystem::Excel1900
1729    }
1730
1731    /// Optional: Build or fetch an exact-match lookup index over an Arrow-backed view.
1732    /// Returns None if not supported by the underlying context.
1733    fn get_lookup_index(
1734        &self,
1735        _view: &RangeView<'_>,
1736        _axis: LookupAxis,
1737    ) -> Option<std::sync::Arc<LookupIndex>> {
1738        None
1739    }
1740
1741    /// Optional: Build or fetch a cached boolean mask for a criterion over an Arrow-backed view.
1742    /// Returns None if not supported by the underlying context.
1743    fn get_criteria_mask(
1744        &self,
1745        _view: &RangeView<'_>,
1746        _col_in_view: usize,
1747        _pred: &crate::args::CriteriaPredicate,
1748    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1749        None
1750    }
1751
1752    /// Optional: Build row-visibility mask aligned to `view` rows.
1753    fn get_row_visibility_mask(
1754        &self,
1755        _view: &RangeView<'_>,
1756        _mode: VisibilityMaskMode,
1757    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1758        None
1759    }
1760}
1761
1762/// Default adapter that wraps an EvaluationContext and provides the narrow FunctionContext.
1763pub struct DefaultFunctionContext<'a> {
1764    pub base: &'a dyn EvaluationContext,
1765    pub current: Option<CellRef>,
1766    pub current_sheet: &'a str,
1767}
1768
1769impl<'a> DefaultFunctionContext<'a> {
1770    pub fn new(
1771        base: &'a dyn EvaluationContext,
1772        current: Option<CellRef>,
1773        current_sheet: &'a str,
1774    ) -> Self {
1775        Self {
1776            base,
1777            current,
1778            current_sheet,
1779        }
1780    }
1781
1782    pub fn new_with_sheet(
1783        base: &'a dyn EvaluationContext,
1784        current: Option<CellRef>,
1785        current_sheet: &'a str,
1786    ) -> Self {
1787        Self::new(base, current, current_sheet)
1788    }
1789}
1790
1791impl<'a> FunctionContext<'a> for DefaultFunctionContext<'a> {
1792    fn locale(&self) -> crate::locale::Locale {
1793        self.base.locale()
1794    }
1795
1796    fn current_sheet(&self) -> &str {
1797        self.current_sheet
1798    }
1799
1800    fn workbook_sheet_count(&self) -> Option<usize> {
1801        self.base.workbook_sheet_count()
1802    }
1803
1804    fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
1805        self.base.sheet_index_by_name(sheet)
1806    }
1807
1808    fn current_sheet_index(&self) -> Option<usize> {
1809        self.base.current_sheet_index(self.current_sheet)
1810    }
1811
1812    fn inspect_reference(
1813        &self,
1814        reference: &ReferenceType,
1815    ) -> Result<Option<ReferenceInfo>, ExcelError> {
1816        self.base.inspect_reference(reference, self.current_sheet)
1817    }
1818
1819    fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
1820        self.base.formula_text_at_cell(cell)
1821    }
1822
1823    fn timezone(&self) -> &crate::timezone::TimeZoneSpec {
1824        self.base.timezone()
1825    }
1826
1827    fn clock(&self) -> &dyn crate::timezone::ClockProvider {
1828        self.base.clock()
1829    }
1830    fn thread_pool(&self) -> Option<&std::sync::Arc<rayon::ThreadPool>> {
1831        self.base.thread_pool()
1832    }
1833    fn cancellation_token(&self) -> Option<crate::engine::CancelToken> {
1834        self.base.cancellation_token()
1835    }
1836    fn chunk_hint(&self) -> Option<usize> {
1837        self.base.chunk_hint()
1838    }
1839
1840    fn volatile_level(&self) -> VolatileLevel {
1841        self.base.volatile_level()
1842    }
1843    fn workbook_seed(&self) -> u64 {
1844        self.base.workbook_seed()
1845    }
1846    fn recalc_epoch(&self) -> u64 {
1847        self.base.recalc_epoch()
1848    }
1849    fn current_cell(&self) -> Option<CellRef> {
1850        self.current
1851    }
1852
1853    fn resolve_range_view(
1854        &self,
1855        reference: &ReferenceType,
1856        current_sheet: &str,
1857    ) -> Result<RangeView<'a>, ExcelError> {
1858        self.base.resolve_range_view(reference, current_sheet)
1859    }
1860
1861    // Flats removed
1862
1863    fn date_system(&self) -> crate::engine::DateSystem {
1864        self.base.date_system()
1865    }
1866
1867    fn get_lookup_index(
1868        &self,
1869        view: &RangeView<'_>,
1870        axis: LookupAxis,
1871    ) -> Option<std::sync::Arc<LookupIndex>> {
1872        self.base.build_lookup_index(view, axis)
1873    }
1874
1875    fn get_criteria_mask(
1876        &self,
1877        view: &RangeView<'_>,
1878        col_in_view: usize,
1879        pred: &crate::args::CriteriaPredicate,
1880    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1881        self.base.build_criteria_mask(view, col_in_view, pred)
1882    }
1883
1884    fn get_row_visibility_mask(
1885        &self,
1886        view: &RangeView<'_>,
1887        mode: VisibilityMaskMode,
1888    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1889        self.base.build_row_visibility_mask(view, mode)
1890    }
1891}