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
90pub struct ArgumentHandle<'a, 'b> {
91    node: &'a ASTNode,
92    interp: &'a Interpreter<'b>,
93}
94
95impl<'a, 'b> ArgumentHandle<'a, 'b> {
96    pub(crate) fn new(node: &'a ASTNode, interp: &'a Interpreter<'b>) -> Self {
97        Self { node, interp }
98    }
99
100    pub fn value(&self) -> Result<CowValue<'_>, ExcelError> {
101        if let ASTNodeType::Literal(ref v) = self.node.node_type {
102            return Ok(Cow::Borrowed(v));
103        }
104        self.interp.evaluate_ast(self.node).map(Cow::Owned)
105    }
106
107    pub fn range(&self) -> Result<Box<dyn Range>, ExcelError> {
108        match &self.node.node_type {
109            ASTNodeType::Reference { reference, .. } => {
110                self.interp.context.resolve_range_like(reference)
111            }
112            ASTNodeType::Array(rows) => {
113                let mut materialized = Vec::new();
114                for row in rows {
115                    let mut materialized_row = Vec::new();
116                    for cell in row {
117                        materialized_row.push(self.interp.evaluate_ast(cell)?);
118                    }
119                    materialized.push(materialized_row);
120                }
121                Ok(Box::new(InMemoryRange::new(materialized)))
122            }
123            _ => Err(ExcelError::new(ExcelErrorKind::Ref)
124                .with_message(format!("Expected a range, got {:?}", self.node.node_type))),
125        }
126    }
127
128    /// Resolve as a RangeView (Phase 2 API). Only supports reference arguments.
129    pub fn range_view(&self) -> Result<RangeView<'_>, ExcelError> {
130        match &self.node.node_type {
131            ASTNodeType::Reference { reference, .. } => self
132                .interp
133                .context
134                .resolve_range_view(reference, self.interp.current_sheet()),
135            // Treat array literals (LiteralValue::Array) as ranges for RangeView APIs
136            ASTNodeType::Literal(formualizer_common::LiteralValue::Array(arr)) => {
137                // Borrow the rows directly from the AST literal
138                Ok(RangeView::from_borrowed(&arr[..]))
139            }
140            ASTNodeType::Array(rows) => {
141                // Materialize AST array to values, then return a borrowed view
142                let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows.len());
143                for r in rows {
144                    let mut row_vals = Vec::with_capacity(r.len());
145                    for cell in r {
146                        row_vals.push(self.interp.evaluate_ast(cell)?);
147                    }
148                    out.push(row_vals);
149                }
150                Ok(RangeView::from_borrowed(Box::leak(Box::new(out))))
151            }
152            _ => Err(ExcelError::new(ExcelErrorKind::Ref)
153                .with_message("Argument cannot be interpreted as a range.")),
154        }
155    }
156
157    pub fn value_or_range(&self) -> Result<EvaluatedArg<'_>, ExcelError> {
158        self.range()
159            .map(EvaluatedArg::Range)
160            .or_else(|_| self.value().map(EvaluatedArg::LiteralValue))
161    }
162
163    /// Lazily iterate values for this argument in row-major expansion order.
164    /// - Reference: stream via RangeView (row-major)
165    /// - Array literal: evaluate each element lazily per cell
166    /// - Scalar/other expressions: a single value
167    pub fn lazy_values_owned(
168        &'a self,
169    ) -> Result<Box<dyn Iterator<Item = LiteralValue> + 'a>, ExcelError> {
170        match &self.node.node_type {
171            ASTNodeType::Reference { .. } => {
172                let view = self.range_view()?;
173                let mut values: Vec<LiteralValue> = Vec::new();
174                view.for_each_cell(&mut |v| {
175                    values.push(v.clone());
176                    Ok(())
177                })?;
178                Ok(Box::new(values.into_iter()))
179            }
180            ASTNodeType::Array(rows) => {
181                struct ArrayEvalIter<'a, 'b> {
182                    rows: &'a [Vec<ASTNode>],
183                    r: usize,
184                    c: usize,
185                    interp: &'a Interpreter<'b>,
186                }
187                impl<'a, 'b> Iterator for ArrayEvalIter<'a, 'b> {
188                    type Item = LiteralValue;
189                    fn next(&mut self) -> Option<Self::Item> {
190                        if self.rows.is_empty() {
191                            return None;
192                        }
193                        let rows = self.rows;
194                        let mut r = self.r;
195                        let mut c = self.c;
196                        if r >= rows.len() {
197                            return None;
198                        }
199                        let node = &rows[r][c];
200                        // advance indices
201                        c += 1;
202                        if c >= rows[r].len() {
203                            r += 1;
204                            c = 0;
205                        }
206                        self.r = r;
207                        self.c = c;
208                        match self.interp.evaluate_ast(node) {
209                            Ok(v) => Some(v),
210                            Err(e) => Some(LiteralValue::Error(e)),
211                        }
212                    }
213                }
214                let it = ArrayEvalIter {
215                    rows,
216                    r: 0,
217                    c: 0,
218                    interp: self.interp,
219                };
220                Ok(Box::new(it))
221            }
222            _ => {
223                // Single value expression
224                let v = self.value()?.into_owned();
225                Ok(Box::new(std::iter::once(v)))
226            }
227        }
228    }
229
230    pub fn ast(&self) -> &'a ASTNode {
231        self.node
232    }
233
234    /// Returns the raw reference from the AST when this argument is a reference.
235    /// This does not evaluate the reference or materialize values.
236    pub fn as_reference(&self) -> Result<&'a ReferenceType, ExcelError> {
237        match &self.node.node_type {
238            ASTNodeType::Reference { reference, .. } => Ok(reference),
239            _ => Err(ExcelError::new(ExcelErrorKind::Ref)
240                .with_message("Expected a reference (by-ref argument)")),
241        }
242    }
243
244    /// Returns a `ReferenceType` if this argument is a reference or a function that
245    /// can yield a reference via `eval_reference`. Materializes no values.
246    pub fn as_reference_or_eval(&self) -> Result<ReferenceType, ExcelError> {
247        match &self.node.node_type {
248            ASTNodeType::Reference { reference, .. } => Ok(reference.clone()),
249            ASTNodeType::Function { name, args } => {
250                if let Some(fun) = self.interp.context.get_function("", name) {
251                    let handles: Vec<ArgumentHandle> = args
252                        .iter()
253                        .map(|n| ArgumentHandle::new(n, self.interp))
254                        .collect();
255                    let fctx =
256                        crate::traits::DefaultFunctionContext::new(self.interp.context, None);
257                    if let Some(res) = fun.eval_reference(&handles, &fctx) {
258                        res
259                    } else {
260                        Err(ExcelError::new(ExcelErrorKind::Ref)
261                            .with_message("Function does not return a reference"))
262                    }
263                } else {
264                    Err(ExcelError::new(ExcelErrorKind::Name))
265                }
266            }
267            _ => {
268                Err(ExcelError::new(ExcelErrorKind::Ref)
269                    .with_message("Argument is not a reference"))
270            }
271        }
272    }
273
274    /* tiny validator helper for macro */
275    pub fn matches_kind(&self, k: formualizer_common::ArgKind) -> Result<bool, ExcelError> {
276        Ok(match k {
277            formualizer_common::ArgKind::Any => true,
278            formualizer_common::ArgKind::Range => self.range().is_ok(),
279            formualizer_common::ArgKind::Number => matches!(
280                self.value()?.as_ref(),
281                LiteralValue::Number(_) | LiteralValue::Int(_)
282            ),
283            formualizer_common::ArgKind::Text => {
284                matches!(self.value()?.as_ref(), LiteralValue::Text(_))
285            }
286            formualizer_common::ArgKind::Logical => {
287                matches!(self.value()?.as_ref(), LiteralValue::Boolean(_))
288            }
289        })
290    }
291}
292
293/* simple Vec-backed range */
294#[derive(Debug, Clone)]
295pub struct InMemoryRange {
296    data: Vec<Vec<LiteralValue>>,
297}
298impl InMemoryRange {
299    pub fn new(d: Vec<Vec<LiteralValue>>) -> Self {
300        Self { data: d }
301    }
302}
303impl Range for InMemoryRange {
304    fn get(&self, r: usize, c: usize) -> Result<LiteralValue, ExcelError> {
305        Ok(self
306            .data
307            .get(r)
308            .and_then(|row| row.get(c))
309            .cloned()
310            .unwrap_or(LiteralValue::Empty))
311    }
312    fn dimensions(&self) -> (usize, usize) {
313        (self.data.len(), self.data.first().map_or(0, |r| r.len()))
314    }
315    fn as_any(&self) -> &dyn Any {
316        self
317    }
318}
319
320/* ───────────────────────── Table abstraction ───────────────────────── */
321
322pub trait Table: Debug + Send + Sync {
323    fn get_cell(&self, row: usize, column: &str) -> Result<LiteralValue, ExcelError>;
324    fn get_column(&self, column: &str) -> Result<Box<dyn Range>, ExcelError>;
325    /// Ordered list of column names
326    fn columns(&self) -> Vec<String> {
327        vec![]
328    }
329    /// Number of data rows (excluding headers/totals)
330    fn data_height(&self) -> usize {
331        0
332    }
333    /// Whether the table has a header row
334    fn has_headers(&self) -> bool {
335        false
336    }
337    /// Whether the table has a totals row
338    fn has_totals(&self) -> bool {
339        false
340    }
341    /// Headers row as a 1xW range
342    fn headers_row(&self) -> Option<Box<dyn Range>> {
343        None
344    }
345    /// Totals row as a 1xW range, if present
346    fn totals_row(&self) -> Option<Box<dyn Range>> {
347        None
348    }
349    /// Entire data body as HxW range
350    fn data_body(&self) -> Option<Box<dyn Range>> {
351        None
352    }
353    fn clone_box(&self) -> Box<dyn Table>;
354}
355impl Table for Box<dyn Table> {
356    fn get_cell(&self, r: usize, c: &str) -> Result<LiteralValue, ExcelError> {
357        (**self).get_cell(r, c)
358    }
359    fn get_column(&self, c: &str) -> Result<Box<dyn Range>, ExcelError> {
360        (**self).get_column(c)
361    }
362    fn columns(&self) -> Vec<String> {
363        (**self).columns()
364    }
365    fn data_height(&self) -> usize {
366        (**self).data_height()
367    }
368    fn has_headers(&self) -> bool {
369        (**self).has_headers()
370    }
371    fn has_totals(&self) -> bool {
372        (**self).has_totals()
373    }
374    fn headers_row(&self) -> Option<Box<dyn Range>> {
375        (**self).headers_row()
376    }
377    fn totals_row(&self) -> Option<Box<dyn Range>> {
378        (**self).totals_row()
379    }
380    fn data_body(&self) -> Option<Box<dyn Range>> {
381        (**self).data_body()
382    }
383    fn clone_box(&self) -> Box<dyn Table> {
384        (**self).clone_box()
385    }
386}
387
388/* ─────────────────────── Resolver super-trait ─────────────────────── */
389
390pub trait ReferenceResolver: Send + Sync {
391    fn resolve_cell_reference(
392        &self,
393        sheet: Option<&str>,
394        row: u32,
395        col: u32,
396    ) -> Result<LiteralValue, ExcelError>;
397}
398pub trait RangeResolver: Send + Sync {
399    fn resolve_range_reference(
400        &self,
401        sheet: Option<&str>,
402        sr: Option<u32>,
403        sc: Option<u32>,
404        er: Option<u32>,
405        ec: Option<u32>,
406    ) -> Result<Box<dyn Range>, ExcelError>;
407}
408pub trait NamedRangeResolver: Send + Sync {
409    fn resolve_named_range_reference(
410        &self,
411        name: &str,
412    ) -> Result<Vec<Vec<LiteralValue>>, ExcelError>;
413}
414pub trait TableResolver: Send + Sync {
415    fn resolve_table_reference(
416        &self,
417        tref: &formualizer_parse::parser::TableReference,
418    ) -> Result<Box<dyn Table>, ExcelError>;
419}
420pub trait Resolver: ReferenceResolver + RangeResolver + NamedRangeResolver + TableResolver {
421    fn resolve_range_like(&self, r: &ReferenceType) -> Result<Box<dyn Range>, ExcelError> {
422        match r {
423            ReferenceType::Range {
424                sheet,
425                start_row,
426                start_col,
427                end_row,
428                end_col,
429            } => self.resolve_range_reference(
430                sheet.as_deref(),
431                *start_row,
432                *start_col,
433                *end_row,
434                *end_col,
435            ),
436            ReferenceType::Table(tref) => {
437                let t = self.resolve_table_reference(tref)?;
438                match &tref.specifier {
439                    Some(TableSpecifier::Column(c)) => t.get_column(c),
440                    Some(TableSpecifier::ColumnRange(start, end)) => {
441                        // Build a rectangular range from start..=end columns in table order
442                        let cols = t.columns();
443                        let start_idx = cols.iter().position(|n| n.eq_ignore_ascii_case(start));
444                        let end_idx = cols.iter().position(|n| n.eq_ignore_ascii_case(end));
445                        if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
446                            if si > ei {
447                                std::mem::swap(&mut si, &mut ei);
448                            }
449                            // Materialize by stacking columns into a 2D array
450                            let h = t.data_height();
451                            let w = ei - si + 1;
452                            let mut rows = vec![vec![LiteralValue::Empty; w]; h];
453                            for (offset, ci) in (si..=ei).enumerate() {
454                                let cname = &cols[ci];
455                                let col_range = t.get_column(cname)?;
456                                let (rh, _) = col_range.dimensions();
457                                for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
458                                    row[offset] = col_range.get(r, 0)?;
459                                }
460                            }
461                            Ok(Box::new(InMemoryRange::new(rows)))
462                        } else {
463                            Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
464                                "Column range refers to unknown column(s)".to_string(),
465                            ))
466                        }
467                    }
468                    Some(TableSpecifier::SpecialItem(
469                        formualizer_parse::parser::SpecialItem::Headers,
470                    )) => {
471                        if let Some(h) = t.headers_row() {
472                            Ok(h)
473                        } else {
474                            Ok(Box::new(InMemoryRange::new(vec![])))
475                        }
476                    }
477                    Some(TableSpecifier::SpecialItem(
478                        formualizer_parse::parser::SpecialItem::Totals,
479                    )) => {
480                        if let Some(tr) = t.totals_row() {
481                            Ok(tr)
482                        } else {
483                            Ok(Box::new(InMemoryRange::new(vec![])))
484                        }
485                    }
486                    Some(TableSpecifier::SpecialItem(
487                        formualizer_parse::parser::SpecialItem::Data,
488                    )) => {
489                        if let Some(body) = t.data_body() {
490                            Ok(body)
491                        } else {
492                            Ok(Box::new(InMemoryRange::new(vec![])))
493                        }
494                    }
495                    Some(TableSpecifier::SpecialItem(
496                        formualizer_parse::parser::SpecialItem::All,
497                    )) => {
498                        // Equivalent to TableSpecifier::All handling
499                        let mut out: Vec<Vec<LiteralValue>> = Vec::new();
500                        if let Some(h) = t.headers_row() {
501                            out.extend(h.iter_rows());
502                        }
503                        if let Some(body) = t.data_body() {
504                            out.extend(body.iter_rows());
505                        }
506                        if let Some(tr) = t.totals_row() {
507                            out.extend(tr.iter_rows());
508                        }
509                        Ok(Box::new(InMemoryRange::new(out)))
510                    }
511                    Some(TableSpecifier::SpecialItem(
512                        formualizer_parse::parser::SpecialItem::ThisRow,
513                    )) => Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
514                        "@ (This Row) requires table-aware context; not yet supported".to_string(),
515                    )),
516                    Some(TableSpecifier::All) => {
517                        // Concatenate headers (if any), data, totals (if any)
518                        let mut out: Vec<Vec<LiteralValue>> = Vec::new();
519                        if let Some(h) = t.headers_row() {
520                            out.extend(h.iter_rows());
521                        }
522                        if let Some(body) = t.data_body() {
523                            out.extend(body.iter_rows());
524                        }
525                        if let Some(tr) = t.totals_row() {
526                            out.extend(tr.iter_rows());
527                        }
528                        Ok(Box::new(InMemoryRange::new(out)))
529                    }
530                    Some(TableSpecifier::Data) => {
531                        if let Some(body) = t.data_body() {
532                            Ok(body)
533                        } else {
534                            Ok(Box::new(InMemoryRange::new(vec![])))
535                        }
536                    }
537                    // Defer complex combinations and row selectors for tranche 1
538                    Some(TableSpecifier::Combination(_)) => Err(ExcelError::new(
539                        ExcelErrorKind::NImpl,
540                    )
541                    .with_message("Complex structured references not yet supported".to_string())),
542                    Some(TableSpecifier::Row(_)) => Err(ExcelError::new(ExcelErrorKind::NImpl)
543                        .with_message("Row selectors (@/index) not yet supported".to_string())),
544                    Some(TableSpecifier::Headers) | Some(TableSpecifier::Totals) => {
545                        Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
546                            "Legacy Headers/Totals variants not used; use SpecialItem".to_string(),
547                        ))
548                    }
549                    None => Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
550                        "Table reference without specifier is unsupported".to_string(),
551                    )),
552                }
553            }
554            ReferenceType::NamedRange(n) => {
555                let v = self.resolve_named_range_reference(n)?;
556                Ok(Box::new(InMemoryRange::new(v)))
557            }
558            ReferenceType::Cell { sheet, row, col } => {
559                let v = self.resolve_cell_reference(sheet.as_deref(), *row, *col)?;
560                Ok(Box::new(InMemoryRange::new(vec![vec![v]])))
561            }
562        }
563    }
564}
565
566/* ───────────────────── EvaluationContext = Resolver+Fns ───────────── */
567
568pub trait FunctionProvider: Send + Sync {
569    fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>>;
570}
571
572pub trait EvaluationContext: Resolver + FunctionProvider {
573    /// Get access to the shared thread pool for parallel evaluation
574    /// Returns None if parallel evaluation is disabled or unavailable
575    fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
576        None
577    }
578
579    /// Optional cancellation token. When Some, long-running operations should periodically abort.
580    fn cancellation_token(&self) -> Option<&std::sync::atomic::AtomicBool> {
581        None
582    }
583
584    /// Optional chunk size hint for streaming visitors.
585    fn chunk_hint(&self) -> Option<usize> {
586        None
587    }
588
589    /// Resolve a reference into a `RangeView` with clear bounds.
590    /// Implementations should resolve un/partially bounded references using used-region.
591    fn resolve_range_view<'c>(
592        &'c self,
593        _reference: &ReferenceType,
594        _current_sheet: &str,
595    ) -> Result<RangeView<'c>, ExcelError> {
596        Err(ExcelError::new(ExcelErrorKind::NImpl))
597    }
598
599    /// Locale provider: invariant by default
600    fn locale(&self) -> crate::locale::Locale {
601        crate::locale::Locale::invariant()
602    }
603
604    /// Timezone provider for date/time functions
605    /// Default: Local (Excel-compatible behavior)
606    /// Functions should use local timezone when this returns Local
607    fn timezone(&self) -> &crate::timezone::TimeZoneSpec {
608        // Static default to avoid allocation
609        static DEFAULT_TZ: std::sync::OnceLock<crate::timezone::TimeZoneSpec> =
610            std::sync::OnceLock::new();
611        DEFAULT_TZ.get_or_init(crate::timezone::TimeZoneSpec::default)
612    }
613
614    /// Volatile granularity. Default Always for backwards compatibility.
615    fn volatile_level(&self) -> VolatileLevel {
616        VolatileLevel::Always
617    }
618
619    /// A stable workbook seed for RNG composition.
620    fn workbook_seed(&self) -> u64 {
621        0xF0F0_D0D0_AAAA_5555
622    }
623
624    /// Recalc epoch that increments on each full recalc when appropriate.
625    fn recalc_epoch(&self) -> u64 {
626        0
627    }
628
629    /* ─────────────── Future-proof IO/backends hooks (default no-op) ─────────────── */
630
631    /// Optional: Return the min/max used rows for a set of columns on a sheet.
632    /// When None, the backend does not provide used-region hints.
633    fn used_rows_for_columns(
634        &self,
635        _sheet: &str,
636        _start_col: u32,
637        _end_col: u32,
638    ) -> Option<(u32, u32)> {
639        None
640    }
641
642    /// Optional: Return the min/max used columns for a set of rows on a sheet.
643    /// When None, the backend does not provide used-region hints.
644    fn used_cols_for_rows(
645        &self,
646        _sheet: &str,
647        _start_row: u32,
648        _end_row: u32,
649    ) -> Option<(u32, u32)> {
650        None
651    }
652
653    /// Optional: Physical sheet bounds (max rows, max cols) if known.
654    fn sheet_bounds(&self, _sheet: &str) -> Option<(u32, u32)> {
655        None
656    }
657
658    /// Monotonic identifier for the current data snapshot; increments on mutation.
659    fn data_snapshot_id(&self) -> u64 {
660        0
661    }
662
663    /// Backend capability advertisement for IO/adapters.
664    fn backend_caps(&self) -> BackendCaps {
665        BackendCaps::default()
666    }
667
668    // Flats removed
669
670    /// Feature gate: enable Arrow fast paths in builtins (e.g., SUMIFS).
671    /// Default is false; engines that wish to enable must override.
672    fn arrow_fastpath_enabled(&self) -> bool {
673        false
674    }
675
676    /// Workbook date system selection (1900 vs 1904).
677    /// Defaults to 1900 for compatibility.
678    fn date_system(&self) -> crate::engine::DateSystem {
679        crate::engine::DateSystem::Excel1900
680    }
681
682    /// Optional: Build or fetch a cached boolean mask for a criterion over an Arrow-backed view.
683    /// Implementations should return None if not supported.
684    fn build_criteria_mask(
685        &self,
686        _view: &crate::arrow_store::ArrowRangeView<'_>,
687        _col_in_view: usize,
688        _pred: &crate::args::CriteriaPredicate,
689    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
690        None
691    }
692}
693
694/// Minimal backend capability descriptor for planning and adapters.
695#[derive(Copy, Clone, Debug, Default)]
696pub struct BackendCaps {
697    /// Provides lazy access (// TODO REMOVE?)
698    pub streaming: bool,
699    /// Can compute used-region for rows/columns
700    pub used_region: bool,
701    /// Supports write-back mutations via external sink
702    pub write: bool,
703    /// Provides table metadata/streaming beyond basic column access
704    pub tables: bool,
705    /// May provide asynchronous/lazy remote streams (reserved)
706    pub async_stream: bool,
707}
708
709/* ───────────────────── FunctionContext (narrow) ───────────────────── */
710
711#[derive(Copy, Clone, Debug, Eq, PartialEq)]
712pub enum VolatileLevel {
713    /// Value can change at any edit; seed excludes recalc_epoch by default.
714    Always,
715    /// Value changes per recalculation; seed should include recalc_epoch.
716    OnRecalc,
717    /// Value changes per open; seed uses only workbook_seed.
718    OnOpen,
719}
720
721/// Minimal context exposed to functions (no engine/graph APIs)
722pub trait FunctionContext {
723    fn locale(&self) -> crate::locale::Locale;
724    fn timezone(&self) -> &crate::timezone::TimeZoneSpec;
725    fn thread_pool(&self) -> Option<&std::sync::Arc<rayon::ThreadPool>>;
726    fn cancellation_token(&self) -> Option<&std::sync::atomic::AtomicBool>;
727    fn chunk_hint(&self) -> Option<usize>;
728
729    fn volatile_level(&self) -> VolatileLevel;
730    fn workbook_seed(&self) -> u64;
731    fn recalc_epoch(&self) -> u64;
732    fn current_cell(&self) -> Option<CellRef>;
733
734    /// Resolve a reference into a RangeView using the underlying engine context.
735    fn resolve_range_view<'c>(
736        &'c self,
737        _reference: &ReferenceType,
738        _current_sheet: &str,
739    ) -> Result<RangeView<'c>, ExcelError> {
740        Err(ExcelError::new(ExcelErrorKind::NImpl))
741    }
742
743    // Flats removed
744
745    /// Get a pre-built criteria mask if available (Phase 3)
746    /// Returns None if not warmed up or not available
747    fn get_or_build_mask(&self, _key: &str) -> Option<crate::engine::masks::DenseMask> {
748        None
749    }
750
751    /// Deterministic RNG seeded for the current evaluation site and function salt.
752    fn rng_for_current(&self, fn_salt: u64) -> rand::rngs::SmallRng {
753        use crate::rng::{compose_seed, small_rng_from_lanes};
754        let (sheet_id, row, col) = self
755            .current_cell()
756            .map(|c| (c.sheet_id as u32, c.coord.row, c.coord.col))
757            .unwrap_or((0, 0, 0));
758        // Include epoch only for OnRecalc
759        let epoch = match self.volatile_level() {
760            VolatileLevel::OnRecalc => self.recalc_epoch(),
761            _ => 0,
762        };
763        let (l0, l1) = compose_seed(self.workbook_seed(), sheet_id, row, col, fn_salt, epoch);
764        small_rng_from_lanes(l0, l1)
765    }
766
767    /// Feature gate: enable Arrow fast paths in builtins (e.g., SUMIFS)
768    fn arrow_fastpath_enabled(&self) -> bool {
769        false
770    }
771
772    /// Workbook date system selection (1900 vs 1904).
773    fn date_system(&self) -> crate::engine::DateSystem {
774        crate::engine::DateSystem::Excel1900
775    }
776
777    /// Optional: Build or fetch a cached boolean mask for a criterion over an Arrow-backed view.
778    /// Returns None if not supported by the underlying context.
779    fn get_criteria_mask(
780        &self,
781        _view: &crate::arrow_store::ArrowRangeView<'_>,
782        _col_in_view: usize,
783        _pred: &crate::args::CriteriaPredicate,
784    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
785        None
786    }
787}
788
789/// Default adapter that wraps an EvaluationContext and provides the narrow FunctionContext.
790pub struct DefaultFunctionContext<'a> {
791    pub base: &'a dyn EvaluationContext,
792    pub current: Option<CellRef>,
793}
794
795impl<'a> DefaultFunctionContext<'a> {
796    pub fn new(base: &'a dyn EvaluationContext, current: Option<CellRef>) -> Self {
797        Self { base, current }
798    }
799}
800
801impl<'a> FunctionContext for DefaultFunctionContext<'a> {
802    fn locale(&self) -> crate::locale::Locale {
803        self.base.locale()
804    }
805    fn timezone(&self) -> &crate::timezone::TimeZoneSpec {
806        self.base.timezone()
807    }
808    fn thread_pool(&self) -> Option<&std::sync::Arc<rayon::ThreadPool>> {
809        self.base.thread_pool()
810    }
811    fn cancellation_token(&self) -> Option<&std::sync::atomic::AtomicBool> {
812        self.base.cancellation_token()
813    }
814    fn chunk_hint(&self) -> Option<usize> {
815        self.base.chunk_hint()
816    }
817
818    fn volatile_level(&self) -> VolatileLevel {
819        self.base.volatile_level()
820    }
821    fn workbook_seed(&self) -> u64 {
822        self.base.workbook_seed()
823    }
824    fn recalc_epoch(&self) -> u64 {
825        self.base.recalc_epoch()
826    }
827    fn current_cell(&self) -> Option<CellRef> {
828        self.current
829    }
830
831    fn resolve_range_view<'c>(
832        &'c self,
833        reference: &ReferenceType,
834        current_sheet: &str,
835    ) -> Result<RangeView<'c>, ExcelError> {
836        self.base.resolve_range_view(reference, current_sheet)
837    }
838
839    // Flats removed
840
841    fn arrow_fastpath_enabled(&self) -> bool {
842        self.base.arrow_fastpath_enabled()
843    }
844
845    fn date_system(&self) -> crate::engine::DateSystem {
846        self.base.date_system()
847    }
848
849    fn get_criteria_mask(
850        &self,
851        view: &crate::arrow_store::ArrowRangeView<'_>,
852        col_in_view: usize,
853        pred: &crate::args::CriteriaPredicate,
854    ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
855        self.base.build_criteria_mask(view, col_in_view, pred)
856    }
857}