1use core::panic;
5
6use crate::{
7 args::ArgSchema,
8 function_contract::{FunctionDependencyContract, FunctionSemanticContract},
9 traits::ArgumentHandle,
10};
11use formualizer_common::{ExcelError, LiteralValue};
12use formualizer_parse::parser::ReferenceType;
13
14pub(crate) fn catch_function_panic<T>(
16 name: &str,
17 run: impl FnOnce() -> T,
18) -> Result<T, ExcelError> {
19 catch_external_panic(run).map_err(|payload| ExcelError::from_function_panic(name, &*payload))
20}
21
22pub(crate) fn catch_external_panic<T>(
24 run: impl FnOnce() -> T,
25) -> Result<T, Box<dyn std::any::Any + Send>> {
26 std::panic::catch_unwind(std::panic::AssertUnwindSafe(run))
27}
28
29pub(crate) trait PanicResult {
31 fn panicked(error: ExcelError) -> Self;
32}
33impl<T> PanicResult for Result<T, ExcelError> {
34 fn panicked(error: ExcelError) -> Self {
35 Err(error)
36 }
37}
38impl<T> PanicResult for Option<Result<T, ExcelError>> {
39 fn panicked(error: ExcelError) -> Self {
40 Some(Err(error))
41 }
42}
43
44#[doc(hidden)]
46#[derive(Clone)]
47pub enum FunctionResolution<'a> {
48 Reference(ReferenceType),
49 ReferenceError(ExcelError),
50 Value(crate::traits::CalcValue<'a>),
51}
52
53pub(crate) fn resolution_to_reference(
54 result: Result<FunctionResolution<'_>, ExcelError>,
55) -> Option<Result<ReferenceType, ExcelError>> {
56 match result {
57 Ok(FunctionResolution::Reference(reference)) => Some(Ok(reference)),
58 Ok(FunctionResolution::ReferenceError(error)) | Err(error) => Some(Err(error)),
59 Ok(FunctionResolution::Value(crate::traits::CalcValue::Scalar(LiteralValue::Error(
60 error,
61 )))) => Some(Err(error)),
62 Ok(FunctionResolution::Value(_)) => None,
63 }
64}
65
66bitflags::bitflags! {
67 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
72 pub struct FnCaps: u32 {
73 const PURE = 0b0000_0000_0001;
77 const VOLATILE = 0b0000_0000_0010;
80
81 const REDUCTION = 0b0000_0000_0100;
84 const ELEMENTWISE = 0b0000_0000_1000;
87 const WINDOWED = 0b0000_0001_0000;
89 const LOOKUP = 0b0000_0010_0000;
91
92 const NUMERIC_ONLY = 0b0000_0100_0000;
96 const BOOL_ONLY = 0b0000_1000_0000;
98
99 const SIMD_OK = 0b0001_0000_0000;
102 const STREAM_OK = 0b0010_0000_0000;
105 const GPU_OK = 0b0100_0000_0000;
107
108 const RETURNS_REFERENCE = 0b1000_0000_0000;
113
114 const SHORT_CIRCUIT = 0b0001_0000_0000_0000;
118 const PARALLEL_ARGS = 0b0010_0000_0000_0000;
121 const PARALLEL_CHUNKS= 0b0100_0000_0000_0000;
124 const DYNAMIC_DEPENDENCY = 0b1000_0000_0000_0000;
126 const LOCAL_ENVIRONMENT = 0b0001_0000_0000_0000_0000;
128 const MAY_SPILL = 0b0010_0000_0000_0000_0000;
130 }
131}
132
133#[doc(hidden)]
140#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141#[non_exhaustive]
142pub enum FamilyKernel {
143 Sum,
144 Average,
145 If,
147 CriteriaAggregate,
151 Lookup,
153 Round,
155 Abs,
156 Min,
157 Max,
158 And,
159 Or,
160 IfError,
161 Count,
163 IsNumber,
166 IsText,
167 IsLogical,
168 IsBlank,
169 IsError,
170 IsErr,
171 IsNa,
172 Year,
173 Month,
174 Day,
175 Weekday,
176}
177
178impl FamilyKernel {
179 pub(crate) fn name(self) -> &'static str {
180 match self {
181 Self::Sum => "SUM",
182 Self::Average => "AVERAGE",
183 Self::If => "IF",
184 Self::CriteriaAggregate => "CRITERIA",
185 Self::Lookup => "LOOKUP",
186 Self::Round => "ROUND",
187 Self::Abs => "ABS",
188 Self::Min => "MIN",
189 Self::Max => "MAX",
190 Self::And => "AND",
191 Self::Or => "OR",
192 Self::IfError => "IFERROR",
193 Self::Count => "COUNT",
194 Self::IsNumber => "ISNUMBER",
195 Self::IsText => "ISTEXT",
196 Self::IsLogical => "ISLOGICAL",
197 Self::IsBlank => "ISBLANK",
198 Self::IsError => "ISERROR",
199 Self::IsErr => "ISERR",
200 Self::IsNa => "ISNA",
201 Self::Year => "YEAR",
202 Self::Month => "MONTH",
203 Self::Day => "DAY",
204 Self::Weekday => "WEEKDAY",
205 }
206 }
207}
208
209pub trait Function: Send + Sync + 'static {
210 fn caps(&self) -> FnCaps {
212 FnCaps::PURE
213 }
214
215 #[doc(hidden)]
220 fn family_kernel(&self) -> Option<FamilyKernel> {
221 None
222 }
223
224 fn name(&self) -> &'static str;
225 fn namespace(&self) -> &'static str {
226 ""
227 }
228 fn min_args(&self) -> usize {
229 0
230 }
231 fn variadic(&self) -> bool {
232 false
233 }
234 fn volatile(&self) -> bool {
235 crate::external_callbacks::dispatch_metadata(self, FnCaps::empty, || self.caps())
236 .contains(FnCaps::VOLATILE)
237 }
238 fn arg_schema(&self) -> &'static [ArgSchema] {
239 if crate::external_callbacks::dispatch_metadata(self, || 0, || self.min_args()) > 0 {
240 panic!("Non-zero min_args must have a valid arg_schema");
241 } else {
242 &[]
243 }
244 }
245
246 fn aliases(&self) -> &'static [&'static str] {
250 &[]
251 }
252
253 fn dependency_contract(&self, _arity: usize) -> Option<FunctionDependencyContract> {
260 None
261 }
262
263 fn reports_known_limitations(&self) -> bool {
270 false
271 }
272
273 fn semantic_contract(&self, _arity: usize) -> Option<FunctionSemanticContract> {
278 None
279 }
280
281 #[inline]
282 fn function_salt(&self) -> u64 {
283 let full_name =
285 if crate::external_callbacks::dispatch_metadata(self, || "", || self.namespace())
286 .is_empty()
287 {
288 crate::external_callbacks::dispatch_metadata(self, || "", || self.name())
289 .to_string()
290 } else {
291 format!(
292 "{}::{}",
293 crate::external_callbacks::dispatch_metadata(self, || "", || self.namespace()),
294 crate::external_callbacks::dispatch_metadata(self, || "", || self.name())
295 )
296 };
297 crate::rng::fnv1a64(full_name.as_bytes())
298 }
299
300 fn propagate_format(
303 &self,
304 result: &crate::traits::CalcValue<'_>,
305 ) -> Option<crate::format::FormatId> {
306 let _ = result;
307 None
308 }
309
310 fn apply_format_propagation<'a>(
311 &self,
312 result: crate::traits::CalcValue<'a>,
313 ) -> crate::traits::CalcValue<'a> {
314 let format = crate::external_callbacks::dispatch_metadata(
315 self,
316 || None,
317 || self.propagate_format(&result),
318 );
319 result.with_format(format)
320 }
321
322 fn eval<'a, 'b, 'c>(
328 &self,
329 args: &'c [ArgumentHandle<'a, 'b>],
330 ctx: &dyn crate::traits::FunctionContext<'b>,
331 ) -> Result<crate::traits::CalcValue<'b>, ExcelError>;
332
333 fn eval_reference<'a, 'b, 'c>(
341 &self,
342 _args: &'c [ArgumentHandle<'a, 'b>],
343 _ctx: &dyn crate::traits::FunctionContext<'b>,
344 ) -> Option<Result<formualizer_parse::parser::ReferenceType, ExcelError>> {
345 None
346 }
347
348 fn resolve_reference_or_value<'a, 'b, 'c>(
355 &self,
356 args: &'c [ArgumentHandle<'a, 'b>],
357 ctx: &dyn crate::traits::FunctionContext<'b>,
358 value_fallback: &dyn Fn() -> Result<crate::traits::CalcValue<'b>, ExcelError>,
359 ) -> Result<FunctionResolution<'b>, ExcelError> {
360 match self.eval_reference(args, ctx) {
361 Some(Ok(reference)) => Ok(FunctionResolution::Reference(reference)),
362 Some(Err(error)) => Ok(FunctionResolution::ReferenceError(error)),
363 None => value_fallback().map(FunctionResolution::Value),
364 }
365 }
366
367 fn dispatch<'a, 'b, 'c>(
369 &self,
370 args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
371 ctx: &dyn crate::traits::FunctionContext<'b>,
372 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
373 if crate::external_callbacks::dispatch_metadata(self, FnCaps::empty, || self.caps())
382 .contains(FnCaps::SHORT_CIRCUIT)
383 {
384 if args.len()
385 < crate::external_callbacks::dispatch_metadata(self, || 0, || self.min_args())
386 {
387 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
388 ExcelError::new(formualizer_common::ExcelErrorKind::Value).with_message(
389 format!(
390 "Too few arguments: expected at least {}, got {}",
391 crate::external_callbacks::dispatch_metadata(
392 self,
393 || 0,
394 || self.min_args()
395 ),
396 args.len()
397 ),
398 ),
399 )));
400 }
401 return self
402 .eval(args, ctx)
403 .map(|result| self.apply_format_propagation(result));
404 }
405
406 {
408 use crate::args::{ValidationOptions, validate_and_prepare_inline};
409 let schema = crate::external_callbacks::dispatch_metadata(
410 self,
411 || &[] as &'static [ArgSchema],
412 || self.arg_schema(),
413 );
414 if let Err(e) = validate_and_prepare_inline(
415 args,
416 schema,
417 ValidationOptions {
418 warn_only: false,
419 min_args: crate::external_callbacks::dispatch_metadata(
420 self,
421 || 0,
422 || self.min_args(),
423 ),
424 },
425 ) {
426 return Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e)));
427 }
428 }
429
430 self.eval(args, ctx)
431 .map(|result| self.apply_format_propagation(result))
432 }
433}
434
435#[cfg(test)]
436mod argument_preparation_tests {
437 use super::*;
438 use crate::{test_workbook::TestWorkbook, traits::CalcValue};
439 use formualizer_common::ExcelErrorKind;
440 use std::sync::{
441 Arc,
442 atomic::{AtomicUsize, Ordering},
443 };
444
445 struct Count(Arc<AtomicUsize>);
446 impl Function for Count {
447 fn name(&self) -> &'static str {
448 "ARGCOUNT"
449 }
450 fn eval<'a, 'b, 'c>(
451 &self,
452 _args: &'c [ArgumentHandle<'a, 'b>],
453 _ctx: &dyn crate::traits::FunctionContext<'b>,
454 ) -> Result<CalcValue<'b>, ExcelError> {
455 self.0.fetch_add(1, Ordering::SeqCst);
456 Ok(CalcValue::Scalar(LiteralValue::Number(-7.0)))
457 }
458 }
459
460 #[test]
461 fn nested_validation_memo_is_linear() -> Result<(), Box<dyn std::error::Error>> {
462 crate::builtins::load_builtins();
463 let calls = Arc::new(AtomicUsize::new(0));
464 let wb = TestWorkbook::new().with_function(Arc::new(Count(Arc::clone(&calls))));
465 let interp = wb.interpreter();
466 let formula = format!("={}ARGCOUNT(){}", "ABS(".repeat(12), ")".repeat(12));
467 let ast = formualizer_parse::parser::parse(&formula)?;
468 for expected in 1..=2 {
469 assert_eq!(
470 interp.evaluate_ast(&ast)?.into_literal(),
471 LiteralValue::Number(7.0)
472 );
473 assert_eq!(
474 calls.load(Ordering::SeqCst),
475 expected,
476 "one leaf evaluation per recalc, not 2^depth"
477 );
478 }
479 Ok(())
480 }
481
482 #[test]
483 fn lazy_omitted_and_validation_errors_are_preserved() -> Result<(), Box<dyn std::error::Error>>
484 {
485 crate::builtins::load_builtins();
486 let calls = Arc::new(AtomicUsize::new(0));
487 let wb = TestWorkbook::new().with_function(Arc::new(Count(Arc::clone(&calls))));
488 let interp = wb.interpreter();
489 for (formula, expected) in [
490 ("=IF(TRUE,7,ARGCOUNT())", LiteralValue::Number(7.0)),
491 ("=IF(FALSE,ARGCOUNT(),7)", LiteralValue::Number(7.0)),
492 ("=IFERROR(7,ARGCOUNT())", LiteralValue::Number(7.0)),
493 ("=IFERROR(1/0,7)", LiteralValue::Number(7.0)),
494 (
495 "=CHOOSE(2,ARGCOUNT(),7,ARGCOUNT())",
496 LiteralValue::Number(7.0),
497 ),
498 ("=IF(TRUE,,ARGCOUNT())", LiteralValue::Number(0.0)),
499 ("=CHOOSE(2,ARGCOUNT(),)", LiteralValue::Number(0.0)),
500 ] {
501 let ast = formualizer_parse::parser::parse(formula)?;
502 assert_eq!(
503 interp.evaluate_ast(&ast)?.into_literal(),
504 expected,
505 "{formula}"
506 );
507 }
508 assert_eq!(
509 calls.load(Ordering::SeqCst),
510 0,
511 "untaken branches stay lazy"
512 );
513 for (formula, kind) in [
514 ("=ROUND(1/0,ARGCOUNT())", ExcelErrorKind::Div),
515 ("=ROUND(NA(),1/0)", ExcelErrorKind::Na),
516 ("=ROUND(NA())", ExcelErrorKind::Value),
517 ("=ABS(\"not a number\")", ExcelErrorKind::Value),
518 ] {
519 let ast = formualizer_parse::parser::parse(formula)?;
520 let result = interp.evaluate_ast(&ast)?.into_literal();
521 assert!(
522 matches!(result, LiteralValue::Error(ref e) if e.kind == kind),
523 "{formula}: {result:?}"
524 );
525 }
526 assert_eq!(
527 calls.load(Ordering::SeqCst),
528 0,
529 "validation stops at the first error"
530 );
531 Ok(())
532 }
533}