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