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
114impl<'a> std::fmt::Debug for CalcValue<'a> {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        match self {
117            CalcValue::Scalar(v) => f.debug_tuple("Scalar").field(v).finish(),
118            CalcValue::Range(rv) => {
119                let (r, c) = rv.dims();
120                f.debug_tuple("Range").field(&(r, c)).finish()
121            }
122            CalcValue::Callable(_) => f.write_str("Callable(<opaque>)"),
123        }
124    }
125}
126
127impl<'a> CalcValue<'a> {
128    pub fn into_literal(self) -> LiteralValue {
129        match self {
130            CalcValue::Scalar(s) => s,
131            CalcValue::Range(rv) => {
132                let (rows, cols) = rv.dims();
133                if rows == 1 && cols == 1 {
134                    rv.get_cell(0, 0)
135                } else {
136                    let mut data = Vec::with_capacity(rows);
137                    // Use a simple materialization loop for now
138                    // In the future, this should be optimized.
139                    let _ = rv.for_each_row(&mut |row| {
140                        data.push(row.to_vec());
141                        Ok(())
142                    });
143                    LiteralValue::Array(data)
144                }
145            }
146            CalcValue::Callable(_) => LiteralValue::Error(
147                ExcelError::new(ExcelErrorKind::Calc).with_message("LAMBDA value must be invoked"),
148            ),
149        }
150    }
151
152    pub fn as_scalar(&self) -> Option<&LiteralValue> {
153        match self {
154            CalcValue::Scalar(s) => Some(s),
155            _ => None,
156        }
157    }
158
159    pub fn as_range(&self) -> Option<&RangeView<'a>> {
160        match self {
161            CalcValue::Range(r) => Some(r),
162            _ => None,
163        }
164    }
165
166    pub fn as_callable(&self) -> Option<&Arc<dyn CustomCallable>> {
167        match self {
168            CalcValue::Callable(c) => Some(c),
169            _ => None,
170        }
171    }
172
173    pub fn into_owned(self) -> LiteralValue {
174        self.into_literal()
175    }
176}
177
178impl From<CalcValue<'_>> for LiteralValue {
179    fn from(val: CalcValue<'_>) -> Self {
180        val.into_literal()
181    }
182}
183
184impl<'a> PartialEq<LiteralValue> for CalcValue<'a> {
185    fn eq(&self, other: &LiteralValue) -> bool {
186        match self {
187            CalcValue::Scalar(s) => s == other,
188            CalcValue::Range(rv) => match other {
189                LiteralValue::Array(arr) => {
190                    let (rows, cols) = rv.dims();
191                    if arr.len() != rows {
192                        return false;
193                    }
194                    for (r, row) in arr.iter().enumerate() {
195                        if row.len() != cols {
196                            return false;
197                        }
198                        for (c, cell) in row.iter().enumerate() {
199                            if &rv.get_cell(r, c) != cell {
200                                return false;
201                            }
202                        }
203                    }
204                    true
205                }
206                _ => {
207                    let (rows, cols) = rv.dims();
208                    rows == 1 && cols == 1 && &rv.get_cell(0, 0) == other
209                }
210            },
211            CalcValue::Callable(_) => false,
212        }
213    }
214}
215
216impl<'a> PartialEq<CalcValue<'a>> for LiteralValue {
217    fn eq(&self, other: &CalcValue<'a>) -> bool {
218        other == self
219    }
220}
221
222pub enum EvaluatedArg<'a> {
223    LiteralValue(CowValue<'a>),
224    Range(Box<dyn Range>),
225}
226
227enum ArgumentExpr<'a> {
228    Ast(&'a ASTNode),
229    Arena {
230        id: crate::engine::arena::AstNodeId,
231        data_store: &'a crate::engine::arena::DataStore,
232        sheet_registry: &'a crate::engine::sheet_registry::SheetRegistry,
233    },
234}
235
236pub struct ArgumentHandle<'a, 'b> {
237    expr: ArgumentExpr<'a>,
238    interp: &'a Interpreter<'b>,
239    cached_ast: std::cell::OnceCell<ASTNode>,
240    cached_ref: std::cell::OnceCell<ReferenceType>,
241    /// Memoized result of [`Self::value`]. `Function::dispatch` evaluates
242    /// every argument once during schema validation and the function's `eval`
243    /// evaluates it again — without this cache that re-entry compounds to
244    /// 2^depth evaluations of the innermost node for nested non-short-circuit
245    /// calls (measured: depth 12 ⇒ 4096 evaluations). The handle is created
246    /// per call site and per evaluation, so the memo can never go stale
247    /// across recalcs. `value_with_env` is intentionally NOT memoized (the
248    /// local env changes the result).
249    cached_value: std::cell::OnceCell<Result<crate::traits::CalcValue<'b>, ExcelError>>,
250}
251
252impl<'a, 'b> ArgumentHandle<'a, 'b> {
253    pub(crate) fn new(node: &'a ASTNode, interp: &'a Interpreter<'b>) -> Self {
254        Self {
255            expr: ArgumentExpr::Ast(node),
256            interp,
257            cached_ast: std::cell::OnceCell::new(),
258            cached_ref: std::cell::OnceCell::new(),
259            cached_value: std::cell::OnceCell::new(),
260        }
261    }
262
263    pub(crate) fn new_arena(
264        id: crate::engine::arena::AstNodeId,
265        interp: &'a Interpreter<'b>,
266        data_store: &'a crate::engine::arena::DataStore,
267        sheet_registry: &'a crate::engine::sheet_registry::SheetRegistry,
268    ) -> Self {
269        Self {
270            expr: ArgumentExpr::Arena {
271                id,
272                data_store,
273                sheet_registry,
274            },
275            interp,
276            cached_ast: std::cell::OnceCell::new(),
277            cached_ref: std::cell::OnceCell::new(),
278            cached_value: std::cell::OnceCell::new(),
279        }
280    }
281
282    pub fn value(&self) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
283        self.cached_value
284            .get_or_init(|| self.compute_value())
285            .clone()
286    }
287
288    fn compute_value(&self) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
289        match &self.expr {
290            ArgumentExpr::Ast(node) => {
291                if let ASTNodeType::Literal(ref v) = node.node_type {
292                    return Ok(crate::traits::CalcValue::Scalar(v.clone()));
293                }
294                self.interp.evaluate_ast(node)
295            }
296            ArgumentExpr::Arena {
297                id,
298                data_store,
299                sheet_registry,
300            } => self
301                .interp
302                .evaluate_arena_ast(*id, data_store, sheet_registry),
303        }
304    }
305
306    pub fn value_with_env(
307        &self,
308        env: crate::interpreter::LocalEnv,
309    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
310        let scoped = self.interp.with_local_env(env);
311        match &self.expr {
312            ArgumentExpr::Ast(node) => {
313                if let ASTNodeType::Literal(ref v) = node.node_type {
314                    return Ok(crate::traits::CalcValue::Scalar(v.clone()));
315                }
316                scoped.evaluate_ast(node)
317            }
318            ArgumentExpr::Arena {
319                id,
320                data_store,
321                sheet_registry,
322            } => scoped.evaluate_arena_ast(*id, data_store, sheet_registry),
323        }
324    }
325
326    pub fn current_env(&self) -> crate::interpreter::LocalEnv {
327        self.interp.local_env().clone()
328    }
329
330    pub fn inline_array_literal(&self) -> Result<Option<Vec<Vec<LiteralValue>>>, ExcelError> {
331        match &self.expr {
332            ArgumentExpr::Ast(node) => match &node.node_type {
333                ASTNodeType::Literal(LiteralValue::Array(arr)) => Ok(Some(arr.clone())),
334                _ => Ok(None),
335            },
336            ArgumentExpr::Arena {
337                id,
338                data_store,
339                sheet_registry,
340            } => {
341                let node = data_store.get_node(*id).ok_or_else(|| {
342                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
343                })?;
344                match node {
345                    crate::engine::arena::AstNodeData::Literal(vref) => {
346                        match data_store.retrieve_value(*vref) {
347                            LiteralValue::Array(arr) => Ok(Some(arr)),
348                            _ => Ok(None),
349                        }
350                    }
351                    _ => {
352                        // preserve existing behavior: only a literal array (not a computed array)
353                        // is treated as "inline array literal".
354                        let _ = sheet_registry;
355                        Ok(None)
356                    }
357                }
358            }
359        }
360    }
361
362    fn reference_for_eval(&self) -> Result<ReferenceType, ExcelError> {
363        match &self.expr {
364            ArgumentExpr::Ast(node) => match &node.node_type {
365                ASTNodeType::Reference { reference, .. } => {
366                    self.interp.reference_for_current_offset(reference)
367                }
368                ASTNodeType::Function { .. } | ASTNodeType::BinaryOp { .. } => {
369                    self.interp.evaluate_ast_as_reference(node)
370                }
371                _ => Err(ExcelError::new(ExcelErrorKind::Ref)
372                    .with_message("Expected a reference (by-ref argument)")),
373            },
374            ArgumentExpr::Arena {
375                id,
376                data_store,
377                sheet_registry,
378            } => {
379                let node = data_store.get_node(*id).ok_or_else(|| {
380                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
381                })?;
382                match node {
383                    crate::engine::arena::AstNodeData::Reference { ref_type, .. } => {
384                        let reference = data_store
385                            .reconstruct_reference_type_for_eval(ref_type, sheet_registry);
386                        self.interp.reference_for_current_offset(&reference)
387                    }
388                    crate::engine::arena::AstNodeData::Function { .. }
389                    | crate::engine::arena::AstNodeData::BinaryOp { .. } => self
390                        .interp
391                        .evaluate_arena_ast_as_reference(*id, data_store, sheet_registry),
392                    _ => Err(ExcelError::new(ExcelErrorKind::Ref)
393                        .with_message("Expected a reference (by-ref argument)")),
394                }
395            }
396        }
397    }
398
399    pub fn range(&self) -> Result<Box<dyn Range>, ExcelError> {
400        match &self.expr {
401            ArgumentExpr::Ast(node) => match &node.node_type {
402                ASTNodeType::Reference { reference, .. } => {
403                    // Prefer RangeView since it has explicit current-sheet context.
404                    let reference = self.interp.reference_for_current_offset(reference)?;
405                    let view = self
406                        .interp
407                        .context
408                        .resolve_range_view(&reference, self.interp.current_sheet())?;
409                    let (rows, cols) = view.dims();
410                    let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
411                    view.for_each_row(&mut |row| {
412                        let row_data: Vec<LiteralValue> = (0..cols)
413                            .map(|c| row.get(c).cloned().unwrap_or(LiteralValue::Empty))
414                            .collect();
415                        out.push(row_data);
416                        Ok(())
417                    })?;
418                    Ok(Box::new(InMemoryRange::new(out)))
419                }
420                ASTNodeType::Function { .. } | ASTNodeType::BinaryOp { .. } => {
421                    let reference = self.reference_for_eval()?;
422                    let view = self
423                        .interp
424                        .context
425                        .resolve_range_view(&reference, self.interp.current_sheet())?;
426                    let (rows, cols) = view.dims();
427                    let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
428                    view.for_each_row(&mut |row| {
429                        let row_data: Vec<LiteralValue> = (0..cols)
430                            .map(|c| row.get(c).cloned().unwrap_or(LiteralValue::Empty))
431                            .collect();
432                        out.push(row_data);
433                        Ok(())
434                    })?;
435                    Ok(Box::new(InMemoryRange::new(out)))
436                }
437                ASTNodeType::Array(rows) => {
438                    let mut materialized = Vec::new();
439                    for row in rows {
440                        let mut materialized_row = Vec::new();
441                        for cell in row {
442                            materialized_row.push(self.interp.evaluate_ast(cell)?.into_literal());
443                        }
444                        materialized.push(materialized_row);
445                    }
446                    Ok(Box::new(InMemoryRange::new(materialized)))
447                }
448                _ => Err(ExcelError::new(ExcelErrorKind::Ref)
449                    .with_message(format!("Expected a range, got {:?}", node.node_type))),
450            },
451            ArgumentExpr::Arena { id, data_store, .. } => {
452                let node = data_store.get_node(*id).ok_or_else(|| {
453                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
454                })?;
455
456                match node {
457                    crate::engine::arena::AstNodeData::Reference { .. }
458                    | crate::engine::arena::AstNodeData::Function { .. }
459                    | crate::engine::arena::AstNodeData::BinaryOp { .. } => {
460                        let reference = self.reference_for_eval()?;
461                        let view = self
462                            .interp
463                            .context
464                            .resolve_range_view(&reference, self.interp.current_sheet())?;
465                        let (rows, cols) = view.dims();
466                        let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
467                        view.for_each_row(&mut |row| {
468                            let row_data: Vec<LiteralValue> = (0..cols)
469                                .map(|c| row.get(c).cloned().unwrap_or(LiteralValue::Empty))
470                                .collect();
471                            out.push(row_data);
472                            Ok(())
473                        })?;
474                        Ok(Box::new(InMemoryRange::new(out)))
475                    }
476                    crate::engine::arena::AstNodeData::Array { .. } => {
477                        let (rows, cols, elements) =
478                            data_store.get_array_elems(*id).ok_or_else(|| {
479                                ExcelError::new(ExcelErrorKind::Value).with_message("Invalid array")
480                            })?;
481                        let rows_usize = rows as usize;
482                        let cols_usize = cols as usize;
483                        let mut materialized: Vec<Vec<LiteralValue>> =
484                            Vec::with_capacity(rows_usize);
485                        for r in 0..rows_usize {
486                            let mut row = Vec::with_capacity(cols_usize);
487                            for c in 0..cols_usize {
488                                let idx = r * cols_usize + c;
489                                let elem_id = elements.get(idx).copied().ok_or_else(|| {
490                                    ExcelError::new(ExcelErrorKind::Value)
491                                        .with_message("Invalid array")
492                                })?;
493                                let v = self.interp.evaluate_arena_ast(
494                                    elem_id,
495                                    data_store,
496                                    self.sheet_registry(),
497                                )?;
498                                row.push(v.into_literal());
499                            }
500                            materialized.push(row);
501                        }
502                        Ok(Box::new(InMemoryRange::new(materialized)))
503                    }
504                    _ => Err(ExcelError::new(ExcelErrorKind::Ref)
505                        .with_message("Argument cannot be interpreted as a range.")),
506                }
507            }
508        }
509    }
510
511    fn sheet_registry(&self) -> &crate::engine::sheet_registry::SheetRegistry {
512        match &self.expr {
513            ArgumentExpr::Ast(_) => {
514                // Not needed; used only in arena flows.
515                unreachable!("sheet_registry only used for arena ArgumentHandle")
516            }
517            ArgumentExpr::Arena { sheet_registry, .. } => sheet_registry,
518        }
519    }
520
521    /// Resolve as a RangeView (Phase 2 API). Only supports reference arguments.
522    pub fn range_view(&self) -> Result<RangeView<'b>, ExcelError> {
523        match &self.expr {
524            ArgumentExpr::Ast(node) => match &node.node_type {
525                ASTNodeType::Reference { reference, .. } => {
526                    let reference = self.interp.reference_for_current_offset(reference)?;
527                    self.interp
528                        .context
529                        .resolve_range_view(&reference, self.interp.current_sheet())
530                        .map(|v| v.with_cancel_token(self.interp.context.cancellation_token()))
531                }
532                // Treat array literals (LiteralValue::Array) as ranges for RangeView APIs
533                ASTNodeType::Literal(formualizer_common::LiteralValue::Array(arr)) => Ok(
534                    RangeView::from_owned_rows(arr.clone(), self.interp.context.date_system())
535                        .with_cancel_token(self.interp.context.cancellation_token()),
536                ),
537                ASTNodeType::Array(rows) => {
538                    let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows.len());
539                    for r in rows {
540                        let mut row_vals = Vec::with_capacity(r.len());
541                        for cell in r {
542                            row_vals.push(self.interp.evaluate_ast(cell)?.into_literal());
543                        }
544                        out.push(row_vals);
545                    }
546                    Ok(
547                        RangeView::from_owned_rows(out, self.interp.context.date_system())
548                            .with_cancel_token(self.interp.context.cancellation_token()),
549                    )
550                }
551                ASTNodeType::Function { .. } | ASTNodeType::BinaryOp { .. } => {
552                    let reference = self.reference_for_eval()?;
553                    self.interp
554                        .context
555                        .resolve_range_view(&reference, self.interp.current_sheet())
556                        .map(|v| v.with_cancel_token(self.interp.context.cancellation_token()))
557                }
558                _ => Err(ExcelError::new(ExcelErrorKind::Ref)
559                    .with_message("Argument cannot be interpreted as a range.")),
560            },
561            ArgumentExpr::Arena {
562                id,
563                data_store,
564                sheet_registry,
565            } => {
566                let node = data_store.get_node(*id).ok_or_else(|| {
567                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
568                })?;
569
570                match node {
571                    crate::engine::arena::AstNodeData::Reference { .. }
572                    | crate::engine::arena::AstNodeData::Function { .. }
573                    | crate::engine::arena::AstNodeData::BinaryOp { .. } => {
574                        let reference = self.reference_for_eval()?;
575                        self.interp
576                            .context
577                            .resolve_range_view(&reference, self.interp.current_sheet())
578                            .map(|v| v.with_cancel_token(self.interp.context.cancellation_token()))
579                    }
580                    crate::engine::arena::AstNodeData::Literal(vref) => {
581                        match data_store.retrieve_value(*vref) {
582                            LiteralValue::Array(arr) => Ok(RangeView::from_owned_rows(
583                                arr,
584                                self.interp.context.date_system(),
585                            )
586                            .with_cancel_token(self.interp.context.cancellation_token())),
587                            _ => Err(ExcelError::new(ExcelErrorKind::Ref)
588                                .with_message("Argument cannot be interpreted as a range.")),
589                        }
590                    }
591                    crate::engine::arena::AstNodeData::Array { .. } => {
592                        let (rows, cols, elements) =
593                            data_store.get_array_elems(*id).ok_or_else(|| {
594                                ExcelError::new(ExcelErrorKind::Value).with_message("Invalid array")
595                            })?;
596
597                        let rows_usize = rows as usize;
598                        let cols_usize = cols as usize;
599                        let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows_usize);
600                        for r in 0..rows_usize {
601                            let mut row = Vec::with_capacity(cols_usize);
602                            for c in 0..cols_usize {
603                                let idx = r * cols_usize + c;
604                                let elem_id = elements.get(idx).copied().ok_or_else(|| {
605                                    ExcelError::new(ExcelErrorKind::Value)
606                                        .with_message("Invalid array")
607                                })?;
608                                let v = self.interp.evaluate_arena_ast(
609                                    elem_id,
610                                    data_store,
611                                    sheet_registry,
612                                )?;
613                                row.push(v.into_literal());
614                            }
615                            out.push(row);
616                        }
617                        Ok(
618                            RangeView::from_owned_rows(out, self.interp.context.date_system())
619                                .with_cancel_token(self.interp.context.cancellation_token()),
620                        )
621                    }
622                    _ => Err(ExcelError::new(ExcelErrorKind::Ref)
623                        .with_message("Argument cannot be interpreted as a range.")),
624                }
625            }
626        }
627    }
628
629    pub fn value_or_range(&self) -> Result<EvaluatedArg<'_>, ExcelError> {
630        self.range().map(EvaluatedArg::Range).or_else(|_| {
631            self.value()
632                .map(|cv| EvaluatedArg::LiteralValue(Cow::Owned(cv.into_literal())))
633        })
634    }
635
636    /// Lazily iterate values for this argument in row-major expansion order.
637    /// - Reference: stream via RangeView (row-major)
638    /// - Array literal: evaluate each element lazily per cell
639    /// - Scalar/other expressions: a single value
640    pub fn lazy_values_owned(
641        &'a self,
642    ) -> Result<Box<dyn Iterator<Item = LiteralValue> + 'a>, ExcelError> {
643        match &self.expr {
644            ArgumentExpr::Ast(node) => match &node.node_type {
645                ASTNodeType::Reference { .. } => {
646                    let view = self.range_view()?;
647                    let mut values: Vec<LiteralValue> = Vec::new();
648                    view.for_each_cell(&mut |v| {
649                        values.push(v.clone());
650                        Ok(())
651                    })?;
652                    Ok(Box::new(values.into_iter()))
653                }
654                ASTNodeType::Array(rows) => {
655                    struct ArrayEvalIter<'a, 'b> {
656                        rows: &'a [Vec<ASTNode>],
657                        r: usize,
658                        c: usize,
659                        interp: &'a Interpreter<'b>,
660                    }
661                    impl<'a, 'b> Iterator for ArrayEvalIter<'a, 'b> {
662                        type Item = LiteralValue;
663                        fn next(&mut self) -> Option<Self::Item> {
664                            if self.rows.is_empty() {
665                                return None;
666                            }
667                            let rows = self.rows;
668                            let mut r = self.r;
669                            let mut c = self.c;
670                            if r >= rows.len() {
671                                return None;
672                            }
673                            let node = &rows[r][c];
674                            // advance indices
675                            c += 1;
676                            if c >= rows[r].len() {
677                                r += 1;
678                                c = 0;
679                            }
680                            self.r = r;
681                            self.c = c;
682                            match self.interp.evaluate_ast(node) {
683                                Ok(cv) => Some(cv.into_literal()),
684                                Err(e) => Some(LiteralValue::Error(e)),
685                            }
686                        }
687                    }
688                    let it = ArrayEvalIter {
689                        rows,
690                        r: 0,
691                        c: 0,
692                        interp: self.interp,
693                    };
694                    Ok(Box::new(it))
695                }
696                _ => {
697                    // Single value expression
698                    let v = self.value()?.into_literal();
699                    Ok(Box::new(std::iter::once(v)))
700                }
701            },
702            ArgumentExpr::Arena {
703                id,
704                data_store,
705                sheet_registry,
706            } => {
707                let node = data_store.get_node(*id).ok_or_else(|| {
708                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
709                })?;
710
711                match node {
712                    crate::engine::arena::AstNodeData::Reference { .. } => {
713                        let view = self.range_view()?;
714                        let mut values: Vec<LiteralValue> = Vec::new();
715                        view.for_each_cell(&mut |v| {
716                            values.push(v.clone());
717                            Ok(())
718                        })?;
719                        Ok(Box::new(values.into_iter()))
720                    }
721                    crate::engine::arena::AstNodeData::Array { .. } => {
722                        let (rows, cols, elements) =
723                            data_store.get_array_elems(*id).ok_or_else(|| {
724                                ExcelError::new(ExcelErrorKind::Value).with_message("Invalid array")
725                            })?;
726
727                        struct ArenaArrayEvalIter<'a, 'b> {
728                            elements: &'a [crate::engine::arena::AstNodeId],
729                            idx: usize,
730                            interp: &'a Interpreter<'b>,
731                            data_store: &'a crate::engine::arena::DataStore,
732                            sheet_registry: &'a crate::engine::sheet_registry::SheetRegistry,
733                        }
734
735                        impl<'a, 'b> Iterator for ArenaArrayEvalIter<'a, 'b> {
736                            type Item = LiteralValue;
737
738                            fn next(&mut self) -> Option<Self::Item> {
739                                let id = self.elements.get(self.idx).copied()?;
740                                self.idx += 1;
741                                match self.interp.evaluate_arena_ast(
742                                    id,
743                                    self.data_store,
744                                    self.sheet_registry,
745                                ) {
746                                    Ok(cv) => Some(cv.into_literal()),
747                                    Err(e) => Some(LiteralValue::Error(e)),
748                                }
749                            }
750                        }
751
752                        let _ = (rows, cols);
753                        let it = ArenaArrayEvalIter {
754                            elements,
755                            idx: 0,
756                            interp: self.interp,
757                            data_store,
758                            sheet_registry,
759                        };
760                        Ok(Box::new(it))
761                    }
762                    _ => {
763                        let v = self
764                            .interp
765                            .evaluate_arena_ast(*id, data_store, sheet_registry)?;
766                        Ok(Box::new(std::iter::once(v.into_literal())))
767                    }
768                }
769            }
770        }
771    }
772
773    pub fn ast(&self) -> &ASTNode {
774        match &self.expr {
775            ArgumentExpr::Ast(node) => node,
776            ArgumentExpr::Arena {
777                id,
778                data_store,
779                sheet_registry,
780            } => self.cached_ast.get_or_init(|| {
781                data_store
782                    .retrieve_ast(*id, sheet_registry)
783                    .unwrap_or_else(|| ASTNode {
784                        node_type: ASTNodeType::Literal(LiteralValue::Error(
785                            ExcelError::new(ExcelErrorKind::Value)
786                                .with_message("Missing formula AST"),
787                        )),
788                        source_token: None,
789                        contains_volatile: false,
790                    })
791            }),
792        }
793    }
794
795    /// Returns the raw reference from the AST when this argument is a reference.
796    /// This does not evaluate the reference or materialize values.
797    pub fn as_reference(&self) -> Result<&ReferenceType, ExcelError> {
798        match &self.expr {
799            ArgumentExpr::Ast(node) => match &node.node_type {
800                ASTNodeType::Reference { reference, .. } => Ok(reference),
801                _ => Err(ExcelError::new(ExcelErrorKind::Ref)
802                    .with_message("Expected a reference (by-ref argument)")),
803            },
804            ArgumentExpr::Arena { .. } => {
805                let reference = self.reference_for_eval()?;
806                Ok(self.cached_ref.get_or_init(|| reference))
807            }
808        }
809    }
810
811    /// Returns a `ReferenceType` if this argument is a reference or a function that
812    /// can yield a reference via `eval_reference`. Materializes no values.
813    pub fn as_reference_or_eval(&self) -> Result<ReferenceType, ExcelError> {
814        match &self.expr {
815            ArgumentExpr::Ast(node) => match &node.node_type {
816                ASTNodeType::Reference { reference, .. } => {
817                    self.interp.reference_for_current_offset(reference)
818                }
819                ASTNodeType::Function { .. } | ASTNodeType::BinaryOp { .. } => {
820                    self.interp.evaluate_ast_as_reference(node)
821                }
822                _ => Err(ExcelError::new(ExcelErrorKind::Ref)
823                    .with_message("Argument is not a reference")),
824            },
825            ArgumentExpr::Arena {
826                id,
827                data_store,
828                sheet_registry,
829            } => {
830                let node = data_store.get_node(*id).ok_or_else(|| {
831                    ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
832                })?;
833
834                match node {
835                    crate::engine::arena::AstNodeData::Reference { .. } => {
836                        self.reference_for_eval()
837                    }
838                    crate::engine::arena::AstNodeData::Function { .. }
839                    | crate::engine::arena::AstNodeData::BinaryOp { .. } => self
840                        .interp
841                        .evaluate_arena_ast_as_reference(*id, data_store, sheet_registry),
842                    _ => Err(ExcelError::new(ExcelErrorKind::Ref)
843                        .with_message("Argument is not a reference")),
844                }
845            }
846        }
847    }
848
849    /* tiny validator helper for macro */
850    pub fn matches_kind(&self, k: formualizer_common::ArgKind) -> Result<bool, ExcelError> {
851        Ok(match k {
852            formualizer_common::ArgKind::Any => true,
853            formualizer_common::ArgKind::Range => self.range().is_ok(),
854            formualizer_common::ArgKind::Number => matches!(
855                self.value()?.into_literal(),
856                LiteralValue::Number(_) | LiteralValue::Int(_)
857            ),
858            formualizer_common::ArgKind::Text => {
859                matches!(self.value()?.into_literal(), LiteralValue::Text(_))
860            }
861            formualizer_common::ArgKind::Logical => {
862                matches!(self.value()?.into_literal(), LiteralValue::Boolean(_))
863            }
864        })
865    }
866}
867
868/* simple Vec-backed range */
869#[derive(Debug, Clone)]
870pub struct InMemoryRange {
871    data: Vec<Vec<LiteralValue>>,
872}
873impl InMemoryRange {
874    pub fn new(d: Vec<Vec<LiteralValue>>) -> Self {
875        Self { data: d }
876    }
877}
878impl Range for InMemoryRange {
879    fn get(&self, r: usize, c: usize) -> Result<LiteralValue, ExcelError> {
880        Ok(self
881            .data
882            .get(r)
883            .and_then(|row| row.get(c))
884            .cloned()
885            .unwrap_or(LiteralValue::Empty))
886    }
887    fn dimensions(&self) -> (usize, usize) {
888        (self.data.len(), self.data.first().map_or(0, |r| r.len()))
889    }
890    fn as_any(&self) -> &dyn Any {
891        self
892    }
893}
894
895/* ───────────────────────── Table abstraction ───────────────────────── */
896
897pub trait Table: Debug + Send + Sync {
898    fn get_cell(&self, row: usize, column: &str) -> Result<LiteralValue, ExcelError>;
899    fn get_column(&self, column: &str) -> Result<Box<dyn Range>, ExcelError>;
900    /// Ordered list of column names
901    fn columns(&self) -> Vec<String> {
902        vec![]
903    }
904    /// Number of data rows (excluding headers/totals)
905    fn data_height(&self) -> usize {
906        0
907    }
908    /// Whether the table has a header row
909    fn has_headers(&self) -> bool {
910        false
911    }
912    /// Whether the table has a totals row
913    fn has_totals(&self) -> bool {
914        false
915    }
916    /// Headers row as a 1xW range
917    fn headers_row(&self) -> Option<Box<dyn Range>> {
918        None
919    }
920    /// Totals row as a 1xW range, if present
921    fn totals_row(&self) -> Option<Box<dyn Range>> {
922        None
923    }
924    /// Entire data body as HxW range
925    fn data_body(&self) -> Option<Box<dyn Range>> {
926        None
927    }
928    fn clone_box(&self) -> Box<dyn Table>;
929}
930impl Table for Box<dyn Table> {
931    fn get_cell(&self, r: usize, c: &str) -> Result<LiteralValue, ExcelError> {
932        (**self).get_cell(r, c)
933    }
934    fn get_column(&self, c: &str) -> Result<Box<dyn Range>, ExcelError> {
935        (**self).get_column(c)
936    }
937    fn columns(&self) -> Vec<String> {
938        (**self).columns()
939    }
940    fn data_height(&self) -> usize {
941        (**self).data_height()
942    }
943    fn has_headers(&self) -> bool {
944        (**self).has_headers()
945    }
946    fn has_totals(&self) -> bool {
947        (**self).has_totals()
948    }
949    fn headers_row(&self) -> Option<Box<dyn Range>> {
950        (**self).headers_row()
951    }
952    fn totals_row(&self) -> Option<Box<dyn Range>> {
953        (**self).totals_row()
954    }
955    fn data_body(&self) -> Option<Box<dyn Range>> {
956        (**self).data_body()
957    }
958    fn clone_box(&self) -> Box<dyn Table> {
959        (**self).clone_box()
960    }
961}
962
963/* ─────────────────────── Resolver super-trait ─────────────────────── */
964
965pub trait ReferenceResolver: Send + Sync {
966    fn resolve_cell_reference(
967        &self,
968        sheet: Option<&str>,
969        row: u32,
970        col: u32,
971    ) -> Result<LiteralValue, ExcelError>;
972}
973pub trait RangeResolver: Send + Sync {
974    fn resolve_range_reference(
975        &self,
976        sheet: Option<&str>,
977        sr: Option<u32>,
978        sc: Option<u32>,
979        er: Option<u32>,
980        ec: Option<u32>,
981    ) -> Result<Box<dyn Range>, ExcelError>;
982}
983pub trait NamedRangeResolver: Send + Sync {
984    fn resolve_named_range_reference(
985        &self,
986        name: &str,
987    ) -> Result<Vec<Vec<LiteralValue>>, ExcelError>;
988}
989pub trait TableResolver: Send + Sync {
990    fn resolve_table_reference(
991        &self,
992        tref: &formualizer_parse::parser::TableReference,
993    ) -> Result<Box<dyn Table>, ExcelError>;
994}
995
996pub trait SourceResolver: Send + Sync {
997    fn source_scalar_version(&self, _name: &str) -> Option<u64> {
998        None
999    }
1000
1001    fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
1002        Err(ExcelError::new(ExcelErrorKind::NImpl)
1003            .with_message(format!("Source scalar not supported: {name}")))
1004    }
1005
1006    fn source_table_version(&self, _name: &str) -> Option<u64> {
1007        None
1008    }
1009
1010    fn resolve_source_table(&self, name: &str) -> Result<Box<dyn Table>, ExcelError> {
1011        Err(ExcelError::new(ExcelErrorKind::NImpl)
1012            .with_message(format!("Source table not supported: {name}")))
1013    }
1014}
1015
1016pub trait Resolver: ReferenceResolver + RangeResolver + NamedRangeResolver + TableResolver {
1017    fn resolve_range_like(&self, r: &ReferenceType) -> Result<Box<dyn Range>, ExcelError> {
1018        match r {
1019            ReferenceType::Range {
1020                sheet,
1021                start_row,
1022                start_col,
1023                end_row,
1024                end_col,
1025                ..
1026            } => self.resolve_range_reference(
1027                sheet.as_deref(),
1028                *start_row,
1029                *start_col,
1030                *end_row,
1031                *end_col,
1032            ),
1033            ReferenceType::External(_) => Err(ExcelError::new(ExcelErrorKind::NImpl)
1034                .with_message("External references are not supported by Resolver".to_string())),
1035            ReferenceType::Table(tref) => {
1036                let t = self.resolve_table_reference(tref)?;
1037                match &tref.specifier {
1038                    Some(TableSpecifier::Column(c)) => t.get_column(c),
1039                    Some(TableSpecifier::ColumnRange(start, end)) => {
1040                        // Build a rectangular range from start..=end columns in table order
1041                        let cols = t.columns();
1042                        let start_key = start.to_lowercase();
1043                        let end_key = end.to_lowercase();
1044                        let start_idx = cols.iter().position(|n| n.to_lowercase() == start_key);
1045                        let end_idx = cols.iter().position(|n| n.to_lowercase() == end_key);
1046                        if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
1047                            if si > ei {
1048                                std::mem::swap(&mut si, &mut ei);
1049                            }
1050                            // Materialize by stacking columns into a 2D array
1051                            let h = t.data_height();
1052                            let w = ei - si + 1;
1053                            let mut rows = vec![vec![LiteralValue::Empty; w]; h];
1054                            for (offset, ci) in (si..=ei).enumerate() {
1055                                let cname = &cols[ci];
1056                                let col_range = t.get_column(cname)?;
1057                                let (rh, _) = col_range.dimensions();
1058                                for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
1059                                    row[offset] = col_range.get(r, 0)?;
1060                                }
1061                            }
1062                            Ok(Box::new(InMemoryRange::new(rows)))
1063                        } else {
1064                            Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
1065                                "Column range refers to unknown column(s)".to_string(),
1066                            ))
1067                        }
1068                    }
1069                    Some(TableSpecifier::SpecialItem(
1070                        formualizer_parse::parser::SpecialItem::Headers,
1071                    )) => {
1072                        if let Some(h) = t.headers_row() {
1073                            Ok(h)
1074                        } else {
1075                            Ok(Box::new(InMemoryRange::new(vec![])))
1076                        }
1077                    }
1078                    Some(TableSpecifier::SpecialItem(
1079                        formualizer_parse::parser::SpecialItem::Totals,
1080                    )) => {
1081                        if let Some(tr) = t.totals_row() {
1082                            Ok(tr)
1083                        } else {
1084                            Ok(Box::new(InMemoryRange::new(vec![])))
1085                        }
1086                    }
1087                    Some(TableSpecifier::SpecialItem(
1088                        formualizer_parse::parser::SpecialItem::Data,
1089                    )) => {
1090                        if let Some(body) = t.data_body() {
1091                            Ok(body)
1092                        } else {
1093                            Ok(Box::new(InMemoryRange::new(vec![])))
1094                        }
1095                    }
1096                    Some(TableSpecifier::SpecialItem(
1097                        formualizer_parse::parser::SpecialItem::All,
1098                    )) => {
1099                        // Equivalent to TableSpecifier::All handling
1100                        let mut out: Vec<Vec<LiteralValue>> = Vec::new();
1101                        if let Some(h) = t.headers_row() {
1102                            out.extend(h.iter_rows());
1103                        }
1104                        if let Some(body) = t.data_body() {
1105                            out.extend(body.iter_rows());
1106                        }
1107                        if let Some(tr) = t.totals_row() {
1108                            out.extend(tr.iter_rows());
1109                        }
1110                        Ok(Box::new(InMemoryRange::new(out)))
1111                    }
1112                    Some(TableSpecifier::SpecialItem(
1113                        formualizer_parse::parser::SpecialItem::ThisRow,
1114                    )) => Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1115                        "@ (This Row) requires table-aware context; not yet supported".to_string(),
1116                    )),
1117                    Some(TableSpecifier::All) => {
1118                        // Concatenate headers (if any), data, totals (if any)
1119                        let mut out: Vec<Vec<LiteralValue>> = Vec::new();
1120                        if let Some(h) = t.headers_row() {
1121                            out.extend(h.iter_rows());
1122                        }
1123                        if let Some(body) = t.data_body() {
1124                            out.extend(body.iter_rows());
1125                        }
1126                        if let Some(tr) = t.totals_row() {
1127                            out.extend(tr.iter_rows());
1128                        }
1129                        Ok(Box::new(InMemoryRange::new(out)))
1130                    }
1131                    Some(TableSpecifier::Data) => {
1132                        if let Some(body) = t.data_body() {
1133                            Ok(body)
1134                        } else {
1135                            Ok(Box::new(InMemoryRange::new(vec![])))
1136                        }
1137                    }
1138                    // Defer complex combinations and row selectors for tranche 1
1139                    Some(TableSpecifier::Combination(_)) => Err(ExcelError::new(
1140                        ExcelErrorKind::NImpl,
1141                    )
1142                    .with_message("Complex structured references not yet supported".to_string())),
1143                    Some(TableSpecifier::Row(_)) => Err(ExcelError::new(ExcelErrorKind::NImpl)
1144                        .with_message("Row selectors (@/index) not yet supported".to_string())),
1145                    Some(TableSpecifier::Headers) | Some(TableSpecifier::Totals) => {
1146                        Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1147                            "Legacy Headers/Totals variants not used; use SpecialItem".to_string(),
1148                        ))
1149                    }
1150                    None => Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
1151                        "Table reference without specifier is unsupported".to_string(),
1152                    )),
1153                }
1154            }
1155            ReferenceType::NamedRange(n) => {
1156                let v = self.resolve_named_range_reference(n)?;
1157                Ok(Box::new(InMemoryRange::new(v)))
1158            }
1159            ReferenceType::Cell {
1160                sheet, row, col, ..
1161            } => {
1162                let v = self.resolve_cell_reference(sheet.as_deref(), *row, *col)?;
1163                Ok(Box::new(InMemoryRange::new(vec![vec![v]])))
1164            }
1165            ReferenceType::Cell3D { .. } | ReferenceType::Range3D { .. } => {
1166                Err(ExcelError::new(ExcelErrorKind::NImpl)
1167                    .with_message("3D references are not yet supported".to_string()))
1168            }
1169        }
1170    }
1171}
1172
1173/* ───────────────────── EvaluationContext = Resolver+Fns ───────────── */
1174
1175pub trait FunctionProvider: Send + Sync {
1176    fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>>;
1177}
1178
1179pub trait EvaluationContext: Resolver + FunctionProvider + SourceResolver {
1180    /// Get access to the shared thread pool for parallel evaluation
1181    /// Returns None if parallel evaluation is disabled or unavailable
1182    fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
1183        None
1184    }
1185
1186    /// Optional cancellation token. When Some, long-running operations should periodically abort.
1187    fn cancellation_token(&self) -> Option<Arc<std::sync::atomic::AtomicBool>> {
1188        None
1189    }
1190
1191    /// Optional chunk size hint for streaming visitors.
1192    fn chunk_hint(&self) -> Option<usize> {
1193        None
1194    }
1195
1196    /// Resolve a reference into a `RangeView` with clear bounds.
1197    /// Implementations should resolve un/partially bounded references using used-region.
1198    fn resolve_range_view<'c>(
1199        &'c self,
1200        _reference: &ReferenceType,
1201        _current_sheet: &str,
1202    ) -> Result<RangeView<'c>, ExcelError> {
1203        Err(ExcelError::new(ExcelErrorKind::NImpl))
1204    }
1205
1206    /// Resolve a single-cell reference as a scalar value.
1207    ///
1208    /// Default implementation preserves existing reference semantics by routing through
1209    /// `resolve_range_view` and extracting a 1x1 value.
1210    fn resolve_cell_reference_value(
1211        &self,
1212        sheet: Option<&str>,
1213        row: u32,
1214        col: u32,
1215        current_sheet: &str,
1216    ) -> Result<LiteralValue, ExcelError> {
1217        let reference = ReferenceType::Cell {
1218            sheet: sheet.map(str::to_string),
1219            row,
1220            col,
1221            row_abs: true,
1222            col_abs: true,
1223        };
1224        let view = self.resolve_range_view(&reference, current_sheet)?;
1225        Ok(view.as_1x1().unwrap_or(LiteralValue::Empty))
1226    }
1227
1228    /// Locale provider: invariant by default
1229    fn locale(&self) -> crate::locale::Locale {
1230        crate::locale::Locale::invariant()
1231    }
1232
1233    /// Number of active sheets in the workbook, if known.
1234    fn workbook_sheet_count(&self) -> Option<usize> {
1235        None
1236    }
1237
1238    /// Excel-style 1-based active-sheet index for a sheet name, if known.
1239    fn sheet_index_by_name(&self, _sheet: &str) -> Option<usize> {
1240        None
1241    }
1242
1243    /// Excel-style 1-based active-sheet index for the current formula sheet, if known.
1244    fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
1245        self.sheet_index_by_name(current_sheet)
1246    }
1247
1248    /// Inspect reference metadata without materializing referenced values.
1249    fn inspect_reference(
1250        &self,
1251        _reference: &ReferenceType,
1252        _current_sheet: &str,
1253    ) -> Result<Option<ReferenceInfo>, ExcelError> {
1254        Ok(None)
1255    }
1256
1257    /// Retrieve formula text for a concrete cell, if that cell stores a formula.
1258    fn formula_text_at_cell(&self, _cell: CellRef) -> Result<Option<String>, ExcelError> {
1259        Ok(None)
1260    }
1261
1262    /// Clock provider for volatile date/time builtins.
1263    ///
1264    /// Default when `system-clock` feature is enabled: `SystemClock(Local)` for
1265    /// Excel-compatible wall-clock behaviour.
1266    ///
1267    /// Default when `system-clock` is **disabled** (portable wasm profile): a
1268    /// UTC epoch `FixedClock`. Implementors that need real wall-clock time should
1269    /// override this method and inject an appropriate `ClockProvider`.
1270    fn clock(&self) -> &dyn crate::timezone::ClockProvider {
1271        #[cfg(feature = "system-clock")]
1272        {
1273            static DEFAULT_CLOCK: std::sync::OnceLock<crate::timezone::SystemClock> =
1274                std::sync::OnceLock::new();
1275            DEFAULT_CLOCK.get_or_init(|| {
1276                crate::timezone::SystemClock::new(crate::timezone::TimeZoneSpec::default())
1277            })
1278        }
1279        #[cfg(not(feature = "system-clock"))]
1280        {
1281            static DEFAULT_CLOCK: std::sync::OnceLock<crate::timezone::FixedClock> =
1282                std::sync::OnceLock::new();
1283            DEFAULT_CLOCK.get_or_init(|| {
1284                crate::timezone::FixedClock::new(
1285                    chrono::DateTime::UNIX_EPOCH,
1286                    crate::timezone::TimeZoneSpec::Utc,
1287                )
1288            })
1289        }
1290    }
1291
1292    /// Timezone spec for date/time functions.
1293    ///
1294    /// Default: derived from `clock()`.
1295    fn timezone(&self) -> &crate::timezone::TimeZoneSpec {
1296        self.clock().timezone()
1297    }
1298
1299    /// Volatile granularity. Default Always for backwards compatibility.
1300    fn volatile_level(&self) -> VolatileLevel {
1301        VolatileLevel::Always
1302    }
1303
1304    /// A stable workbook seed for RNG composition.
1305    fn workbook_seed(&self) -> u64 {
1306        0xF0F0_D0D0_AAAA_5555
1307    }
1308
1309    /// Recalc epoch that increments on each full recalc when appropriate.
1310    fn recalc_epoch(&self) -> u64 {
1311        0
1312    }
1313
1314    /* ─────────────── Future-proof IO/backends hooks (default no-op) ─────────────── */
1315
1316    /// Optional: Return the min/max used rows for a set of columns on a sheet.
1317    /// When None, the backend does not provide used-region hints.
1318    fn used_rows_for_columns(
1319        &self,
1320        _sheet: &str,
1321        _start_col: u32,
1322        _end_col: u32,
1323    ) -> Option<(u32, u32)> {
1324        None
1325    }
1326
1327    /// Optional: Return the min/max used columns for a set of rows on a sheet.
1328    /// When None, the backend does not provide used-region hints.
1329    fn used_cols_for_rows(
1330        &self,
1331        _sheet: &str,
1332        _start_row: u32,
1333        _end_row: u32,
1334    ) -> Option<(u32, u32)> {
1335        None
1336    }
1337
1338    /// Optional: Physical sheet bounds (max rows, max cols) if known.
1339    fn sheet_bounds(&self, _sheet: &str) -> Option<(u32, u32)> {
1340        None
1341    }
1342
1343    /// Monotonic identifier for the current data snapshot; increments on mutation.
1344    fn data_snapshot_id(&self) -> u64 {
1345        0
1346    }
1347
1348    /// Backend capability advertisement for IO/adapters.
1349    fn backend_caps(&self) -> BackendCaps {
1350        BackendCaps::default()
1351    }
1352
1353    // Flats removed
1354
1355    /// Workbook date system selection (1900 vs 1904).
1356    /// Defaults to 1900 for compatibility.
1357    fn date_system(&self) -> crate::engine::DateSystem {
1358        crate::engine::DateSystem::Excel1900
1359    }
1360
1361    /// Optional: Build or fetch an exact-match lookup index over an Arrow-backed view.
1362    /// Implementations should return None if not supported or unsafe.
1363    fn build_lookup_index(
1364        &self,
1365        _view: &RangeView<'_>,
1366        _axis: LookupAxis,
1367    ) -> Option<std::sync::Arc<LookupIndex>> {
1368        None
1369    }
1370
1371    /// Optional: Build or fetch a cached boolean mask for a criterion over an Arrow-backed view.
1372    /// Implementations should return None if not supported.
1373    fn build_criteria_mask(
1374        &self,
1375        _view: &RangeView<'_>,
1376        _col_in_view: usize,
1377        _pred: &crate::args::CriteriaPredicate,
1378    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1379        None
1380    }
1381
1382    /// Optional: Build row-visibility mask aligned to `view` rows.
1383    /// Returns None if not supported by the underlying context.
1384    fn build_row_visibility_mask(
1385        &self,
1386        _view: &RangeView<'_>,
1387        _mode: VisibilityMaskMode,
1388    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1389        None
1390    }
1391}
1392
1393/// Minimal backend capability descriptor for planning and adapters.
1394#[derive(Copy, Clone, Debug, Default)]
1395pub struct BackendCaps {
1396    /// Provides lazy access (// TODO REMOVE?)
1397    pub streaming: bool,
1398    /// Can compute used-region for rows/columns
1399    pub used_region: bool,
1400    /// Supports write-back mutations via external sink
1401    pub write: bool,
1402    /// Provides table metadata/streaming beyond basic column access
1403    pub tables: bool,
1404    /// May provide asynchronous/lazy remote streams (reserved)
1405    pub async_stream: bool,
1406}
1407
1408/* ───────────────────── FunctionContext (narrow) ───────────────────── */
1409
1410#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1411pub enum VolatileLevel {
1412    /// Value can change at any edit; seed excludes recalc_epoch by default.
1413    Always,
1414    /// Value changes per recalculation; seed should include recalc_epoch.
1415    OnRecalc,
1416    /// Value changes per open; seed uses only workbook_seed.
1417    OnOpen,
1418}
1419
1420/// Minimal context exposed to functions (no engine/graph APIs)
1421pub trait FunctionContext<'ctx> {
1422    fn locale(&self) -> crate::locale::Locale;
1423    fn timezone(&self) -> &crate::timezone::TimeZoneSpec;
1424    fn clock(&self) -> &dyn crate::timezone::ClockProvider;
1425    fn thread_pool(&self) -> Option<&std::sync::Arc<rayon::ThreadPool>>;
1426    fn cancellation_token(&self) -> Option<Arc<std::sync::atomic::AtomicBool>>;
1427    fn chunk_hint(&self) -> Option<usize>;
1428
1429    /// Current formula sheet name.
1430    fn current_sheet(&self) -> &str;
1431
1432    fn workbook_sheet_count(&self) -> Option<usize> {
1433        None
1434    }
1435
1436    fn sheet_index_by_name(&self, _sheet: &str) -> Option<usize> {
1437        None
1438    }
1439
1440    fn current_sheet_index(&self) -> Option<usize> {
1441        self.sheet_index_by_name(self.current_sheet())
1442    }
1443
1444    fn inspect_reference(
1445        &self,
1446        _reference: &ReferenceType,
1447    ) -> Result<Option<ReferenceInfo>, ExcelError> {
1448        Ok(None)
1449    }
1450
1451    fn formula_text_at_cell(&self, _cell: CellRef) -> Result<Option<String>, ExcelError> {
1452        Ok(None)
1453    }
1454
1455    fn volatile_level(&self) -> VolatileLevel;
1456    fn workbook_seed(&self) -> u64;
1457    fn recalc_epoch(&self) -> u64;
1458    fn current_cell(&self) -> Option<CellRef>;
1459
1460    /// Resolve a reference into a RangeView using the underlying engine context.
1461    fn resolve_range_view(
1462        &self,
1463        _reference: &ReferenceType,
1464        _current_sheet: &str,
1465    ) -> Result<RangeView<'ctx>, ExcelError>;
1466
1467    // Flats removed
1468
1469    /// Deterministic RNG seeded for the current evaluation site and function salt.
1470    fn rng_for_current(&self, fn_salt: u64) -> rand::rngs::SmallRng {
1471        use crate::rng::{compose_seed, small_rng_from_lanes};
1472        let (sheet_id, row, col) = self
1473            .current_cell()
1474            .map(|c| (c.sheet_id as u32, c.coord.row(), c.coord.col()))
1475            .unwrap_or((0, 0, 0));
1476        // Include epoch only for OnRecalc
1477        let epoch = match self.volatile_level() {
1478            VolatileLevel::OnRecalc => self.recalc_epoch(),
1479            _ => 0,
1480        };
1481        let (l0, l1) = compose_seed(self.workbook_seed(), sheet_id, row, col, fn_salt, epoch);
1482        small_rng_from_lanes(l0, l1)
1483    }
1484
1485    /// Workbook date system selection (1900 vs 1904).
1486    fn date_system(&self) -> crate::engine::DateSystem {
1487        crate::engine::DateSystem::Excel1900
1488    }
1489
1490    /// Optional: Build or fetch an exact-match lookup index over an Arrow-backed view.
1491    /// Returns None if not supported by the underlying context.
1492    fn get_lookup_index(
1493        &self,
1494        _view: &RangeView<'_>,
1495        _axis: LookupAxis,
1496    ) -> Option<std::sync::Arc<LookupIndex>> {
1497        None
1498    }
1499
1500    /// Optional: Build or fetch a cached boolean mask for a criterion over an Arrow-backed view.
1501    /// Returns None if not supported by the underlying context.
1502    fn get_criteria_mask(
1503        &self,
1504        _view: &RangeView<'_>,
1505        _col_in_view: usize,
1506        _pred: &crate::args::CriteriaPredicate,
1507    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1508        None
1509    }
1510
1511    /// Optional: Build row-visibility mask aligned to `view` rows.
1512    fn get_row_visibility_mask(
1513        &self,
1514        _view: &RangeView<'_>,
1515        _mode: VisibilityMaskMode,
1516    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1517        None
1518    }
1519}
1520
1521/// Default adapter that wraps an EvaluationContext and provides the narrow FunctionContext.
1522pub struct DefaultFunctionContext<'a> {
1523    pub base: &'a dyn EvaluationContext,
1524    pub current: Option<CellRef>,
1525    pub current_sheet: &'a str,
1526}
1527
1528impl<'a> DefaultFunctionContext<'a> {
1529    pub fn new(
1530        base: &'a dyn EvaluationContext,
1531        current: Option<CellRef>,
1532        current_sheet: &'a str,
1533    ) -> Self {
1534        Self {
1535            base,
1536            current,
1537            current_sheet,
1538        }
1539    }
1540
1541    pub fn new_with_sheet(
1542        base: &'a dyn EvaluationContext,
1543        current: Option<CellRef>,
1544        current_sheet: &'a str,
1545    ) -> Self {
1546        Self::new(base, current, current_sheet)
1547    }
1548}
1549
1550impl<'a> FunctionContext<'a> for DefaultFunctionContext<'a> {
1551    fn locale(&self) -> crate::locale::Locale {
1552        self.base.locale()
1553    }
1554
1555    fn current_sheet(&self) -> &str {
1556        self.current_sheet
1557    }
1558
1559    fn workbook_sheet_count(&self) -> Option<usize> {
1560        self.base.workbook_sheet_count()
1561    }
1562
1563    fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
1564        self.base.sheet_index_by_name(sheet)
1565    }
1566
1567    fn current_sheet_index(&self) -> Option<usize> {
1568        self.base.current_sheet_index(self.current_sheet)
1569    }
1570
1571    fn inspect_reference(
1572        &self,
1573        reference: &ReferenceType,
1574    ) -> Result<Option<ReferenceInfo>, ExcelError> {
1575        self.base.inspect_reference(reference, self.current_sheet)
1576    }
1577
1578    fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
1579        self.base.formula_text_at_cell(cell)
1580    }
1581
1582    fn timezone(&self) -> &crate::timezone::TimeZoneSpec {
1583        self.base.timezone()
1584    }
1585
1586    fn clock(&self) -> &dyn crate::timezone::ClockProvider {
1587        self.base.clock()
1588    }
1589    fn thread_pool(&self) -> Option<&std::sync::Arc<rayon::ThreadPool>> {
1590        self.base.thread_pool()
1591    }
1592    fn cancellation_token(&self) -> Option<Arc<std::sync::atomic::AtomicBool>> {
1593        self.base.cancellation_token()
1594    }
1595    fn chunk_hint(&self) -> Option<usize> {
1596        self.base.chunk_hint()
1597    }
1598
1599    fn volatile_level(&self) -> VolatileLevel {
1600        self.base.volatile_level()
1601    }
1602    fn workbook_seed(&self) -> u64 {
1603        self.base.workbook_seed()
1604    }
1605    fn recalc_epoch(&self) -> u64 {
1606        self.base.recalc_epoch()
1607    }
1608    fn current_cell(&self) -> Option<CellRef> {
1609        self.current
1610    }
1611
1612    fn resolve_range_view(
1613        &self,
1614        reference: &ReferenceType,
1615        current_sheet: &str,
1616    ) -> Result<RangeView<'a>, ExcelError> {
1617        self.base.resolve_range_view(reference, current_sheet)
1618    }
1619
1620    // Flats removed
1621
1622    fn date_system(&self) -> crate::engine::DateSystem {
1623        self.base.date_system()
1624    }
1625
1626    fn get_lookup_index(
1627        &self,
1628        view: &RangeView<'_>,
1629        axis: LookupAxis,
1630    ) -> Option<std::sync::Arc<LookupIndex>> {
1631        self.base.build_lookup_index(view, axis)
1632    }
1633
1634    fn get_criteria_mask(
1635        &self,
1636        view: &RangeView<'_>,
1637        col_in_view: usize,
1638        pred: &crate::args::CriteriaPredicate,
1639    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1640        self.base.build_criteria_mask(view, col_in_view, pred)
1641    }
1642
1643    fn get_row_visibility_mask(
1644        &self,
1645        view: &RangeView<'_>,
1646        mode: VisibilityMaskMode,
1647    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1648        self.base.build_row_visibility_mask(view, mode)
1649    }
1650}