Skip to main content

formualizer_eval/
function.rs

1//! formualizer-eval/src/function.rs
2// New home for the core `Function` trait and its capability flags.
3
4use core::panic;
5
6use crate::{args::ArgSchema, traits::ArgumentHandle};
7use formualizer_common::{ExcelError, LiteralValue};
8
9bitflags::bitflags! {
10    /// Describes the capabilities and properties of a function.
11    ///
12    /// This allows the engine to select optimal evaluation paths (e.g., vectorized,
13    /// parallel, GPU) and to enforce semantic contracts at compile time.
14    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15    pub struct FnCaps: u16 {
16        // --- Semantics ---
17        /// The function always produces the same output for the same input and has no
18        /// side effects. This is the default for most functions.
19        const PURE          = 0b0000_0000_0001;
20        /// The function's output can change even with the same inputs (e.g., `RAND()`,
21        /// `NOW()`). Volatile functions are re-evaluated on every sheet change.
22        const VOLATILE      = 0b0000_0000_0010;
23
24        // --- Shape / Evaluation Strategy ---
25        /// The function reduces a range of inputs to a single value (e.g., `SUM`, `AVERAGE`).
26        const REDUCTION     = 0b0000_0000_0100;
27        /// The function operates on each element of its input ranges independently
28        /// (e.g., `SIN`, `ABS`).
29        const ELEMENTWISE   = 0b0000_0000_1000;
30        /// The function operates on a sliding window over its input (e.g., `MOVING_AVERAGE`).
31        const WINDOWED      = 0b0000_0001_0000;
32        /// The function performs a lookup or search operation (e.g., `VLOOKUP`).
33        const LOOKUP        = 0b0000_0010_0000;
34
35        // --- Input Data Types ---
36        /// The function primarily operates on numbers. The engine can prepare
37        /// optimized numeric stripes (`&[f64]`) for it.
38        const NUMERIC_ONLY  = 0b0000_0100_0000;
39        /// The function primarily operates on booleans.
40        const BOOL_ONLY     = 0b0000_1000_0000;
41
42        // --- Backend Optimizations ---
43        /// The function has an implementation suitable for SIMD vectorization.
44        const SIMD_OK       = 0b0001_0000_0000;
45        /// The function can process input as a stream, without materializing the
46        /// entire range in memory.
47        const STREAM_OK     = 0b0010_0000_0000;
48        /// The function has a GPU-accelerated implementation.
49        const GPU_OK        = 0b0100_0000_0000;
50
51        // --- Reference semantics ---
52        /// The function can return a reference (to a cell/range/table) when
53        /// evaluated in a reference context. When used in a value context,
54        /// engines may materialize the reference to a `LiteralValue`.
55    const RETURNS_REFERENCE = 0b1000_0000_0000;
56
57    // --- Planning / Interpreter parallelism hints ---
58    /// The function enforces left-to-right evaluation and early-exit semantics.
59    /// The planner must not evaluate arguments in parallel nor reorder them.
60    const SHORT_CIRCUIT  = 0b0001_0000_0000_0000;
61    /// It is safe and potentially profitable to evaluate arguments in parallel.
62    /// The engine should still fold results in argument order for determinism.
63    const PARALLEL_ARGS  = 0b0010_0000_0000_0000;
64    /// It is safe to chunk and process input windows in parallel (e.g., SUMIFS).
65    /// It is safe to chunk and process input windows in parallel (e.g., SUMIFS).
66    const PARALLEL_CHUNKS= 0b0100_0000_0000_0000;
67    /// Function has dynamic dependencies determined at runtime (e.g. INDIRECT, OFFSET)
68    const DYNAMIC_DEPENDENCY = 0b1000_0000_0000_0000;
69    }
70}
71
72/// Revised, object-safe trait for all Excel-style functions.
73///
74/// This trait uses a capability-based model (`FnCaps`) to declare function
75/// properties, enabling the evaluation engine to select the most optimal
76/// execution path (e.g., scalar, vectorized, parallel).
77pub trait Function: Send + Sync + 'static {
78    /// Capability flags for this function
79    fn caps(&self) -> FnCaps {
80        FnCaps::PURE
81    }
82
83    fn name(&self) -> &'static str;
84    fn namespace(&self) -> &'static str {
85        ""
86    }
87    fn min_args(&self) -> usize {
88        0
89    }
90    fn variadic(&self) -> bool {
91        false
92    }
93    fn volatile(&self) -> bool {
94        self.caps().contains(FnCaps::VOLATILE)
95    }
96    fn arg_schema(&self) -> &'static [ArgSchema] {
97        if self.min_args() > 0 {
98            panic!("Non-zero min_args must have a valid arg_schema");
99        } else {
100            &[]
101        }
102    }
103
104    /// Optional list of additional alias names (case-insensitive) that should resolve to this
105    /// function. Default: empty slice. Implementors can override to expose legacy names.
106    /// Returned slice must have 'static lifetime (typically a static array reference).
107    fn aliases(&self) -> &'static [&'static str] {
108        &[]
109    }
110
111    #[inline]
112    fn function_salt(&self) -> u64 {
113        // Stable hash of function name + namespace
114        let full_name = if self.namespace().is_empty() {
115            self.name().to_string()
116        } else {
117            format!("{}::{}", self.namespace(), self.name())
118        };
119        crate::rng::fnv1a64(full_name.as_bytes())
120    }
121
122    /// The unified evaluation path.
123    ///
124    /// This method replaces the separate scalar, fold, and map paths.
125    /// Functions use the provided `ArgumentHandle`s to access inputs as either
126    /// scalars or `RangeView`s (Arrow-backed virtual ranges).
127    fn eval<'a, 'b, 'c>(
128        &self,
129        args: &'c [ArgumentHandle<'a, 'b>],
130        ctx: &dyn crate::traits::FunctionContext<'b>,
131    ) -> Result<crate::traits::CalcValue<'b>, ExcelError>;
132
133    /// Optional reference result path. Only called by the interpreter/engine
134    /// when the callsite expects a reference (e.g., range combinators, by-ref
135    /// argument positions, or spill sources).
136    ///
137    /// Default implementation returns `None`, indicating the function does not
138    /// support returning references. Functions that set `RETURNS_REFERENCE`
139    /// should override this.
140    fn eval_reference<'a, 'b, 'c>(
141        &self,
142        _args: &'c [ArgumentHandle<'a, 'b>],
143        _ctx: &dyn crate::traits::FunctionContext<'b>,
144    ) -> Option<Result<formualizer_parse::parser::ReferenceType, ExcelError>> {
145        None
146    }
147
148    /// Dispatch to the unified evaluation path with automatic argument validation.
149    fn dispatch<'a, 'b, 'c>(
150        &self,
151        args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
152        ctx: &dyn crate::traits::FunctionContext<'b>,
153    ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
154        // Central argument validation
155        {
156            use crate::args::{ValidationOptions, validate_and_prepare};
157            let schema = self.arg_schema();
158            if let Err(e) =
159                validate_and_prepare(args, schema, ValidationOptions { warn_only: false })
160            {
161                return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
162            }
163        }
164
165        self.eval(args, ctx)
166    }
167}