formualizer_eval/
traits.rs

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