Skip to main content

formualizer_eval/
traits.rs

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