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