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::{
7 args::ArgSchema,
8 function_contract::{FunctionDependencyContract, FunctionSemanticContract},
9 traits::ArgumentHandle,
10};
11use formualizer_common::{ExcelError, LiteralValue};
12
13bitflags::bitflags! {
14 /// Describes the capabilities and properties of a function.
15 ///
16 /// This allows the engine to select optimal evaluation paths (e.g., vectorized,
17 /// parallel, GPU) and to enforce semantic contracts at compile time.
18 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19 pub struct FnCaps: u32 {
20 // --- Semantics ---
21 /// The function always produces the same output for the same input and has no
22 /// side effects. This is the default for most functions.
23 const PURE = 0b0000_0000_0001;
24 /// The function's output can change even with the same inputs (e.g., `RAND()`,
25 /// `NOW()`). Volatile functions are re-evaluated on every sheet change.
26 const VOLATILE = 0b0000_0000_0010;
27
28 // --- Shape / Evaluation Strategy ---
29 /// The function reduces a range of inputs to a single value (e.g., `SUM`, `AVERAGE`).
30 const REDUCTION = 0b0000_0000_0100;
31 /// The function operates on each element of its input ranges independently
32 /// (e.g., `SIN`, `ABS`).
33 const ELEMENTWISE = 0b0000_0000_1000;
34 /// The function operates on a sliding window over its input (e.g., `MOVING_AVERAGE`).
35 const WINDOWED = 0b0000_0001_0000;
36 /// The function performs a lookup or search operation (e.g., `VLOOKUP`).
37 const LOOKUP = 0b0000_0010_0000;
38
39 // --- Input Data Types ---
40 /// The function primarily operates on numbers. The engine can prepare
41 /// optimized numeric stripes (`&[f64]`) for it.
42 const NUMERIC_ONLY = 0b0000_0100_0000;
43 /// The function primarily operates on booleans.
44 const BOOL_ONLY = 0b0000_1000_0000;
45
46 // --- Backend Optimizations ---
47 /// The function has an implementation suitable for SIMD vectorization.
48 const SIMD_OK = 0b0001_0000_0000;
49 /// The function can process input as a stream, without materializing the
50 /// entire range in memory.
51 const STREAM_OK = 0b0010_0000_0000;
52 /// The function has a GPU-accelerated implementation.
53 const GPU_OK = 0b0100_0000_0000;
54
55 // --- Reference semantics ---
56 /// The function can return a reference (to a cell/range/table) when
57 /// evaluated in a reference context. When used in a value context,
58 /// engines may materialize the reference to a `LiteralValue`.
59 const RETURNS_REFERENCE = 0b1000_0000_0000;
60
61 // --- Planning / Interpreter parallelism hints ---
62 /// The function enforces left-to-right evaluation and early-exit semantics.
63 /// The planner must not evaluate arguments in parallel nor reorder them.
64 const SHORT_CIRCUIT = 0b0001_0000_0000_0000;
65 /// It is safe and potentially profitable to evaluate arguments in parallel.
66 /// The engine should still fold results in argument order for determinism.
67 const PARALLEL_ARGS = 0b0010_0000_0000_0000;
68 /// It is safe to chunk and process input windows in parallel (e.g., SUMIFS).
69 /// It is safe to chunk and process input windows in parallel (e.g., SUMIFS).
70 const PARALLEL_CHUNKS= 0b0100_0000_0000_0000;
71 /// Function has dynamic dependencies determined at runtime (e.g. INDIRECT, OFFSET).
72 const DYNAMIC_DEPENDENCY = 0b1000_0000_0000_0000;
73 /// Function establishes lexical bindings or evaluates a local call environment.
74 const LOCAL_ENVIRONMENT = 0b0001_0000_0000_0000_0000;
75 /// Function can produce a multi-cell dynamic-array result.
76 const MAY_SPILL = 0b0010_0000_0000_0000_0000;
77 }
78}
79
80/// Revised, object-safe trait for all Excel-style functions.
81///
82/// This trait uses a capability-based model (`FnCaps`) to declare function
83/// properties, enabling the evaluation engine to select the most optimal
84/// execution path (e.g., scalar, vectorized, parallel).
85pub trait Function: Send + Sync + 'static {
86 /// Capability flags for this function
87 fn caps(&self) -> FnCaps {
88 FnCaps::PURE
89 }
90
91 fn name(&self) -> &'static str;
92 fn namespace(&self) -> &'static str {
93 ""
94 }
95 fn min_args(&self) -> usize {
96 0
97 }
98 fn variadic(&self) -> bool {
99 false
100 }
101 fn volatile(&self) -> bool {
102 self.caps().contains(FnCaps::VOLATILE)
103 }
104 fn arg_schema(&self) -> &'static [ArgSchema] {
105 if self.min_args() > 0 {
106 panic!("Non-zero min_args must have a valid arg_schema");
107 } else {
108 &[]
109 }
110 }
111
112 /// Optional list of additional alias names (case-insensitive) that should resolve to this
113 /// function. Default: empty slice. Implementors can override to expose legacy names.
114 /// Returned slice must have 'static lifetime (typically a static array reference).
115 fn aliases(&self) -> &'static [&'static str] {
116 &[]
117 }
118
119 /// Optional dependency contract for passive planning/FormulaPlane analysis.
120 ///
121 /// The default is deliberately conservative: functions that do not opt in
122 /// must not receive dependency-summary optimization. Implementations should
123 /// return `Some` only for arities and argument roles they can describe
124 /// without under-approximating dependencies.
125 fn dependency_contract(&self, _arity: usize) -> Option<FunctionDependencyContract> {
126 None
127 }
128
129 /// Explicit semantic classification for this call arity.
130 ///
131 /// The public default is intentionally untrusted. The registry supplies a
132 /// sealed default only for crate-owned builtin registration.
133 fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
134 None
135 }
136
137 #[inline]
138 fn function_salt(&self) -> u64 {
139 // Stable hash of function name + namespace
140 let full_name = if self.namespace().is_empty() {
141 self.name().to_string()
142 } else {
143 format!("{}::{}", self.namespace(), self.name())
144 };
145 crate::rng::fnv1a64(full_name.as_bytes())
146 }
147
148 /// The unified evaluation path.
149 ///
150 /// This method replaces the separate scalar, fold, and map paths.
151 /// Functions use the provided `ArgumentHandle`s to access inputs as either
152 /// scalars or `RangeView`s (Arrow-backed virtual ranges).
153 fn eval<'a, 'b, 'c>(
154 &self,
155 args: &'c [ArgumentHandle<'a, 'b>],
156 ctx: &dyn crate::traits::FunctionContext<'b>,
157 ) -> Result<crate::traits::CalcValue<'b>, ExcelError>;
158
159 /// Optional reference result path. Only called by the interpreter/engine
160 /// when the callsite expects a reference (e.g., range combinators, by-ref
161 /// argument positions, or spill sources).
162 ///
163 /// Default implementation returns `None`, indicating the function does not
164 /// support returning references. Functions that set `RETURNS_REFERENCE`
165 /// should override this.
166 fn eval_reference<'a, 'b, 'c>(
167 &self,
168 _args: &'c [ArgumentHandle<'a, 'b>],
169 _ctx: &dyn crate::traits::FunctionContext<'b>,
170 ) -> Option<Result<formualizer_parse::parser::ReferenceType, ExcelError>> {
171 None
172 }
173
174 /// Dispatch to the unified evaluation path with automatic argument validation.
175 fn dispatch<'a, 'b, 'c>(
176 &self,
177 args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
178 ctx: &dyn crate::traits::FunctionContext<'b>,
179 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
180 // Short-circuit functions (IF/IFS/CHOOSE/SWITCH/AND/OR, ...) evaluate
181 // their arguments lazily inside `eval`; eagerly materializing every
182 // argument here would execute reads in untaken branches (defeating the
183 // documented short-circuit semantics) and double-evaluate taken ones.
184 // Their schemas are Any-kind with no per-arg coercion, so per-argument
185 // validation cannot fail; only the min-arity check is meaningful.
186 // (LET/LAMBDA already bypass validation via `dispatch` overrides for
187 // the same reason.)
188 if self.caps().contains(FnCaps::SHORT_CIRCUIT) {
189 if args.len() < self.min_args() {
190 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
191 ExcelError::new(formualizer_common::ExcelErrorKind::Value).with_message(
192 format!(
193 "Too few arguments: expected at least {}, got {}",
194 self.min_args(),
195 args.len()
196 ),
197 ),
198 )));
199 }
200 return self.eval(args, ctx);
201 }
202
203 // Central argument validation (includes min-arity check)
204 {
205 use crate::args::{ValidationOptions, validate_and_prepare};
206 let schema = self.arg_schema();
207 if let Err(e) = validate_and_prepare(
208 args,
209 schema,
210 ValidationOptions {
211 warn_only: false,
212 min_args: self.min_args(),
213 },
214 ) {
215 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
216 }
217 }
218
219 self.eval(args, ctx)
220 }
221}