1#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
13#![forbid(unsafe_code)]
14
15use std::collections::HashMap;
16use std::sync::OnceLock;
17
18use crate::base::{Array, CellError, DateSystem, Value};
19
20macro_rules! f {
25 (volatile; $name:literal, $min:expr, $max:expr, $cat:ident, $scal:expr, $sig:literal, $desc:literal, $imp:expr) => {
26 $crate::functions::FnSpec {
27 name: $name,
28 min_args: $min,
29 max_args: $max,
30 category: $crate::functions::Category::$cat,
31 volatile: true,
32 scalar_args: $scal,
33 value_args: &[],
34 signature: $sig,
35 description: $desc,
36 imp: {
37 fn w(a: &[$crate::functions::Arg], c: &mut dyn $crate::functions::Ctx) -> crate::base::Value {
38 $crate::functions::util::finish(($imp)(a, c))
39 }
40 w
41 },
42 }
43 };
44 ($name:literal, $min:expr, $max:expr, $cat:ident, $scal:expr, $sig:literal, $desc:literal, $imp:expr) => {
45 $crate::functions::FnSpec {
46 name: $name,
47 min_args: $min,
48 max_args: $max,
49 category: $crate::functions::Category::$cat,
50 volatile: false,
51 scalar_args: $scal,
52 value_args: &[],
53 signature: $sig,
54 description: $desc,
55 imp: {
56 fn w(a: &[$crate::functions::Arg], c: &mut dyn $crate::functions::Ctx) -> crate::base::Value {
57 $crate::functions::util::finish(($imp)(a, c))
58 }
59 w
60 },
61 }
62 };
63}
64
65pub(crate) mod criteria;
66pub(crate) mod database;
67pub(crate) mod date;
68pub(crate) mod dynamic;
69pub(crate) mod engineering;
70pub(crate) mod ets;
71pub(crate) mod financial;
72pub(crate) mod info;
73pub(crate) mod lift;
74pub(crate) mod lookup;
75pub(crate) mod math;
76pub(crate) mod special;
77pub(crate) mod stat;
78pub(crate) mod text;
79pub(crate) mod util;
80
81pub use ets::Timeline;
82pub use stat::{aggregate_values, aggregate_values_k};
83
84#[derive(Clone, Debug)]
87pub struct Arg {
88 pub value: Value,
89 pub from_ref: bool,
90}
91
92impl Arg {
93 pub fn val(value: impl Into<Value>) -> Arg {
95 Arg { value: value.into(), from_ref: false }
96 }
97 pub fn reference(value: impl Into<Value>) -> Arg {
99 Arg { value: value.into(), from_ref: true }
100 }
101}
102
103pub trait Ctx {
105 fn date_system(&self) -> DateSystem;
106 fn now_serial(&self) -> f64;
108 fn random(&mut self) -> f64;
110 fn computed_criterion(&mut self, _row: usize, _col: usize, _db: &Array, _record: usize) -> Option<bool> {
114 None
115 }
116 fn has_lambda(&self) -> bool {
118 false
119 }
120 fn call_lambda(&mut self, _arg: Value) -> Option<Value> {
123 None
124 }
125}
126
127pub type FnImpl = fn(&[Arg], &mut dyn Ctx) -> Value;
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130pub enum Category {
131 Financial,
132 Logical,
133 Text,
134 DateTime,
135 Lookup,
136 MathTrig,
137 Statistical,
138 Engineering,
139 Information,
140 Database,
141 Compatibility,
142 Web,
143 Cube,
144 AddIn,
146}
147
148pub struct FnSpec {
149 pub name: &'static str,
151 pub min_args: usize,
152 pub max_args: usize,
154 pub category: Category,
155 pub volatile: bool,
156 pub scalar_args: &'static [bool],
161 pub value_args: &'static [bool],
166 pub signature: &'static str,
167 pub description: &'static str,
168 pub imp: FnImpl,
169}
170
171impl std::fmt::Debug for FnSpec {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 f.debug_struct("FnSpec").field("name", &self.name).field("min_args", &self.min_args).field("max_args", &self.max_args).finish()
174 }
175}
176
177impl FnSpec {
178 pub fn is_scalar_arg(&self, i: usize) -> bool {
180 match self.scalar_args.get(i) {
181 Some(b) => *b,
182 None => self.scalar_args.last().copied().unwrap_or(false),
183 }
184 }
185
186 pub fn is_value_arg(&self, i: usize) -> bool {
188 match self.value_args.get(i) {
189 Some(b) => *b,
190 None => self.value_args.last().copied().unwrap_or(false),
191 }
192 }
193
194 pub fn accepts(&self, n: usize) -> bool {
196 (self.min_args..=self.max_args).contains(&n) && n <= 255
197 }
198}
199
200pub(crate) const VAR: usize = usize::MAX;
202
203fn registry() -> &'static (Vec<FnSpec>, HashMap<&'static str, usize>) {
204 static REG: OnceLock<(Vec<FnSpec>, HashMap<&str, usize>)> = OnceLock::new();
205 REG.get_or_init(|| {
206 let mut v: Vec<FnSpec> = Vec::new();
207 for list in [
208 math::specs(),
209 stat::specs(),
210 ets::specs(),
211 text::specs(),
212 date::specs(),
213 lookup::specs(),
214 dynamic::specs(),
215 financial::specs(),
216 info::specs(),
217 engineering::specs(),
218 database::specs(),
219 ] {
220 v.extend(list);
221 }
222 for s in &mut v {
223 s.value_args = lift::value_args(s.name);
224 }
225 let mut map = HashMap::with_capacity(v.len());
226 for (i, s) in v.iter().enumerate() {
227 map.entry(s.name).or_insert(i);
228 }
229 (v, map)
230 })
231}
232
233pub fn lookup(name: &str) -> Option<&'static FnSpec> {
236 let mut n = name.trim().to_ascii_uppercase();
237 loop {
238 if let Some(r) = n.strip_prefix("_XLFN.") {
239 n = r.to_string();
240 } else if let Some(r) = n.strip_prefix("_XLWS.") {
241 n = r.to_string();
242 } else {
243 break;
244 }
245 }
246 let (v, map) = registry();
247 map.get(n.as_str()).and_then(|&i| v.get(i))
248}
249
250pub fn all() -> &'static [FnSpec] {
252 ®istry().0
253}
254
255pub fn call(spec: &FnSpec, args: &[Arg], ctx: &mut dyn Ctx) -> Value {
257 if !spec.accepts(args.len()) {
258 return Value::Error(CellError::Value);
259 }
260 lift::call_lifted(spec, args, ctx)
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn registry_is_consistent() {
269 let mut seen = std::collections::HashSet::new();
270 for s in all() {
271 assert!(seen.insert(s.name), "duplicate function {}", s.name);
272 assert_eq!(s.name, s.name.to_ascii_uppercase(), "{} not upper-case", s.name);
273 assert!(s.min_args <= s.max_args, "{} arity", s.name);
274 assert!(!s.description.is_empty() && !s.signature.is_empty(), "{} docs", s.name);
275 }
276 println!("registered functions: {}", all().len());
277 assert!(all().len() >= 400);
278 assert!(lookup("_xlfn._xlws.sort").is_some_and(|s| s.name == "SORT"));
279 assert!(lookup("nosuchfn").is_none());
280 for s in all() {
281 assert!(lookup(&format!("_xlfn.{}", s.name.to_ascii_lowercase())).is_some_and(|x| x.name == s.name));
282 }
283 }
284
285 #[test]
287 fn nan_index_is_a_value_error() {
288 use crate::base::CellError;
289 use crate::functions::util::testutil::*;
290 let table = rf(arr(vec![vec![tv("Field"), tv("Other")], vec![nv(1.0), nv(2.0)]]));
291 let nan = || av(Value::Number(f64::NAN));
292 assert_eq!(ev("VLOOKUP", vec![n(1.0), table.clone(), nan()]), Value::Error(CellError::Value));
293 assert_eq!(ev("HLOOKUP", vec![t("Field"), table.clone(), nan()]), Value::Error(CellError::Value));
294 assert_eq!(ev("DSUM", vec![table.clone(), nan(), table.clone()]), Value::Error(CellError::Value));
295 assert_eq!(ev("VLOOKUP", vec![n(1.0), table, n(2.0)]), Value::Number(2.0));
296 }
297
298 #[test]
301 fn regexreplace_bounds_its_result() {
302 use crate::base::CellError;
303 use crate::functions::util::testutil::*;
304 let many = "a".repeat(32_767);
305 assert_eq!(ev("REGEXREPLACE", vec![t(&many), t("."), t(&"b".repeat(32_767))]), Value::Error(CellError::Value));
306 assert_eq!(ev("REGEXREPLACE", vec![t(&many), t("a"), t("b")]), Value::text("b".repeat(32_767)));
307 assert_eq!(ev("REGEXREPLACE", vec![t("a1b22c"), t("[0-9]+"), t("#")]), Value::text("a#b#c"));
308 assert_eq!(ev("REGEXREPLACE", vec![t("2026-10-22"), t("(\\d+)-(\\d+)-(\\d+)"), t("$3/$2/$1")]), Value::text("22/10/2026"));
309 assert_eq!(ev("REGEXREPLACE", vec![t("abc"), t("x*"), t("-")]), Value::text("-a-b-c-"));
310 assert_eq!(ev("REGEXREPLACE", vec![t("a1b2"), t("[0-9]"), t("#"), n(2.0)]), Value::text("a1b#"));
311 assert_eq!(ev("REGEXREPLACE", vec![t(&many), t("^.*$"), t(&"$0".repeat(10_000))]), Value::Error(CellError::Value));
313 assert_eq!(ev("REGEXREPLACE", vec![t(&many), t("^.*$"), t(&"$0".repeat(10_000)), n(1.0)]), Value::Error(CellError::Value));
314 }
315
316 #[test]
317 fn hostile_inputs_never_panic() {
318 use crate::base::{Array, CellError};
319 use crate::functions::util::testutil::*;
320 let mixed = Value::from(
321 Array::new(2, 3, vec![Value::Number(1.0), Value::from("x"), Value::Empty, Value::Bool(true), Value::Number(-1e300), Value::Number(0.5)])
322 .unwrap(),
323 );
324 let patterns: Vec<Arg> = vec![
325 n(0.0),
326 n(-1.0),
327 n(1e308),
328 n(-1e308),
329 n(1e10),
330 n(2.5),
331 av(Value::Number(f64::NAN)),
333 av(Value::Number(f64::INFINITY)),
334 t(""),
335 t("abc"),
336 t("*?~"),
337 b(true),
338 e(CellError::NA),
339 empty(),
340 av(mixed.clone()),
341 rf(mixed),
342 rf(col(&[3.0, 1.0, 2.0])),
343 ];
344 for spec in all() {
345 let max = spec.max_args.min(spec.min_args + 3);
346 for count in spec.min_args..=max {
347 for p in &patterns {
348 let args = vec![p.clone(); count];
349 let _ = call(spec, &args, &mut TestCtx::default());
350 }
351 let args: Vec<Arg> = (0..count).map(|i| patterns[(i * 7 + count) % patterns.len()].clone()).collect();
353 let _ = call(spec, &args, &mut TestCtx::default());
354 }
355 }
356 }
357}