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 const PARALLEL_CHUNKS= 0b0100_0000_0000_0000;
66 }
67}
68
69/// Revised, object-safe trait for all Excel-style functions.
70///
71/// This trait uses a capability-based model (`FnCaps`) to declare function
72/// properties, enabling the evaluation engine to select the most optimal
73/// execution path (e.g., scalar, vectorized, parallel).
74pub trait Function: Send + Sync + 'static {
75 /// Capability flags for this function
76 fn caps(&self) -> FnCaps {
77 FnCaps::PURE
78 }
79
80 fn name(&self) -> &'static str;
81 fn namespace(&self) -> &'static str {
82 ""
83 }
84 fn min_args(&self) -> usize {
85 0
86 }
87 fn variadic(&self) -> bool {
88 false
89 }
90 fn volatile(&self) -> bool {
91 self.caps().contains(FnCaps::VOLATILE)
92 }
93 fn arg_schema(&self) -> &'static [ArgSchema] {
94 if self.min_args() > 0 {
95 panic!("Non-zero min_args must have a valid arg_schema");
96 } else {
97 &[]
98 }
99 }
100
101 /// Optional list of additional alias names (case-insensitive) that should resolve to this
102 /// function. Default: empty slice. Implementors can override to expose legacy names.
103 /// Returned slice must have 'static lifetime (typically a static array reference).
104 fn aliases(&self) -> &'static [&'static str] {
105 &[]
106 }
107
108 #[inline]
109 fn function_salt(&self) -> u64 {
110 // Stable hash of function name + namespace
111 let full_name = if self.namespace().is_empty() {
112 self.name().to_string()
113 } else {
114 format!("{}::{}", self.namespace(), self.name())
115 };
116 crate::rng::fnv1a64(full_name.as_bytes())
117 }
118
119 /// The unified evaluation path.
120 ///
121 /// This method replaces the separate scalar, fold, and map paths.
122 /// Functions use the provided `ArgumentHandle`s to access inputs as either
123 /// scalars or `RangeView`s (Arrow-backed virtual ranges).
124 fn eval<'a, 'b, 'c>(
125 &self,
126 args: &'c [ArgumentHandle<'a, 'b>],
127 ctx: &dyn crate::traits::FunctionContext<'b>,
128 ) -> Result<crate::traits::CalcValue<'b>, ExcelError>;
129
130 /// Optional reference result path. Only called by the interpreter/engine
131 /// when the callsite expects a reference (e.g., range combinators, by-ref
132 /// argument positions, or spill sources).
133 ///
134 /// Default implementation returns `None`, indicating the function does not
135 /// support returning references. Functions that set `RETURNS_REFERENCE`
136 /// should override this.
137 fn eval_reference<'a, 'b, 'c>(
138 &self,
139 _args: &'c [ArgumentHandle<'a, 'b>],
140 _ctx: &dyn crate::traits::FunctionContext<'b>,
141 ) -> Option<Result<formualizer_parse::parser::ReferenceType, ExcelError>> {
142 None
143 }
144
145 /// Dispatch to the unified evaluation path with automatic argument validation.
146 fn dispatch<'a, 'b, 'c>(
147 &self,
148 args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
149 ctx: &dyn crate::traits::FunctionContext<'b>,
150 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
151 // Central argument validation
152 {
153 use crate::args::{ValidationOptions, validate_and_prepare};
154 let schema = self.arg_schema();
155 if let Err(e) =
156 validate_and_prepare(args, schema, ValidationOptions { warn_only: false })
157 {
158 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
159 }
160 }
161
162 self.eval(args, ctx)
163 }
164}