1use std::cell::{Cell, RefCell};
6use std::collections::{HashMap, HashSet};
7use std::rc::Rc;
8
9use crate::ast::{AggregateCall, BinaryOp, Criteria, GroupOp, UnaryOp};
10use crate::format::{Culture, format_composite};
11use crate::functions::{Aggregate, Function};
12use crate::value::{DateTime, Value};
13
14pub trait Record: Sized {
16 fn field(&self, path: &[String]) -> Option<Value>;
18
19 fn collection(&self, path: &[String]) -> Option<Vec<&Self>>;
21}
22
23#[cfg(feature = "json")]
24fn json_at<'j>(base: &'j serde_json::Value, path: &[String]) -> Option<&'j serde_json::Value> {
25 let mut cur = base;
26 for seg in path {
27 cur = match cur {
28 serde_json::Value::Object(map) => map.get(seg)?,
29 serde_json::Value::Array(items) => items.get(seg.parse::<usize>().ok()?)?,
30 _ => return None,
31 };
32 }
33 Some(cur)
34}
35
36#[cfg(feature = "json")]
37impl Record for serde_json::Value {
38 fn field(&self, path: &[String]) -> Option<Value> {
39 json_at(self, path).map(Value::from_json)
40 }
41
42 fn collection(&self, path: &[String]) -> Option<Vec<&Self>> {
43 match json_at(self, path)? {
44 serde_json::Value::Array(items) => Some(items.iter().collect()),
45 _ => None,
46 }
47 }
48}
49
50impl Record for HashMap<String, Value> {
51 fn field(&self, path: &[String]) -> Option<Value> {
52 self.get(&path.join(".")).cloned()
53 }
54
55 fn collection(&self, _path: &[String]) -> Option<Vec<&Self>> {
56 None
57 }
58}
59
60pub type CustomFunction = Rc<dyn Fn(&[Value]) -> Result<Value, String>>;
62
63pub struct Environment {
66 parameters: HashMap<String, Value>,
67 calculated: HashMap<String, Criteria>,
68 functions: HashMap<String, CustomFunction>,
69 culture: Culture,
70 now: DateTime,
71 running: RefCell<HashMap<(usize, usize), (usize, f64)>>,
73 depth: Cell<u32>,
74 random: Cell<u64>,
75 error: RefCell<Option<EvalError>>,
76}
77
78impl std::fmt::Debug for Environment {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 f.debug_struct("Environment").field("culture", &self.culture.name).finish_non_exhaustive()
81 }
82}
83
84impl Default for Environment {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90impl Environment {
91 pub fn new() -> Self {
93 let now = DateTime::now();
94 Self {
95 parameters: HashMap::new(),
96 calculated: HashMap::new(),
97 functions: HashMap::new(),
98 culture: Culture::en_us(),
99 now,
100 running: RefCell::new(HashMap::new()),
101 depth: Cell::new(0),
102 random: Cell::new(
103 (now.days as u64).wrapping_mul(86_400) ^ u64::from(now.seconds) ^ 0x9e37_79b9_7f4a_7c15,
104 ),
105 error: RefCell::new(None),
106 }
107 }
108
109 pub fn culture(mut self, culture: Culture) -> Self {
111 self.culture = culture;
112 self
113 }
114
115 pub fn formats(&self) -> &Culture {
117 &self.culture
118 }
119
120 pub fn at(mut self, now: DateTime) -> Self {
122 self.now = now;
123 self
124 }
125
126 pub fn parameter(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
128 self.parameters.insert(name.into(), value.into());
129 self
130 }
131
132 pub fn calculated(mut self, name: impl Into<String>, expression: Criteria) -> Self {
134 self.calculated.insert(name.into(), expression);
135 self
136 }
137
138 pub fn register(
151 mut self,
152 name: &str,
153 function: impl Fn(&[Value]) -> Result<Value, String> + 'static,
154 ) -> Self {
155 self.functions.insert(name.to_lowercase(), Rc::new(function));
156 self
157 }
158
159 pub fn has_function(&self, name: &str) -> bool {
161 self.functions.contains_key(&name.to_lowercase())
162 }
163
164 fn fail(&self, error: EvalError) {
165 self.error.borrow_mut().get_or_insert(error);
166 }
167}
168
169#[derive(Clone, Debug, PartialEq, Eq)]
172#[non_exhaustive]
173pub enum EvalError {
174 UnknownFunction(String),
176 FunctionFailed {
178 name: String,
180 message: String,
182 },
183 Cycle(String),
185}
186
187impl std::fmt::Display for EvalError {
188 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189 match self {
190 Self::UnknownFunction(name) => write!(f, "unknown function '{name}'"),
191 Self::FunctionFailed { name, message } => write!(f, "function '{name}' failed: {message}"),
192 Self::Cycle(name) => write!(f, "calculated field '{name}' depends on itself"),
193 }
194 }
195}
196
197impl std::error::Error for EvalError {}
198
199pub struct Scope<'a, R> {
202 env: &'a Environment,
203 row: Option<&'a R>,
204 parent: Option<&'a R>,
205 rows: &'a [&'a R],
206 position: Option<usize>,
207 page: Option<(usize, usize)>,
208}
209
210impl<R> std::fmt::Debug for Scope<'_, R> {
211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212 f.debug_struct("Scope")
213 .field("rows", &self.rows.len())
214 .field("position", &self.position)
215 .finish_non_exhaustive()
216 }
217}
218
219impl<R> Clone for Scope<'_, R> {
220 fn clone(&self) -> Self {
221 *self
222 }
223}
224
225impl<R> Copy for Scope<'_, R> {}
226
227impl<'a, R: Record> Scope<'a, R> {
228 pub fn new(env: &'a Environment) -> Self {
230 Self { env, row: None, parent: None, rows: &[], position: None, page: None }
231 }
232
233 pub fn row(mut self, row: &'a R) -> Self {
235 self.row = Some(row);
236 self
237 }
238
239 pub fn maybe_row(mut self, row: Option<&'a R>) -> Self {
241 self.row = row;
242 self
243 }
244
245 pub fn parent(mut self, parent: Option<&'a R>) -> Self {
247 self.parent = parent;
248 self
249 }
250
251 pub fn rows(mut self, rows: &'a [&'a R]) -> Self {
253 self.rows = rows;
254 self
255 }
256
257 pub fn position(mut self, position: Option<usize>) -> Self {
259 self.position = position;
260 self
261 }
262
263 pub fn page(mut self, number: usize, count: usize) -> Self {
265 self.page = Some((number, count));
266 self
267 }
268
269 pub fn current_row(&self) -> Option<&'a R> {
271 self.row
272 }
273
274 pub fn current_rows(&self) -> &'a [&'a R] {
276 self.rows
277 }
278
279 pub fn evaluate(&self, criteria: &Criteria) -> Result<Value, EvalError> {
281 let value = self.node(criteria);
282 match self.env.error.borrow_mut().take() {
283 Some(e) => Err(e),
284 None => Ok(value),
285 }
286 }
287
288 pub fn matches(&self, criteria: &Criteria) -> bool {
290 self.evaluate(criteria).is_ok_and(|v| v.is_true())
291 }
292
293 fn field(&self, path: &[String]) -> Value {
294 let (base, path) = match path.split_first() {
295 Some((first, rest)) if first == "^" => (self.parent, rest),
296 _ => (self.row, path),
297 };
298 if path.len() == 1
299 && let Some(calc) = self.env.calculated.get(&path[0])
300 {
301 if self.env.depth.get() > 32 {
302 self.env.fail(EvalError::Cycle(path[0].clone()));
303 return Value::Null;
304 }
305 self.env.depth.set(self.env.depth.get() + 1);
306 let v = self.node(calc);
307 self.env.depth.set(self.env.depth.get() - 1);
308 return v;
309 }
310 base.and_then(|b| b.field(path)).unwrap_or_default()
311 }
312
313 fn node(&self, n: &Criteria) -> Value {
314 match n {
315 Criteria::Value(v) => v.clone(),
316 Criteria::Property(path) => self.field(path),
317 Criteria::Parameter(name) => self.env.parameters.get(name).cloned().unwrap_or_default(),
318 Criteria::Unary(op, a) => {
319 let v = self.node(a);
320 match op {
321 UnaryOp::Not => Value::Bool(!v.is_true()),
322 UnaryOp::Minus => v.as_number().map_or(Value::Null, |x| Value::Number(-x)),
323 UnaryOp::BitNot => {
324 v.as_number().map_or(Value::Null, |x| Value::Number(!(x as i64) as f64))
325 }
326 }
327 }
328 Criteria::Group(GroupOp::And, items) => Value::Bool(items.iter().all(|c| self.node(c).is_true())),
329 Criteria::Group(GroupOp::Or, items) => Value::Bool(items.iter().any(|c| self.node(c).is_true())),
330 Criteria::Binary(op, a, b) => binary(*op, self.node(a), self.node(b)),
331 Criteria::In(a, items) => {
332 let v = self.node(a);
333 Value::Bool(items.iter().any(|i| v.loose_eq(&self.node(i))))
334 }
335 Criteria::Between(a, lo, hi) => {
336 let v = self.node(a);
337 if v.is_null() {
338 return Value::Bool(false);
339 }
340 Value::Bool(v.compare(&self.node(lo)).is_ge() && v.compare(&self.node(hi)).is_le())
341 }
342 Criteria::IsNull(a) => Value::Bool(self.node(a).is_null()),
343 Criteria::Function(f, args) => self.call(*f, args),
344 Criteria::Custom(name, args) => {
345 let Some(function) = self.env.functions.get(&name.to_lowercase()).cloned() else {
346 self.env.fail(EvalError::UnknownFunction(name.clone()));
347 return Value::Null;
348 };
349 let values: Vec<Value> = args.iter().map(|a| self.node(a)).collect();
350 function(&values).unwrap_or_else(|message| {
351 self.env.fail(EvalError::FunctionFailed { name: name.clone(), message });
352 Value::Null
353 })
354 }
355 Criteria::Aggregate(call) => self.aggregate_call(call),
356 }
357 }
358
359 fn aggregate_call(&self, call: &AggregateCall) -> Value {
360 let key = std::ptr::from_ref(call) as usize;
361 match &call.collection {
362 None => {
363 let filtered: Vec<&R>;
364 let rows: &[&R] = match &call.condition {
365 Some(cond) => {
366 filtered = self
367 .rows
368 .iter()
369 .copied()
370 .filter(|r| self.at_row(r, None).node(cond).is_true())
371 .collect();
372 &filtered
373 }
374 None => self.rows,
375 };
376 self.aggregate(call, rows, key)
377 }
378 Some(path) => {
379 let base = match path.split_first() {
380 Some((first, rest)) if first == "^" => self.parent.and_then(|p| p.collection(rest)),
381 _ => self.row.and_then(|r| r.collection(path)),
382 };
383 let Some(items) = base else {
384 return match call.function {
385 Aggregate::Count | Aggregate::DistinctCount => Value::Number(0.0),
386 Aggregate::Exists => Value::Bool(false),
387 _ => Value::Null,
388 };
389 };
390 let inner = Scope { parent: self.row, position: None, ..*self };
392 let rows: Vec<&R> = match &call.condition {
393 Some(cond) => {
394 items.into_iter().filter(|r| inner.at_row(r, None).node(cond).is_true()).collect()
395 }
396 None => items,
397 };
398 aggregate_over(self.env, call, &rows, self.row, None, self.page, key)
400 }
401 }
402 }
403
404 fn at_row(&self, row: &'a R, position: Option<usize>) -> Self {
405 Scope { row: Some(row), position, ..*self }
406 }
407
408 fn aggregate(&self, call: &AggregateCall, rows: &[&R], key: usize) -> Value {
409 aggregate_over(
410 self.env,
411 call,
412 rows,
413 self.parent,
414 self.position.filter(|_| rows.len() == self.rows.len()),
415 self.page,
416 key,
417 )
418 }
419
420 #[allow(clippy::too_many_lines, reason = "one arm per function is the clearest form")]
421 fn call(&self, f: Function, args: &[Criteria]) -> Value {
422 use Function as F;
423 let a = |i: usize| args.get(i).map(|n| self.node(n)).unwrap_or_default();
424 let text = |i: usize| a(i).to_text();
425 let num = |i: usize| a(i).as_number();
426 let date = |i: usize| a(i).as_date();
427 let math = |g: fn(f64) -> f64| num(0).map_or(Value::Null, |x| Value::Number(g(x)));
428 let part = |g: fn(DateTime) -> i64| date(0).map_or(Value::Null, |d| Value::Number(g(d) as f64));
429 let add = |unit: i64| match (date(0), num(1)) {
430 (Some(d), Some(n)) => Value::Date(add_seconds(d, n as i64 * unit)),
431 _ => Value::Null,
432 };
433 let diff = |g: fn(DateTime, DateTime) -> i64| match (date(0), date(1)) {
434 (Some(x), Some(y)) => Value::Number(g(x, y) as f64),
435 _ => Value::Null,
436 };
437 let now = self.env.now;
438 let today = DateTime { seconds: 0, ..now };
439 let month_start = |d: DateTime| {
440 let (y, m, _) = d.ymd();
441 DateTime::from_ymd(y, m, 1).unwrap_or(d)
442 };
443 let year_start = |d: DateTime| DateTime::from_ymd(d.ymd().0, 1, 1).unwrap_or(d);
444 let week_start = |d: DateTime| DateTime { seconds: 0, ..d.add_days(-i64::from(d.weekday())) };
445 let month_is = |m: u32| Value::Bool(date(0).is_some_and(|d| d.ymd().1 == m));
446 let in_days = |from: DateTime, to: DateTime| {
447 Value::Bool(date(0).is_some_and(|d| d.days >= from.days && d.days < to.days))
448 };
449 match f {
450 F::Iif => {
451 let mut i = 0;
452 while i + 1 < args.len() {
453 if a(i).is_true() {
454 return a(i + 1);
455 }
456 i += 2;
457 }
458 a(args.len() - 1)
459 }
460 F::IsNull if args.len() == 1 => Value::Bool(a(0).is_null()),
461 F::IsNull => match a(0) {
462 Value::Null => a(1),
463 v => v,
464 },
465 F::IsNullOrEmpty => Value::Bool(match a(0) {
466 Value::Null => true,
467 Value::Text(s) => s.is_empty(),
468 _ => false,
469 }),
470 F::Ascii => text(0).chars().next().map_or(Value::Null, |c| Value::Number(f64::from(c as u32))),
471 F::Char => num(0)
472 .and_then(|n| char::from_u32(n as u32))
473 .map_or(Value::Null, |c| Value::Text(c.to_string())),
474 F::CharIndex => {
475 let (needle, hay) = (text(0), text(1));
476 let start = num(2).unwrap_or(0.0).max(0.0) as usize;
477 let skip: usize = hay.chars().take(start).map(char::len_utf8).sum();
478 let found = hay[skip..].find(&needle).map(|b| start + hay[skip..skip + b].chars().count());
479 Value::Number(found.map_or(-1.0, |i| i as f64))
480 }
481 F::Concat => Value::Text((0..args.len()).map(text).collect()),
482 F::Contains => Value::Bool(text(0).contains(&text(1))),
483 F::EndsWith => Value::Bool(text(0).ends_with(&text(1))),
484 F::Insert => {
485 let mut chars: Vec<char> = text(0).chars().collect();
486 let at = (num(1).unwrap_or(0.0).max(0.0) as usize).min(chars.len());
487 chars.splice(at..at, text(2).chars());
488 Value::Text(chars.into_iter().collect())
489 }
490 F::Len => Value::Number(text(0).chars().count() as f64),
491 F::Lower => Value::Text(text(0).to_lowercase()),
492 F::NewLine => Value::Text("\n".into()),
493 F::PadLeft | F::PadRight => {
494 let s = text(0);
495 let width = num(1).unwrap_or(0.0).max(0.0) as usize;
496 let fill = text(2).chars().next().unwrap_or(' ');
497 let pad: String =
498 std::iter::repeat_n(fill, width.saturating_sub(s.chars().count())).collect();
499 Value::Text(if f == F::PadLeft { pad + &s } else { s + &pad })
500 }
501 F::Remove => {
502 let chars: Vec<char> = text(0).chars().collect();
503 let start = (num(1).unwrap_or(0.0).max(0.0) as usize).min(chars.len());
504 let count = num(2).map_or(chars.len() - start, |n| n.max(0.0) as usize);
505 let end = start.saturating_add(count).min(chars.len());
506 Value::Text(chars[..start].iter().chain(&chars[end..]).collect())
507 }
508 F::Replace => Value::Text(text(0).replace(&text(1), &text(2))),
509 F::Reverse => Value::Text(text(0).chars().rev().collect()),
510 F::StartsWith => Value::Bool(text(0).starts_with(&text(1))),
511 F::Substring => {
512 let start = num(1).unwrap_or(0.0).max(0.0) as usize;
513 let count = num(2).map_or(usize::MAX, |n| n.max(0.0) as usize);
514 Value::Text(text(0).chars().skip(start).take(count).collect())
515 }
516 F::ToStr => Value::Text(text(0)),
517 F::Trim => Value::Text(text(0).trim().to_owned()),
518 F::Upper => Value::Text(text(0).to_uppercase()),
519 F::Abs => math(f64::abs),
520 F::Acos => math(f64::acos),
521 F::Asin => math(f64::asin),
522 F::Atn => math(f64::atan),
523 F::Atn2 => match (num(0), num(1)) {
524 (Some(y), Some(x)) => Value::Number(y.atan2(x)),
525 _ => Value::Null,
526 },
527 F::BigMul => match (num(0), num(1)) {
528 (Some(x), Some(y)) => Value::Number(x * y),
529 _ => Value::Null,
530 },
531 F::Ceiling => math(f64::ceil),
532 F::Cos => math(f64::cos),
533 F::Cosh => math(f64::cosh),
534 F::Exp => math(f64::exp),
535 F::Floor => math(f64::floor),
536 F::Log => match (num(0).filter(|x| *x > 0.0), num(1)) {
537 (Some(x), Some(base)) => Value::Number(x.log(base)),
538 (Some(x), None) => Value::Number(x.ln()),
539 _ => Value::Null,
540 },
541 F::Log10 => num(0).filter(|x| *x > 0.0).map_or(Value::Null, |x| Value::Number(x.log10())),
542 F::Max2 | F::Min2 => {
543 let (x, y) = (a(0), a(1));
544 let first = if f == F::Min2 { x.compare(&y).is_le() } else { x.compare(&y).is_ge() };
545 if first { x } else { y }
546 }
547 F::Power => match (num(0), num(1)) {
548 (Some(x), Some(y)) => Value::Number(x.powf(y)),
549 _ => Value::Null,
550 },
551 F::Rnd => {
552 let mut x = self.env.random.get();
553 x ^= x << 13;
554 x ^= x >> 7;
555 x ^= x << 17;
556 self.env.random.set(x);
557 Value::Number((x >> 11) as f64 / (1u64 << 53) as f64)
558 }
559 F::Round => num(0).map_or(Value::Null, |x| {
561 let p = 10f64.powi(num(1).unwrap_or(0.0) as i32);
562 Value::Number((x * p).round() / p)
563 }),
564 F::Sign => num(0).map_or(Value::Null, |x| Value::Number(if x == 0.0 { 0.0 } else { x.signum() })),
565 F::Sin => math(f64::sin),
566 F::Sinh => math(f64::sinh),
567 F::Sqr | F::Sqrt => num(0).filter(|x| *x >= 0.0).map_or(Value::Null, |x| Value::Number(x.sqrt())),
568 F::Tan => math(f64::tan),
569 F::Tanh => math(f64::tanh),
570 F::ToDecimal | F::ToDouble | F::ToFloat | F::ToNumber => {
571 num(0).map_or(Value::Null, Value::Number)
572 }
573 F::ToInt | F::ToLong => math(f64::trunc),
574 F::AddDays => add(86_400),
575 F::AddHours => add(3600),
576 F::AddMinutes => add(60),
577 F::AddSeconds => add(1),
578 F::AddMonths | F::AddYears => match (date(0), num(1)) {
579 (Some(d), Some(n)) => {
580 Value::Date(d.add_months(n as i64 * if f == F::AddYears { 12 } else { 1 }))
581 }
582 _ => Value::Null,
583 },
584 F::DateDiffDay => diff(|x, y| y.days - x.days),
585 F::DateDiffHour => diff(|x, y| (total_seconds(y) - total_seconds(x)) / 3600),
586 F::DateDiffMinute => diff(|x, y| (total_seconds(y) - total_seconds(x)) / 60),
587 F::DateDiffSecond => diff(|x, y| total_seconds(y) - total_seconds(x)),
588 F::DateDiffMonth => diff(|x, y| {
589 let (xy, xm, _) = x.ymd();
590 let (yy, ym, _) = y.ymd();
591 (i64::from(yy) - i64::from(xy)) * 12 + i64::from(ym) - i64::from(xm)
592 }),
593 F::DateDiffYear => diff(|x, y| i64::from(y.ymd().0) - i64::from(x.ymd().0)),
594 F::DateTimeFromParts => {
595 let n = |i: usize, default: f64| num(i).unwrap_or(default);
596 let d = DateTime::from_ymd(n(0, 0.0) as i32, n(1, 0.0) as u32, n(2, 0.0) as u32);
597 d.map_or(Value::Null, |d| {
598 Value::Date(DateTime {
599 seconds: (n(3, 0.0) * 3600.0 + n(4, 0.0) * 60.0 + n(5, 0.0)) as u32,
600 ..d
601 })
602 })
603 }
604 F::GetDate => date(0).map_or(Value::Null, |d| Value::Date(DateTime { seconds: 0, ..d })),
605 F::GetDay => part(|d| i64::from(d.ymd().2)),
606 F::GetDayOfWeek => part(|d| i64::from(d.weekday())),
607 F::GetDayOfYear => {
608 part(|d| d.days - DateTime::from_ymd(d.ymd().0, 1, 1).map_or(d.days, |s| s.days) + 1)
609 }
610 F::GetHour => part(|d| i64::from(d.hms().0)),
611 F::GetMinute => part(|d| i64::from(d.hms().1)),
612 F::GetMonth => part(|d| i64::from(d.ymd().1)),
613 F::GetSecond => part(|d| i64::from(d.hms().2)),
614 F::GetYear => part(|d| i64::from(d.ymd().0)),
615 F::Now | F::UtcNow | F::LocalDateTimeNow => Value::Date(now),
616 F::Today | F::LocalDateTimeToday => Value::Date(today),
617 F::LocalDateTimeTomorrow => Value::Date(today.add_days(1)),
618 F::LocalDateTimeYesterday => Value::Date(today.add_days(-1)),
619 F::LocalDateTimeDayAfterTomorrow => Value::Date(today.add_days(2)),
620 F::LocalDateTimeThisWeek => Value::Date(week_start(now)),
621 F::LocalDateTimeLastWeek => Value::Date(week_start(now).add_days(-7)),
622 F::LocalDateTimeNextWeek => Value::Date(week_start(now).add_days(7)),
623 F::LocalDateTimeTwoWeeksAway => Value::Date(week_start(now).add_days(14)),
624 F::LocalDateTimeThisMonth => Value::Date(month_start(now)),
625 F::LocalDateTimeLastMonth => Value::Date(month_start(now).add_months(-1)),
626 F::LocalDateTimeNextMonth => Value::Date(month_start(now).add_months(1)),
627 F::LocalDateTimeTwoMonthsAway => Value::Date(month_start(now).add_months(2)),
628 F::LocalDateTimeThisYear => Value::Date(year_start(now)),
629 F::LocalDateTimeLastYear => Value::Date(year_start(now).add_months(-12)),
630 F::LocalDateTimeNextYear => Value::Date(year_start(now).add_months(12)),
631 F::LocalDateTimeTwoYearsAway => Value::Date(year_start(now).add_months(24)),
632 F::LocalDateTimeYearBeforeToday => Value::Date(today.add_months(-12)),
633 F::InDateRange => match (date(0), date(1), date(2)) {
634 (Some(d), Some(lo), Some(hi)) => Value::Bool(d.days >= lo.days && d.days <= hi.days),
635 _ => Value::Bool(false),
636 },
637 F::InTimeRange | F::OutOfTimeRange => match (date(0), date(1), date(2)) {
638 (Some(d), Some(lo), Some(hi)) => {
639 let (t, lo, hi) = (d.seconds, lo.seconds, hi.seconds);
640 let inside = if lo <= hi { t >= lo && t <= hi } else { t >= lo || t <= hi };
642 Value::Bool(inside == (f == F::InTimeRange))
643 }
644 _ => Value::Bool(false),
645 },
646 F::IsSameDay => match date(0) {
647 Some(d) => Value::Bool((1..args.len()).any(|i| date(i).is_some_and(|o| o.days == d.days))),
648 None => Value::Bool(false),
649 },
650 F::IsThisWeek => in_days(week_start(now), week_start(now).add_days(7)),
651 F::IsThisMonth => in_days(month_start(now), month_start(now).add_months(1)),
652 F::IsLastMonth => in_days(month_start(now).add_months(-1), month_start(now)),
653 F::IsNextMonth => in_days(month_start(now).add_months(1), month_start(now).add_months(2)),
654 F::IsThisYear => in_days(year_start(now), year_start(now).add_months(12)),
655 F::IsLastYear => in_days(year_start(now).add_months(-12), year_start(now)),
656 F::IsNextYear => in_days(year_start(now).add_months(12), year_start(now).add_months(24)),
657 F::IsYearToDate => in_days(year_start(now), today.add_days(1)),
658 F::IsJanuary => month_is(1),
659 F::IsFebruary => month_is(2),
660 F::IsMarch => month_is(3),
661 F::IsApril => month_is(4),
662 F::IsMay => month_is(5),
663 F::IsJune => month_is(6),
664 F::IsJuly => month_is(7),
665 F::IsAugust => month_is(8),
666 F::IsSeptember => month_is(9),
667 F::IsOctober => month_is(10),
668 F::IsNovember => month_is(11),
669 F::IsDecember => month_is(12),
670 F::FormatString => {
671 let rest: Vec<Value> = (1..args.len()).map(a).collect();
672 Value::Text(format_composite(&text(0), &rest, &self.env.culture))
673 }
674 F::Rgb => color(&[num(0), num(1), num(2)]),
675 F::Argb => color(&[num(1), num(2), num(3), num(0)]),
676 F::PageNumber => self.page.map_or(Value::Null, |(n, _)| Value::Number(n as f64)),
677 F::PageCount => self.page.map_or(Value::Null, |(_, c)| Value::Number(c as f64)),
678 F::CurrentRowIndexInGroup => self.position.map_or(Value::Null, |p| Value::Number(p as f64)),
679 F::PrevRowColumnValue | F::NextRowColumnValue => {
680 let Some(p) = self.position else { return Value::Null };
681 let at = if f == F::PrevRowColumnValue { p.checked_sub(1) } else { p.checked_add(1) };
682 match at.and_then(|i| self.rows.get(i)) {
683 Some(row) => self.at_row(row, at).node(&args[0]),
684 None => Value::Null,
685 }
686 }
687 }
688 }
689}
690
691fn total_seconds(d: DateTime) -> i64 {
692 d.days * 86_400 + i64::from(d.seconds)
693}
694
695fn add_seconds(d: DateTime, n: i64) -> DateTime {
696 let t = total_seconds(d) + n;
697 DateTime { days: t.div_euclid(86_400), seconds: t.rem_euclid(86_400) as u32 }
698}
699
700fn color(parts: &[Option<f64>]) -> Value {
701 let mut s = String::from("#");
702 for p in parts {
703 let Some(p) = p else { return Value::Null };
704 s.push_str(&format!("{:02x}", p.clamp(0.0, 255.0) as u8));
705 }
706 Value::Text(s)
707}
708
709fn variance(values: &[f64], population: bool) -> Option<f64> {
710 let n = values.len() as f64;
711 let denominator = if population { n } else { n - 1.0 };
712 if values.is_empty() || denominator <= 0.0 {
713 return None;
714 }
715 let mean = values.iter().sum::<f64>() / n;
716 Some(values.iter().map(|v| (v - mean) * (v - mean)).sum::<f64>() / denominator)
717}
718
719fn number(v: Option<f64>) -> Value {
720 v.map_or(Value::Null, Value::Number)
721}
722
723#[allow(clippy::too_many_lines, reason = "one arm per aggregate is the clearest form")]
725fn aggregate_over<R: Record>(
726 env: &Environment,
727 call: &AggregateCall,
728 rows: &[&R],
729 parent: Option<&R>,
730 position: Option<usize>,
731 page: Option<(usize, usize)>,
732 key: usize,
733) -> Value {
734 let scope_at = |i: usize| Scope { env, row: Some(rows[i]), parent, rows, position: Some(i), page };
735 let arg = |i: usize, n: usize| -> Value {
736 match call.args.get(n) {
737 Some(a) => scope_at(i).node(a),
738 None => Value::Bool(true),
739 }
740 };
741 let values = || -> Vec<Value> { (0..rows.len()).map(|i| arg(i, 0)).collect() };
742 let numbers = || -> Vec<f64> { values().iter().filter_map(Value::as_number).collect() };
743 let distinct = || -> Vec<f64> {
744 let mut seen = HashSet::new();
745 numbers().into_iter().filter(|n| seen.insert(n.to_bits())).collect()
746 };
747 use Aggregate as A;
748 match call.function {
749 A::Count if call.args.is_empty() => Value::Number(rows.len() as f64),
750 A::Count => Value::Number(values().iter().filter(|v| !v.is_null()).count() as f64),
751 A::Sum => Value::Number(numbers().iter().sum()),
752 A::Avg => {
753 let n = numbers();
754 if n.is_empty() { Value::Null } else { Value::Number(n.iter().sum::<f64>() / n.len() as f64) }
755 }
756 A::Min | A::Max => values()
757 .into_iter()
758 .filter(|v| !v.is_null())
759 .reduce(|a, b| {
760 let take_b =
761 if call.function == A::Min { b.compare(&a).is_lt() } else { b.compare(&a).is_gt() };
762 if take_b { b } else { a }
763 })
764 .unwrap_or_default(),
765 A::Exists if call.args.is_empty() => Value::Bool(!rows.is_empty()),
766 A::Exists => Value::Bool((0..rows.len()).any(|i| arg(i, 0).is_true())),
767 A::Single => match rows.len() {
768 1 if call.args.is_empty() => Value::Bool(true),
769 1 => arg(0, 0),
770 _ => Value::Null,
771 },
772 A::Join => {
773 let sep = call.args.get(1).map_or_else(
774 || ", ".to_owned(),
775 |c| Scope { env, row: None, parent, rows, position: None, page }.node(c).to_text(),
776 );
777 Value::Text(
778 values().iter().filter(|v| !v.is_null()).map(Value::to_text).collect::<Vec<_>>().join(&sep),
779 )
780 }
781 A::First => {
782 if rows.is_empty() {
783 Value::Null
784 } else {
785 arg(0, 0)
786 }
787 }
788 A::Last => {
789 if rows.is_empty() {
790 Value::Null
791 } else {
792 arg(rows.len() - 1, 0)
793 }
794 }
795 A::Median => {
796 let mut n = numbers();
797 if n.is_empty() {
798 return Value::Null;
799 }
800 n.sort_by(f64::total_cmp);
801 let mid = n.len() / 2;
802 Value::Number(if n.len() % 2 == 1 { n[mid] } else { (n[mid - 1] + n[mid]) / 2.0 })
803 }
804 A::Var => number(variance(&numbers(), false)),
805 A::VarP => number(variance(&numbers(), true)),
806 A::StdDev => number(variance(&numbers(), false).map(f64::sqrt)),
807 A::StdDevP => number(variance(&numbers(), true).map(f64::sqrt)),
808 A::DistinctSum => Value::Number(distinct().iter().sum()),
809 A::DistinctCount => {
810 let set: HashSet<String> =
811 values().iter().filter(|v| !v.is_null()).map(|v| format!("{v:?}")).collect();
812 Value::Number(set.len() as f64)
813 }
814 A::DistinctAvg => {
815 let n = distinct();
816 if n.is_empty() { Value::Null } else { Value::Number(n.iter().sum::<f64>() / n.len() as f64) }
817 }
818 A::DistinctVar => number(variance(&distinct(), false)),
819 A::DistinctVarP => number(variance(&distinct(), true)),
820 A::DistinctStdDev => number(variance(&distinct(), false).map(f64::sqrt)),
821 A::DistinctStdDevP => number(variance(&distinct(), true).map(f64::sqrt)),
822 A::WeightedAvg => {
823 let (mut total, mut weight) = (0.0, 0.0);
824 for i in 0..rows.len() {
825 if let (Some(v), Some(w)) = (arg(i, 0).as_number(), arg(i, 1).as_number()) {
826 total += v * w;
827 weight += w;
828 }
829 }
830 if weight == 0.0 { Value::Null } else { Value::Number(total / weight) }
831 }
832 A::RecordNumber => position.map_or(Value::Null, |p| Value::Number(p as f64 + 1.0)),
833 A::Percentage => {
834 let total: f64 = numbers().iter().sum();
835 match position.filter(|p| *p < rows.len()) {
836 Some(p) if total != 0.0 => number(arg(p, 0).as_number().map(|v| v / total)),
837 _ => Value::Null,
838 }
839 }
840 A::RunningSum => {
841 let Some(upto) = position.filter(|p| *p < rows.len()) else {
843 return Value::Number(numbers().iter().sum());
844 };
845 let cache_key = (key, rows.as_ptr() as usize);
846 let cached = env.running.borrow().get(&cache_key).copied();
847 let total = match cached {
848 Some((last, acc)) if last + 1 == upto => acc + arg(upto, 0).as_number().unwrap_or(0.0),
849 Some((last, acc)) if last == upto => acc,
850 _ => (0..=upto).filter_map(|i| arg(i, 0).as_number()).sum(),
851 };
852 env.running.borrow_mut().insert(cache_key, (upto, total));
853 Value::Number(total)
854 }
855 }
856}
857
858fn like(text: &str, pattern: &str) -> bool {
860 let (t, p): (Vec<char>, Vec<char>) = (text.chars().collect(), pattern.chars().collect());
861 let (mut ti, mut pi, mut star, mut mark) = (0, 0, None, 0);
862 while ti < t.len() {
863 if pi < p.len() && (p[pi] == '_' || (p[pi] != '%' && p[pi] == t[ti])) {
864 ti += 1;
865 pi += 1;
866 } else if pi < p.len() && p[pi] == '%' {
867 star = Some(pi);
868 mark = ti;
869 pi += 1;
870 } else if let Some(s) = star {
871 pi = s + 1;
872 mark += 1;
873 ti = mark;
874 } else {
875 return false;
876 }
877 }
878 p[pi..].iter().all(|c| *c == '%')
879}
880
881fn binary(op: BinaryOp, x: Value, y: Value) -> Value {
882 use std::cmp::Ordering;
883 let cmp = |o: fn(Ordering) -> bool| {
884 if x.is_null() || y.is_null() { Value::Bool(false) } else { Value::Bool(o(x.compare(&y))) }
886 };
887 match op {
888 BinaryOp::Eq => {
889 if x.is_null() || y.is_null() {
890 return Value::Bool(x.is_null() && y.is_null());
891 }
892 Value::Bool(x.loose_eq(&y))
893 }
894 BinaryOp::Ne => match binary(BinaryOp::Eq, x, y) {
895 Value::Bool(b) => Value::Bool(!b),
896 v => v,
897 },
898 BinaryOp::Lt => cmp(Ordering::is_lt),
899 BinaryOp::Le => cmp(Ordering::is_le),
900 BinaryOp::Gt => cmp(Ordering::is_gt),
901 BinaryOp::Ge => cmp(Ordering::is_ge),
902 BinaryOp::Like => {
903 if x.is_null() || y.is_null() {
904 Value::Bool(false)
905 } else {
906 Value::Bool(like(&x.to_text(), &y.to_text()))
907 }
908 }
909 BinaryOp::Add => match (&x, &y) {
910 (Value::Null, _) | (_, Value::Null) => Value::Null,
911 (Value::Text(_), _) | (_, Value::Text(_)) => Value::Text(x.to_text() + &y.to_text()),
913 (Value::Date(d), _) => y.as_number().map_or(Value::Null, |n| Value::Date(d.add_days(n as i64))),
914 _ => arith(&x, &y, |a, b| a + b),
915 },
916 BinaryOp::Sub => match (&x, &y) {
917 (Value::Date(a), Value::Date(b)) => Value::Number((a.days - b.days) as f64),
918 (Value::Date(d), _) => {
919 y.as_number().map_or(Value::Null, |n| Value::Date(d.add_days(-(n as i64))))
920 }
921 _ => arith(&x, &y, |a, b| a - b),
922 },
923 BinaryOp::Mul => arith(&x, &y, |a, b| a * b),
924 BinaryOp::Div => match y.as_number() {
926 Some(d) if d != 0.0 => arith(&x, &y, |a, b| a / b),
927 _ => Value::Null,
928 },
929 BinaryOp::Rem => match y.as_number() {
930 Some(d) if d != 0.0 => arith(&x, &y, |a, b| a % b),
931 _ => Value::Null,
932 },
933 BinaryOp::BitAnd => arith(&x, &y, |a, b| ((a as i64) & (b as i64)) as f64),
934 BinaryOp::BitOr => arith(&x, &y, |a, b| ((a as i64) | (b as i64)) as f64),
935 BinaryOp::BitXor => arith(&x, &y, |a, b| ((a as i64) ^ (b as i64)) as f64),
936 }
937}
938
939fn arith(x: &Value, y: &Value, f: impl Fn(f64, f64) -> f64) -> Value {
940 match (x.as_number(), y.as_number()) {
941 (Some(a), Some(b)) => Value::Number(f(a, b)),
942 _ => Value::Null,
943 }
944}