1use crate::dates::{day_from_number, day_number, days_in_month, format_day, iso_week, parse_day, weekday_of};
12use crate::json;
13use serde_json::{Map, Value};
14
15pub const MAX_LENGTH: usize = 1000;
17pub const MAX_DEPTH: usize = 32;
18pub const MAX_STEPS: usize = 10_000;
19
20pub const DATE_UNITS: &[&str] = &["second", "minute", "hour", "day", "week", "month", "quarter", "year"];
22pub const CALENDAR_UNITS: &[&str] = &["day", "week", "month", "quarter", "year"];
23pub const DURATION_FORMATS: &[&str] = &["h:mm", "hh:mm"];
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum FunctionEffect {
33 Pure,
34 Write,
35 Host,
36}
37
38impl FunctionEffect {
39 pub fn as_str(self) -> &'static str {
41 match self {
42 FunctionEffect::Pure => "pure",
43 FunctionEffect::Write => "write",
44 FunctionEffect::Host => "host",
45 }
46 }
47}
48
49pub const WRITE_FUNCTIONS: &[&str] = &["set", "records.upsert", "records.delete", "records.batch"];
51pub const HOST_FUNCTIONS: &[&str] = &["openUrl", "bind", "app.navigate"];
53pub const HOST_FUNCTION_PREFIXES: &[&str] = &["actions.", "harness.", "exponential.", "tools.", "upstream."];
54
55pub fn function_effect(name: &str) -> FunctionEffect {
58 if WRITE_FUNCTIONS.contains(&name) {
59 return FunctionEffect::Write;
60 }
61 if HOST_FUNCTIONS.contains(&name) || HOST_FUNCTION_PREFIXES.iter().any(|p| name.starts_with(p)) {
62 return FunctionEffect::Host;
63 }
64 FunctionEffect::Pure
65}
66
67pub fn effect_words(name: &str, effect: FunctionEffect) -> String {
69 format!("{name} {}", if effect == FunctionEffect::Write { "writes" } else { "is a host function" })
70}
71
72#[derive(Clone, Copy, Default)]
74pub struct ExpressionEnv<'a> {
75 pub now: Option<f64>,
77 pub offset: Option<&'a (dyn Fn(f64) -> i32 + Send + Sync)>,
79 pub max_steps: Option<usize>,
81}
82
83impl std::fmt::Debug for ExpressionEnv<'_> {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.debug_struct("ExpressionEnv").field("now", &self.now).field("max_steps", &self.max_steps).finish_non_exhaustive()
86 }
87}
88
89struct Env<'a> {
90 now: f64,
91 offset: Option<&'a (dyn Fn(f64) -> i32 + Send + Sync)>,
92}
93
94impl<'a> Env<'a> {
95 fn of(env: &ExpressionEnv<'a>) -> Env<'a> {
96 Env { now: env.now.unwrap_or_else(crate::format::now_ms).floor(), offset: env.offset }
97 }
98 fn off(&self, ms: f64) -> f64 {
99 self.offset.map_or(0.0, |o| f64::from(o(ms)))
100 }
101}
102
103type R<T> = Result<T, String>;
104
105pub fn type_name(v: &Value) -> &'static str {
111 match v {
112 Value::Null => "null",
113 Value::Number(_) => "number",
114 Value::String(_) => "string",
115 Value::Bool(_) => "bool",
116 Value::Array(_) => "list",
117 Value::Object(_) => "map",
118 }
119}
120
121fn num(v: f64) -> R<f64> {
123 if !v.is_finite() {
124 return Err("number out of range".into());
125 }
126 Ok(if v == 0.0 { 0.0 } else { v })
127}
128
129fn num_text(n: f64) -> String {
130 json::number_to_string(n)
131}
132
133fn deep_equal(a: &Value, b: &Value) -> bool {
134 match (a, b) {
135 (Value::Number(x), Value::Number(y)) => x.as_f64() == y.as_f64(),
136 (Value::Array(x), Value::Array(y)) => x.len() == y.len() && x.iter().zip(y).all(|(a, b)| deep_equal(a, b)),
137 (Value::Object(x), Value::Object(y)) => x.len() == y.len() && x.iter().all(|(k, v)| y.get(k).is_some_and(|w| deep_equal(v, w))),
138 _ => a == b,
139 }
140}
141
142fn round_half_away(x: f64) -> f64 {
144 let a = x.abs();
145 let f = a.floor();
146 let r = if a - f >= 0.5 { f + 1.0 } else { f };
147 if x < 0.0 {
148 -r
149 } else {
150 r
151 }
152}
153
154fn utf16_cmp(a: &str, b: &str) -> std::cmp::Ordering {
155 a.encode_utf16().cmp(b.encode_utf16())
156}
157
158const DAY_MS: f64 = 86_400_000.0;
163
164fn unit_ms(unit: &str) -> Option<f64> {
165 match unit {
166 "second" => Some(1000.0),
167 "minute" => Some(60_000.0),
168 "hour" => Some(3_600_000.0),
169 _ => None,
170 }
171}
172
173#[derive(Clone, Copy)]
174enum Temporal {
175 Date(i64),
176 Ts(f64),
177}
178
179pub fn parse_timestamp(text: &str) -> Option<f64> {
182 let b = text.as_bytes();
183 let digit = |i: usize| b.get(i).is_some_and(u8::is_ascii_digit);
184 let int = |from: usize, n: usize| b[from..from + n].iter().fold(0i64, |acc, c| acc * 10 + i64::from(c - b'0'));
185 if b.len() < 16 || b[10] != b'T' || !(digit(11) && digit(12) && b[13] == b':' && digit(14) && digit(15)) {
186 return None;
187 }
188 let day = parse_day(&text[..10]).filter(|_| text.is_char_boundary(10))?;
189 if !(b[..10].iter().enumerate().all(|(i, c)| if i == 4 || i == 7 { *c == b'-' } else { c.is_ascii_digit() })) {
190 return None;
191 }
192 let (h, mi) = (int(11, 2), int(14, 2));
193 let mut i = 16;
194 let mut s = 0;
195 let mut ms = 0;
196 if b.get(i) == Some(&b':') {
197 if !(digit(i + 1) && digit(i + 2)) {
198 return None;
199 }
200 s = int(i + 1, 2);
201 i += 3;
202 if b.get(i) == Some(&b'.') {
203 let start = i + 1;
204 let mut end = start;
205 while digit(end) {
206 end += 1;
207 }
208 if end == start {
209 return None;
210 }
211 let frac: String = text[start..end.min(start + 3)].chars().collect();
212 ms = format!("{frac:0<3}").parse::<i64>().ok()?;
213 i = end;
214 }
215 }
216 let mut offset = 0;
217 match b.get(i) {
218 None => {}
219 Some(b'Z') if i + 1 == b.len() => {}
220 Some(c @ (b'+' | b'-')) if b.len() == i + 6 && digit(i + 1) && digit(i + 2) && b[i + 3] == b':' && digit(i + 4) && digit(i + 5) => {
221 let (oh, om) = (int(i + 1, 2), int(i + 4, 2));
222 if oh > 23 || om > 59 {
223 return None;
224 }
225 offset = (if *c == b'-' { -1 } else { 1 }) * (oh * 60 + om);
226 }
227 _ => return None,
228 }
229 if h > 23 || mi > 59 || s > 59 {
230 return None;
231 }
232 Some((day_number(day.y, day.m, day.d) as f64) * DAY_MS + (h * 3_600_000 + mi * 60_000 + s * 1000 + ms - offset * 60_000) as f64)
233}
234
235pub fn format_timestamp(ms: f64) -> R<String> {
237 if !(ms >= MIN_DAY as f64 * DAY_MS && ms < (MAX_DAY + 1) as f64 * DAY_MS) {
238 return Err("date out of range".into());
239 }
240 let day = (ms / DAY_MS).floor();
241 let tod = (ms - day * DAY_MS) as i64;
242 let d = day_from_number(day as i64);
243 if d.y < 0 || d.y > 9999 {
244 return Err("date out of range".into());
245 }
246 Ok(format!("{}T{:02}:{:02}:{:02}.{:03}Z", format_day(d), tod / 3_600_000, (tod / 60_000) % 60, (tod / 1000) % 60, tod % 1000))
247}
248
249const MIN_DAY: i64 = -719_528;
252const MAX_DAY: i64 = 2_932_896;
253const MAX_CALENDAR_STEPS: f64 = 4_000_000.0;
255
256fn day_text(day: i64) -> R<String> {
257 if !(MIN_DAY..=MAX_DAY).contains(&day) {
258 return Err("date out of range".into());
259 }
260 let d = day_from_number(day);
261 if d.y < 0 || d.y > 9999 {
262 return Err("date out of range".into());
263 }
264 Ok(format_day(d))
265}
266
267fn to_local(env: &Env, ms: f64) -> f64 {
269 ms + env.off(ms) * 60_000.0
270}
271
272fn from_local(env: &Env, wall: f64) -> f64 {
275 let first = env.off(wall);
276 let utc = wall - first * 60_000.0;
277 let second = env.off(utc);
278 if second == first {
279 utc
280 } else {
281 wall - second * 60_000.0
282 }
283}
284
285fn temporal(f: &str, v: &Value) -> R<Temporal> {
286 let Value::String(s) = v else { return Err(format!("{f} needs a date, got {}", type_name(v))) };
287 if s.chars().count() == 10 {
288 if let Some(d) = parse_day(s) {
289 return Ok(Temporal::Date(day_number(d.y, d.m, d.d)));
290 }
291 }
292 parse_timestamp(s).map(Temporal::Ts).ok_or_else(|| format!("not a date: {s}"))
293}
294
295fn floor_day(wall: f64) -> i64 {
296 (wall / DAY_MS).floor() as i64
297}
298
299fn wall_of(env: &Env, t: Temporal) -> f64 {
300 match t {
301 Temporal::Date(day) => day as f64 * DAY_MS,
302 Temporal::Ts(ms) => to_local(env, ms),
303 }
304}
305
306fn unit_of<'a>(f: &str, v: &'a Value, units: &[&str]) -> R<&'a str> {
307 let Value::String(s) = v else { return Err(format!("{f} needs a string, got {}", type_name(v))) };
308 if !units.contains(&s.as_str()) {
309 return Err(format!("unknown unit {s}"));
310 }
311 Ok(s)
312}
313
314fn add_months(day: i64, months: i64) -> i64 {
315 let c = day_from_number(day);
316 let total = c.y * 12 + (c.m - 1) + months;
317 let y = total.div_euclid(12);
318 let m = total - y * 12 + 1;
319 day_number(y, m, c.d.min(days_in_month(y, m)))
320}
321
322fn add_calendar(day: i64, n: i64, unit: &str) -> i64 {
323 match unit {
324 "day" => day + n,
325 "week" => day + 7 * n,
326 "month" => add_months(day, n),
327 "quarter" => add_months(day, n * 3),
328 _ => add_months(day, n * 12),
329 }
330}
331
332fn start_day(day: i64, unit: &str) -> i64 {
333 let c = day_from_number(day);
334 match unit {
335 "day" => day,
336 "week" => {
337 let wd = match weekday_of(c.y, c.m, c.d) {
338 0 => 7,
339 n => n as i64,
340 };
341 day - (wd - 1)
342 }
343 "month" => day_number(c.y, c.m, 1),
344 "quarter" => day_number(c.y, (c.m - 1) / 3 * 3 + 1, 1),
345 _ => day_number(c.y, 1, 1),
346 }
347}
348
349fn number(f: &str, v: &Value) -> R<f64> {
350 match v {
351 Value::Number(n) => Ok(n.as_f64().unwrap_or(0.0)),
352 _ => Err(format!("{f} needs a number, got {}", type_name(v))),
353 }
354}
355
356fn whole_number(f: &str, v: &Value) -> R<f64> {
357 let n = number(f, v)?;
358 if n.fract() != 0.0 {
359 return Err(format!("{f} needs a whole number, got {}", num_text(n)));
360 }
361 Ok(n)
362}
363
364fn text<'a>(f: &str, v: &'a Value) -> R<&'a str> {
365 match v {
366 Value::String(s) => Ok(s),
367 _ => Err(format!("{f} needs a string, got {}", type_name(v))),
368 }
369}
370
371fn date_time(args: &[Value], env: &Env) -> R<Value> {
374 let Temporal::Date(day) = temporal("dateTime", &args[0])? else {
375 return Err("dateTime needs a day".into());
376 };
377 let clock = match args.get(1) {
378 None | Some(Value::Null) => "00:00",
379 Some(v) => text("dateTime", v)?,
380 };
381 let bytes = clock.as_bytes();
382 let digits = |i: usize| bytes[i].is_ascii_digit();
383 if bytes.len() != 5 || bytes[2] != b':' || !digits(0) || !digits(1) || !digits(3) || !digits(4) {
384 return Err(format!("not a time: {clock}"));
385 }
386 let (h, m) = ((bytes[0] - b'0') as i64 * 10 + (bytes[1] - b'0') as i64, (bytes[3] - b'0') as i64 * 10 + (bytes[4] - b'0') as i64);
387 if h > 23 || m > 59 {
388 return Err(format!("not a time: {clock}"));
389 }
390 Ok(Value::String(format_timestamp(from_local(env, day as f64 * DAY_MS + ((h * 60 + m) * 60_000) as f64))?))
391}
392
393fn date_add(args: &[Value], env: &Env) -> R<Value> {
394 let t = temporal("dateAdd", &args[0])?;
395 let n = whole_number("dateAdd", &args[1])?;
396 let unit = unit_of("dateAdd", &args[2], DATE_UNITS)?;
397 let out = match t {
398 Temporal::Date(day) => {
399 if unit_ms(unit).is_some() {
400 return Err(format!("unit {unit} needs a timestamp"));
401 }
402 if !(n.abs() <= MAX_CALENDAR_STEPS) {
403 return Err("date out of range".into());
404 }
405 day_text(add_calendar(day, n as i64, unit))?
406 }
407 Temporal::Ts(ms) => match unit_ms(unit) {
408 Some(u) => format_timestamp(num(ms + n * u)?)?,
409 None => {
410 if !(n.abs() <= MAX_CALENDAR_STEPS) {
411 return Err("date out of range".into());
412 }
413 let wall = to_local(env, ms);
414 let day = floor_day(wall);
415 let tod = wall - day as f64 * DAY_MS;
416 format_timestamp(from_local(env, add_calendar(day, n as i64, unit) as f64 * DAY_MS + tod))?
417 }
418 },
419 };
420 Ok(Value::String(out))
421}
422
423fn date_diff(args: &[Value], env: &Env) -> R<Value> {
424 let a = temporal("dateDiff", &args[0])?;
425 let b = temporal("dateDiff", &args[1])?;
426 let unit = unit_of("dateDiff", &args[2], DATE_UNITS)?;
427 if let Some(u) = unit_ms(unit) {
428 let instant = |t: Temporal| match t {
429 Temporal::Date(day) => from_local(env, day as f64 * DAY_MS),
430 Temporal::Ts(ms) => ms,
431 };
432 return Ok(json::number(num(((instant(b) - instant(a)) / u).trunc())?));
433 }
434 let (wa, wb) = (wall_of(env, a), wall_of(env, b));
435 if unit == "day" || unit == "week" {
436 let span = if unit == "day" { DAY_MS } else { 7.0 * DAY_MS };
437 return Ok(json::number(num(((wb - wa) / span).trunc())?));
438 }
439 let (da, db) = (floor_day(wa), floor_day(wb));
440 let (ca, cb) = (day_from_number(da), day_from_number(db));
441 let mut months = (cb.y - ca.y) * 12 + (cb.m - ca.m);
442 let rest_a = ca.d as f64 * DAY_MS + (wa - da as f64 * DAY_MS);
443 let rest_b = cb.d as f64 * DAY_MS + (wb - db as f64 * DAY_MS);
444 if months > 0 && rest_b < rest_a {
445 months -= 1;
446 } else if months < 0 && rest_b > rest_a {
447 months += 1;
448 }
449 let per = match unit {
450 "month" => 1.0,
451 "quarter" => 3.0,
452 _ => 12.0,
453 };
454 Ok(json::number(num((months as f64 / per).trunc())?))
455}
456
457fn start_end(f: &str, args: &[Value], env: &Env) -> R<Value> {
458 let t = temporal(f, &args[0])?;
459 let unit = unit_of(f, &args[1], CALENDAR_UNITS)?;
460 let day = match t {
461 Temporal::Date(day) => day,
462 Temporal::Ts(ms) => floor_day(to_local(env, ms)),
463 };
464 let is_date = matches!(t, Temporal::Date(_));
465 let out = if f == "startOf" {
466 let start = start_day(day, unit);
467 if is_date {
468 day_text(start)?
469 } else {
470 format_timestamp(from_local(env, start as f64 * DAY_MS))?
471 }
472 } else {
473 let next = add_calendar(start_day(day, unit), 1, unit);
474 if is_date {
475 day_text(next - 1)?
476 } else {
477 format_timestamp(from_local(env, next as f64 * DAY_MS) - 1.0)?
478 }
479 };
480 Ok(Value::String(out))
481}
482
483fn local_day(f: &str, v: &Value, env: &Env) -> R<crate::dates::CalendarDay> {
484 Ok(day_from_number(match temporal(f, v)? {
485 Temporal::Date(day) => day,
486 Temporal::Ts(ms) => floor_day(to_local(env, ms)),
487 }))
488}
489
490fn format_duration(args: &[Value]) -> R<Value> {
491 let h = number("formatDuration", &args[0])?;
492 let format = text("formatDuration", &args[1])?;
493 if !DURATION_FORMATS.contains(&format) {
494 return Err(format!("unknown duration format {format}"));
495 }
496 let total = round_half_away(h.abs() * 60.0);
497 if total > 1e15 {
498 return Err("number out of range".into());
499 }
500 let hours = (total / 60.0).floor() as i64;
501 let minutes = total as i64 - hours * 60;
502 let sign = if h < 0.0 && total > 0.0 { "-" } else { "" };
503 let hours = if format == "hh:mm" { format!("{hours:02}") } else { hours.to_string() };
504 Ok(Value::String(format!("{sign}{hours}:{minutes:02}")))
505}
506
507fn round(args: &[Value]) -> R<Value> {
508 let x = number("round", &args[0])?;
509 let digits = match args.get(1) {
510 None => 0.0,
511 Some(Value::Number(n)) => {
512 let d = n.as_f64().unwrap_or(f64::NAN);
513 if d.fract() != 0.0 || !(0.0..=10.0).contains(&d) {
514 return Err("round needs 0 to 10 digits".into());
515 }
516 d
517 }
518 Some(_) => return Err("round needs 0 to 10 digits".into()),
519 };
520 let mut p = 1.0;
521 for _ in 0..digits as usize {
522 p *= 10.0;
523 }
524 Ok(json::number(num(round_half_away(x * p) / p)?))
525}
526
527fn size(v: &Value) -> R<Value> {
528 Ok(json::number(match v {
529 Value::String(s) => s.chars().count() as f64,
530 Value::Array(a) => a.len() as f64,
531 Value::Object(m) => m.len() as f64,
532 _ => return Err(format!("size needs a list, string or map, got {}", type_name(v))),
533 }))
534}
535
536fn string_of(v: &Value) -> R<Value> {
538 Ok(Value::String(match v {
539 Value::String(s) => s.clone(),
540 Value::Number(n) => num_text(n.as_f64().unwrap_or(0.0)),
541 Value::Bool(b) => b.to_string(),
542 _ => return Err(format!("string needs a number, string or bool, got {}", type_name(v))),
543 }))
544}
545
546fn list_of<'a>(f: &str, v: &'a Value) -> R<&'a Vec<Value>> {
547 v.as_array().ok_or_else(|| format!("{f} needs a list, got {}", type_name(v)))
548}
549
550fn numbers_of(f: &str, v: &Value) -> R<Vec<f64>> {
553 let mut out = Vec::new();
554 for x in list_of(f, v)? {
555 match x {
556 Value::Null => {}
557 Value::Number(n) => out.push(n.as_f64().unwrap_or(0.0)),
558 _ => return Err(format!("{f} needs a list of numbers, got {}", type_name(x))),
559 }
560 }
561 Ok(out)
562}
563
564fn field_of<'a>(row: &'a Value, field: &str) -> &'a Value {
565 row.as_object().and_then(|m| m.get(field)).unwrap_or(&Value::Null)
566}
567
568fn sort_by(args: &[Value]) -> R<Value> {
572 use std::cmp::Ordering;
573 let list = list_of("sortBy", &args[0])?;
574 let field = text("sortBy", &args[1])?;
575 let desc = match args.get(2) {
576 None => false,
577 Some(Value::String(o)) if o == "asc" => false,
578 Some(Value::String(o)) if o == "desc" => true,
579 Some(_) => return Err("sortBy order is asc or desc".into()),
580 };
581 let mut out = list.clone();
582 out.sort_by(|x, y| {
583 let (a, b) = (field_of(x, field), field_of(y, field));
584 if a.is_null() || b.is_null() {
585 return match (a.is_null(), b.is_null()) {
586 (true, true) => Ordering::Equal,
587 (true, false) => Ordering::Greater,
588 _ => Ordering::Less,
589 };
590 }
591 let c = match (a, b) {
592 (Value::Number(p), Value::Number(q)) => p.as_f64().unwrap_or(0.0).partial_cmp(&q.as_f64().unwrap_or(0.0)).unwrap_or(Ordering::Equal),
593 (Value::String(p), Value::String(q)) => utf16_cmp(p, q),
594 _ => Ordering::Equal,
595 };
596 if desc {
597 c.reverse()
598 } else {
599 c
600 }
601 });
602 Ok(Value::Array(out))
603}
604
605fn group_of(args: &[Value]) -> R<Value> {
606 let field = text("groupOf", &args[1])?;
607 let mut out: Vec<(Value, Vec<Value>)> = Vec::new();
608 for row in list_of("groupOf", &args[0])? {
609 let key = field_of(row, field);
610 match out.iter_mut().find(|(k, _)| deep_equal(k, key)) {
611 Some((_, rows)) => rows.push(row.clone()),
612 None => out.push((key.clone(), vec![row.clone()])),
613 }
614 }
615 Ok(Value::Array(out.into_iter().map(|(key, rows)| serde_json::json!({"key": key, "rows": rows})).collect()))
616}
617
618fn trim_text(v: &str) -> &str {
620 v.trim_matches(|c: char| (c.is_whitespace() && c != '\u{85}') || c == '\u{FEFF}')
621}
622
623#[derive(Clone, Copy, PartialEq, Eq)]
624enum Param {
625 Number,
626 Text,
627 Temporal,
628 Sized,
629 List,
630 Any,
631}
632
633struct Def {
634 params: &'static [Param],
635 min: usize,
636 variadic: bool,
637 args: &'static [&'static str],
638}
639
640use Param::{Any as A, List as L, Number as N, Sized as Z, Temporal as T, Text as S};
641
642pub const FUNCTION_NAMES: &[&str] = &["mul", "div", "mod", "round", "floor", "ceil", "abs", "min", "max", "today", "now", "dateTime", "dateAdd", "dateDiff", "startOf", "endOf", "weekday", "isoWeek", "hours", "formatDuration", "size", "orElse", "string", "trim", "lower", "upper", "split", "join", "sumOf", "avgOf", "minOf", "maxOf", "countOf", "sortBy", "groupOf"];
645
646pub const CALL_NAMES: &[&str] = &["mul", "div", "mod", "round", "floor", "ceil", "abs", "min", "max", "today", "now", "dateTime", "dateAdd", "dateDiff", "startOf", "endOf", "weekday", "isoWeek", "hours", "formatDuration", "orElse", "expr"];
649
650pub const CEL_ONLY: &[&str] = &["size", "string", "trim", "lower", "upper", "split", "join", "sumOf", "avgOf", "minOf", "maxOf", "countOf", "sortBy", "groupOf"];
653
654fn def(name: &str) -> Option<Def> {
655 let d = |params: &'static [Param], args: &'static [&'static str]| Def { params, min: params.len(), variadic: false, args };
656 Some(match name {
657 "mul" | "div" | "mod" => d(&[N, N], &["a", "b"]),
658 "round" => Def { params: &[N, N], min: 1, variadic: false, args: &["value", "digits"] },
659 "floor" | "ceil" | "abs" => d(&[N], &["value"]),
660 "min" | "max" => Def { params: &[N], min: 1, variadic: true, args: &["values"] },
661 "today" | "now" => d(&[], &[]),
662 "dateTime" => Def { params: &[T, A], min: 1, variadic: false, args: &["date", "time"] },
663 "dateAdd" => d(&[T, N, S], &["date", "n", "unit"]),
664 "dateDiff" => d(&[T, T, S], &["a", "b", "unit"]),
665 "startOf" | "endOf" => d(&[T, S], &["date", "unit"]),
666 "weekday" | "isoWeek" => d(&[T], &["date"]),
667 "hours" => d(&[N], &["minutes"]),
668 "formatDuration" => d(&[N, S], &["hours", "format"]),
669 "size" => d(&[Z], &["value"]),
670 "string" => d(&[A], &["value"]),
672 "trim" | "lower" | "upper" => d(&[S], &["value"]),
673 "split" => d(&[S, S], &["value", "separator"]),
674 "join" => Def { params: &[L, S], min: 1, variadic: false, args: &["values", "separator"] },
675 "sumOf" | "avgOf" | "minOf" | "maxOf" | "countOf" => d(&[L], &["values"]),
676 "sortBy" => Def { params: &[L, S, S], min: 2, variadic: false, args: &["values", "field", "order"] },
677 "groupOf" => d(&[L, S], &["values", "field"]),
678 "orElse" => d(&[A, A], &["value", "default"]),
680 _ => return None,
681 })
682}
683
684fn arity_error(name: &str, def: &Def, n: usize) -> Option<String> {
685 if def.variadic {
686 return (n < 1).then(|| format!("{name} takes at least 1 argument"));
687 }
688 let (min, max) = (def.min, def.params.len());
689 if n >= min && n <= max {
690 return None;
691 }
692 Some(if min == max { format!("{name} takes {min} argument{}", if min == 1 { "" } else { "s" }) } else { format!("{name} takes {min} or {max} arguments") })
693}
694
695fn run(name: &str, args: &[Value], env: &Env) -> R<Value> {
696 let n = |f: fn(f64) -> f64| -> R<Value> { Ok(json::number(num(f(number(name, &args[0])?))?)) };
697 match name {
698 "mul" => Ok(json::number(num(number("mul", &args[0])? * number("mul", &args[1])?)?)),
699 "div" => {
700 let (x, y) = (number("div", &args[0])?, number("div", &args[1])?);
701 if y == 0.0 {
702 return Err("division by zero".into());
703 }
704 Ok(json::number(num(x / y)?))
705 }
706 "mod" => {
707 let (x, y) = (number("mod", &args[0])?, number("mod", &args[1])?);
708 if y == 0.0 {
709 return Err("modulo by zero".into());
710 }
711 Ok(json::number(num(x % y)?))
712 }
713 "round" => round(args),
714 "floor" => n(f64::floor),
715 "ceil" => n(f64::ceil),
716 "abs" => n(f64::abs),
717 "min" | "max" => {
718 let mut out = if name == "min" { f64::INFINITY } else { f64::NEG_INFINITY };
719 for v in args {
720 let x = number(name, v)?;
721 out = if name == "min" { out.min(x) } else { out.max(x) };
722 }
723 Ok(json::number(num(out)?))
724 }
725 "today" => Ok(Value::String(day_text(floor_day(to_local(env, env.now)))?)),
726 "now" => Ok(Value::String(format_timestamp(env.now)?)),
727 "dateTime" => date_time(args, env),
728 "dateAdd" => date_add(args, env),
729 "dateDiff" => date_diff(args, env),
730 "startOf" | "endOf" => start_end(name, args, env),
731 "weekday" => {
732 let d = local_day("weekday", &args[0], env)?;
733 Ok(json::number(match weekday_of(d.y, d.m, d.d) {
734 0 => 7.0,
735 w => w as f64,
736 }))
737 }
738 "isoWeek" => {
739 let d = local_day("isoWeek", &args[0], env)?;
740 Ok(json::number(iso_week(d.y, d.m, d.d).1 as f64))
741 }
742 "hours" => Ok(json::number(num(number("hours", &args[0])? / 60.0)?)),
743 "formatDuration" => format_duration(args),
744 "size" => size(&args[0]),
745 "string" => string_of(&args[0]),
746 "trim" => Ok(Value::String(trim_text(text("trim", &args[0])?).to_string())),
747 "lower" => Ok(Value::String(text("lower", &args[0])?.to_lowercase())),
748 "upper" => Ok(Value::String(text("upper", &args[0])?.to_uppercase())),
749 "split" => {
750 let value = text("split", &args[0])?;
751 let separator = text("split", &args[1])?;
752 if separator.is_empty() {
753 return Err("split needs a separator".into());
754 }
755 Ok(Value::Array(value.split(separator).map(|s| Value::String(s.to_string())).collect()))
756 }
757 "join" => {
758 let separator = match args.get(1) {
759 Some(v) => text("join", v)?,
760 None => "",
761 };
762 let mut parts: Vec<&str> = Vec::new();
763 for x in list_of("join", &args[0])? {
764 match x {
765 Value::String(s) => parts.push(s),
766 _ => return Err(format!("join needs a list of strings, got {}", type_name(x))),
767 }
768 }
769 Ok(Value::String(parts.join(separator)))
770 }
771 "sumOf" => Ok(json::number(num(numbers_of("sumOf", &args[0])?.iter().sum::<f64>())?)),
772 "avgOf" => {
773 let ns = numbers_of("avgOf", &args[0])?;
774 if ns.is_empty() {
775 return Ok(Value::Null);
776 }
777 Ok(json::number(num(ns.iter().sum::<f64>() / ns.len() as f64)?))
778 }
779 "minOf" | "maxOf" => {
780 let ns = numbers_of(name, &args[0])?;
781 let picked = ns.into_iter().reduce(|a, b| if (name == "minOf" && b < a) || (name == "maxOf" && b > a) { b } else { a });
782 Ok(picked.map_or(Value::Null, json::number))
783 }
784 "countOf" => Ok(json::number(list_of("countOf", &args[0])?.iter().filter(|x| !x.is_null()).count() as f64)),
785 "sortBy" => sort_by(args),
786 "groupOf" => group_of(args),
787 "orElse" => Ok(if args[0].is_null() { args.get(1).cloned().unwrap_or(Value::Null) } else { args[0].clone() }),
788 _ => Err(format!("unknown function {name}")),
789 }
790}
791
792pub fn call_expression_function(name: &str, args: &Map<String, Value>, env: &ExpressionEnv) -> Option<Option<Value>> {
797 let fallback = || Some(args.get("default").cloned());
798 if name == "expr" {
799 let Some(Value::String(expr)) = args.get("expr") else { return fallback() };
800 let empty = Map::new();
801 let vars = args.get("vars").and_then(Value::as_object).unwrap_or(&empty);
802 return evaluate_expression(expr, vars, env).map_or_else(|_| fallback(), |v| Some(Some(v)));
803 }
804 if CEL_ONLY.contains(&name) {
805 return None;
806 }
807 let def = def(name)?;
808 let positional: Vec<Value> = if def.variadic {
809 args.get("values").and_then(Value::as_array).cloned().unwrap_or_default()
810 } else {
811 let mut list: Vec<Option<&Value>> = def.args.iter().map(|k| args.get(*k)).collect();
812 while list.len() > def.min && list.last().is_some_and(Option::is_none) {
813 list.pop();
814 }
815 list.into_iter().map(|v| v.cloned().unwrap_or(Value::Null)).collect()
816 };
817 if arity_error(name, &def, positional.len()).is_some() {
818 return fallback();
819 }
820 run(name, &positional, &Env::of(env)).map_or_else(|_| fallback(), |v| Some(Some(v)))
821}
822
823#[derive(Debug, Clone, PartialEq)]
829pub enum Node {
830 Lit(Value, usize),
831 Id(String, usize),
832 List(Vec<Node>, usize),
833 Map(Vec<(String, Node)>, usize),
835 Field(Box<Node>, String, usize),
836 Index(Box<Node>, Box<Node>, usize),
837 Unary(char, Box<Node>, usize),
838 Bin(&'static str, Box<Node>, Box<Node>, usize),
839 Cond(Box<Node>, Box<Node>, Box<Node>, usize),
840 Call(String, Vec<Node>, usize),
841 Macro(String, Box<Node>, String, Box<Node>, usize),
842 Has(Box<Node>, usize),
844}
845
846#[derive(Debug, Clone, PartialEq)]
847enum Tok {
848 Num(f64),
849 Str(String),
850 Id(String),
851 Op(&'static str),
852 End,
853}
854
855#[derive(Debug, Clone)]
856struct Token {
857 t: Tok,
858 at: usize,
859 text: String,
860}
861
862const OPS2: &[&str] = &["==", "!=", "<=", ">=", "&&", "||"];
863const OPS1: &[&str] = &["+", "-", "*", "/", "%", "<", ">", "!", "(", ")", "[", "]", "{", "}", ",", ".", "?", ":"];
864
865fn is_digit(c: Option<&char>) -> bool {
866 c.is_some_and(char::is_ascii_digit)
867}
868fn is_id_start(c: Option<&char>) -> bool {
869 c.is_some_and(|c| c.is_ascii_alphabetic() || *c == '_')
870}
871fn is_id_part(c: Option<&char>) -> bool {
872 is_id_start(c) || is_digit(c)
873}
874
875fn tokenize(cs: &[char]) -> R<Vec<Token>> {
876 let mut out = Vec::new();
877 let mut i = 0;
878 while i < cs.len() {
879 let c = cs[i];
880 if matches!(c, ' ' | '\t' | '\n' | '\r') {
881 i += 1;
882 continue;
883 }
884 let at = i;
885 if c.is_ascii_digit() {
886 while is_digit(cs.get(i)) {
887 i += 1;
888 }
889 if cs.get(i) == Some(&'.') && is_digit(cs.get(i + 1)) {
890 i += 1;
891 while is_digit(cs.get(i)) {
892 i += 1;
893 }
894 }
895 if matches!(cs.get(i), Some('e' | 'E')) && (is_digit(cs.get(i + 1)) || (matches!(cs.get(i + 1), Some('+' | '-')) && is_digit(cs.get(i + 2)))) {
896 i += 2;
897 while is_digit(cs.get(i)) {
898 i += 1;
899 }
900 }
901 let text: String = cs[at..i].iter().collect();
902 let v = text.parse::<f64>().unwrap_or(f64::INFINITY);
903 out.push(Token { t: Tok::Num(v), at, text });
904 continue;
905 }
906 if c == '\'' || c == '"' {
907 i += 1;
908 let mut s = String::new();
909 loop {
910 let d = cs.get(i).copied();
911 match d {
912 None | Some('\n') => return Err(format!("at {at}: unterminated string")),
913 Some(d) if d == c => break,
914 Some('\\') => {
915 let e = cs.get(i + 1).copied();
916 let mapped = match e {
917 Some('\\') => '\\',
918 Some('\'') => '\'',
919 Some('"') => '"',
920 Some('n') => '\n',
921 Some('t') => '\t',
922 Some('r') => '\r',
923 _ => return Err(format!("at {i}: unknown escape \\{}", e.map(String::from).unwrap_or_default())),
924 };
925 s.push(mapped);
926 i += 2;
927 }
928 Some(d) => {
929 s.push(d);
930 i += 1;
931 }
932 }
933 }
934 i += 1;
935 out.push(Token { t: Tok::Str(s), at, text: cs[at..i].iter().collect() });
936 continue;
937 }
938 if is_id_start(Some(&c)) {
939 while is_id_part(cs.get(i)) {
940 i += 1;
941 }
942 let text: String = cs[at..i].iter().collect();
943 out.push(Token { t: Tok::Id(text.clone()), at, text });
944 continue;
945 }
946 let two: String = cs[i..(i + 2).min(cs.len())].iter().collect();
947 if let Some(op) = OPS2.iter().find(|o| **o == two) {
948 out.push(Token { t: Tok::Op(op), at, text: two });
949 i += 2;
950 continue;
951 }
952 if let Some(op) = OPS1.iter().find(|o| o.starts_with(c)) {
953 out.push(Token { t: Tok::Op(op), at, text: c.to_string() });
954 i += 1;
955 continue;
956 }
957 return Err(format!("at {at}: unexpected {c}"));
958 }
959 out.push(Token { t: Tok::End, at: cs.len(), text: "end".into() });
960 Ok(out)
961}
962
963const MACROS: &[&str] = &["map", "filter", "all", "exists"];
964
965struct Parser {
966 tokens: Vec<Token>,
967 i: usize,
968 depth: usize,
969 in_body: usize,
970}
971
972impl Parser {
973 fn peek(&self) -> &Token {
974 &self.tokens[self.i]
975 }
976 fn is_op(&self, v: &str) -> bool {
977 matches!(&self.peek().t, Tok::Op(o) if *o == v)
978 }
979 fn next(&mut self) -> Token {
980 let t = self.tokens[self.i].clone();
981 self.i += 1;
982 t
983 }
984 fn unexpected<T>(&self) -> R<T> {
985 let t = self.peek();
986 Err(format!("at {}: unexpected {}", t.at, t.text))
987 }
988 fn expect(&mut self, v: &str) -> R<Token> {
989 if !self.is_op(v) {
990 return self.unexpected();
991 }
992 Ok(self.next())
993 }
994 fn enter(&mut self, at: usize) -> R<()> {
995 self.depth += 1;
996 if self.depth > MAX_DEPTH {
997 return Err(format!("at {at}: expression is nested too deeply"));
998 }
999 Ok(())
1000 }
1001
1002 fn parse_all(&mut self) -> R<Node> {
1003 let node = self.expr()?;
1004 if self.peek().t != Tok::End {
1005 return self.unexpected();
1006 }
1007 Ok(node)
1008 }
1009
1010 fn expr(&mut self) -> R<Node> {
1011 crate::deep(|| self.expr_inner())
1012 }
1013
1014 fn expr_inner(&mut self) -> R<Node> {
1015 let at = self.peek().at;
1016 self.enter(at)?;
1017 let test = self.or()?;
1018 let mut out = test;
1019 if self.is_op("?") {
1020 self.next();
1021 let then = self.expr()?;
1022 self.expect(":")?;
1023 let otherwise = self.expr()?;
1024 out = Node::Cond(Box::new(out), Box::new(then), Box::new(otherwise), at);
1025 }
1026 self.depth -= 1;
1027 Ok(out)
1028 }
1029
1030 fn binary(&mut self, ops: &[&'static str], operand: fn(&mut Parser) -> R<Node>) -> R<Node> {
1031 let mut a = operand(self)?;
1032 loop {
1033 let t = self.peek().clone();
1034 let Tok::Op(op) = t.t else { return Ok(a) };
1035 let Some(op) = ops.iter().find(|o| **o == op) else { return Ok(a) };
1036 self.next();
1037 let b = operand(self)?;
1038 a = Node::Bin(op, Box::new(a), Box::new(b), t.at);
1039 }
1040 }
1041 fn or(&mut self) -> R<Node> {
1042 self.binary(&["||"], Parser::and)
1043 }
1044 fn and(&mut self) -> R<Node> {
1045 self.binary(&["&&"], Parser::rel)
1046 }
1047 fn rel(&mut self) -> R<Node> {
1048 self.binary(&["==", "!=", "<", "<=", ">", ">="], Parser::add)
1049 }
1050 fn add(&mut self) -> R<Node> {
1051 self.binary(&["+", "-"], Parser::mul)
1052 }
1053 fn mul(&mut self) -> R<Node> {
1054 self.binary(&["*", "/", "%"], Parser::unary)
1055 }
1056
1057 fn unary(&mut self) -> R<Node> {
1058 let t = self.peek().clone();
1059 if let Tok::Op(op @ ("!" | "-")) = t.t {
1060 self.next();
1061 self.enter(t.at)?;
1062 let arg = crate::deep(|| self.unary())?;
1063 self.depth -= 1;
1064 return Ok(Node::Unary(op.chars().next().unwrap_or('!'), Box::new(arg), t.at));
1065 }
1066 self.postfix()
1067 }
1068
1069 fn postfix(&mut self) -> R<Node> {
1070 let mut node = self.primary()?;
1071 loop {
1072 if self.is_op(".") {
1073 self.next();
1074 let name = self.peek().clone();
1075 let Tok::Id(field) = name.t.clone() else { return self.unexpected() };
1076 self.next();
1077 if self.is_op("(") {
1078 if !MACROS.contains(&field.as_str()) {
1079 if let Some(dotted) = dotted_name(&node) {
1080 let full = format!("{dotted}.{field}");
1081 let effect = function_effect(&full);
1082 if effect != FunctionEffect::Pure {
1083 return Err(format!("at {}: {}: an expression is pure, use an action step", root_at(&node), effect_words(&full, effect)));
1084 }
1085 }
1086 return Err(format!("at {}: unknown method {field}", name.at));
1087 }
1088 if self.in_body > 0 {
1089 return Err(format!("at {}: nested comprehensions are not allowed", name.at));
1090 }
1091 self.next();
1092 let v = self.peek().clone();
1093 let comma = matches!(self.tokens.get(self.i + 1).map(|t| &t.t), Some(Tok::Op(",")));
1094 let Tok::Id(var) = v.t.clone() else { return Err(format!("at {}: {field} needs a name first", v.at)) };
1095 if !comma {
1096 return Err(format!("at {}: {field} needs a name first", v.at));
1097 }
1098 self.next();
1099 self.next();
1100 self.in_body += 1;
1101 let body = self.expr()?;
1102 self.in_body -= 1;
1103 self.expect(")")?;
1104 node = Node::Macro(field, Box::new(node), var, Box::new(body), name.at);
1105 } else {
1106 node = Node::Field(Box::new(node), field, name.at);
1107 }
1108 continue;
1109 }
1110 if self.is_op("[") {
1111 let at = self.next().at;
1112 let index = self.expr()?;
1113 self.expect("]")?;
1114 node = Node::Index(Box::new(node), Box::new(index), at);
1115 continue;
1116 }
1117 return Ok(node);
1118 }
1119 }
1120
1121 fn list(&mut self, close: &str) -> R<Vec<Node>> {
1122 let mut items = Vec::new();
1123 if self.is_op(close) {
1124 self.next();
1125 return Ok(items);
1126 }
1127 loop {
1128 items.push(self.expr()?);
1129 if self.is_op(",") {
1130 self.next();
1131 if self.is_op(close) && close == "]" {
1132 self.next();
1133 return Ok(items);
1134 }
1135 continue;
1136 }
1137 self.expect(close)?;
1138 return Ok(items);
1139 }
1140 }
1141
1142 fn map(&mut self) -> R<Vec<(String, Node)>> {
1145 let mut entries: Vec<(String, Node)> = Vec::new();
1146 while !self.is_op("}") {
1147 let key = self.peek().clone();
1148 let Tok::Str(name) = key.t else {
1149 return Err(format!("at {}: a map key is a string", key.at));
1150 };
1151 if name == "?" {
1152 return Err(format!("at {}: ? is not a map key", key.at));
1153 }
1154 if entries.iter().any(|(k, _)| *k == name) {
1155 return Err(format!("at {}: duplicate key {name}", key.at));
1156 }
1157 self.next();
1158 self.expect(":")?;
1159 let value = self.expr()?;
1160 entries.push((name, value));
1161 if !self.is_op(",") {
1162 break;
1163 }
1164 self.next();
1165 }
1166 self.expect("}")?;
1167 Ok(entries)
1168 }
1169
1170 fn primary(&mut self) -> R<Node> {
1171 let t = self.peek().clone();
1172 match t.t {
1173 Tok::Num(v) => {
1174 self.next();
1175 if !v.is_finite() {
1176 return Err(format!("at {}: number out of range", t.at));
1177 }
1178 Ok(Node::Lit(json::number(v), t.at))
1179 }
1180 Tok::Str(s) => {
1181 self.next();
1182 Ok(Node::Lit(Value::String(s), t.at))
1183 }
1184 Tok::Id(name) => {
1185 self.next();
1186 match name.as_str() {
1187 "true" | "false" => return Ok(Node::Lit(Value::Bool(name == "true"), t.at)),
1188 "null" => return Ok(Node::Lit(Value::Null, t.at)),
1189 _ => {}
1190 }
1191 if name == "has" && self.is_op("(") {
1193 self.next();
1194 let arg = self.expr()?;
1195 self.expect(")")?;
1196 if !matches!(arg, Node::Field(..)) {
1197 let hint = match &arg {
1198 Node::Id(name, _) => format!("; for a name write {name} != null"),
1199 _ => String::new(),
1200 };
1201 return Err(format!("at {}: has needs a field: has(a.b){hint}", t.at));
1202 }
1203 return Ok(Node::Has(Box::new(arg), t.at));
1204 }
1205 if self.is_op("(") {
1206 if def(&name).is_none() {
1207 let effect = function_effect(&name);
1208 if effect != FunctionEffect::Pure {
1209 return Err(format!("at {}: {}: an expression is pure, use an action step", t.at, effect_words(&name, effect)));
1210 }
1211 return Err(format!("at {}: unknown function {name}", t.at));
1212 }
1213 self.next();
1214 let args = self.list(")")?;
1215 return Ok(Node::Call(name, args, t.at));
1216 }
1217 Ok(Node::Id(name, t.at))
1218 }
1219 Tok::Op("(") => {
1220 self.next();
1221 let inner = self.expr()?;
1222 self.expect(")")?;
1223 Ok(inner)
1224 }
1225 Tok::Op("[") => {
1226 self.next();
1227 Ok(Node::List(self.list("]")?, t.at))
1228 }
1229 Tok::Op("{") => {
1230 self.next();
1231 Ok(Node::Map(self.map()?, t.at))
1232 }
1233 _ => self.unexpected(),
1234 }
1235 }
1236}
1237
1238fn dotted_name(node: &Node) -> Option<String> {
1240 match node {
1241 Node::Id(name, _) => Some(name.clone()),
1242 Node::Field(of, name, _) => dotted_name(of).map(|of| format!("{of}.{name}")),
1243 _ => None,
1244 }
1245}
1246
1247pub fn expression_names(node: &Node) -> Vec<String> {
1250 fn walk(n: &Node, bound: &mut Vec<String>, out: &mut std::collections::BTreeSet<String>) {
1251 match n {
1252 Node::Lit(..) => {}
1253 Node::Id(name, _) => {
1254 if !bound.iter().any(|b| b == name) {
1255 out.insert(name.clone());
1256 }
1257 }
1258 Node::List(items, _) | Node::Call(_, items, _) => items.iter().for_each(|x| walk(x, bound, out)),
1259 Node::Map(entries, _) => entries.iter().for_each(|(_, x)| walk(x, bound, out)),
1260 Node::Field(of, _, _) | Node::Has(of, _) | Node::Unary(_, of, _) => walk(of, bound, out),
1261 Node::Index(of, index, _) => {
1262 walk(of, bound, out);
1263 walk(index, bound, out);
1264 }
1265 Node::Bin(_, a, b, _) => {
1266 walk(a, bound, out);
1267 walk(b, bound, out);
1268 }
1269 Node::Cond(test, then, other, _) => {
1270 walk(test, bound, out);
1271 walk(then, bound, out);
1272 walk(other, bound, out);
1273 }
1274 Node::Macro(_, of, v, body, _) => {
1275 walk(of, bound, out);
1276 bound.push(v.clone());
1277 walk(body, bound, out);
1278 bound.pop();
1279 }
1280 }
1281 }
1282 let mut out = std::collections::BTreeSet::new();
1283 walk(node, &mut Vec::new(), &mut out);
1284 out.into_iter().collect()
1285}
1286
1287pub fn path_text(node: &Node) -> Option<String> {
1290 match node {
1291 Node::Id(name, _) => Some(name.clone()),
1292 Node::Field(of, name, _) => path_text(of).map(|of| format!("{of}.{name}")),
1293 Node::Index(of, index, _) => {
1294 let of = path_text(of)?;
1295 match literal(index) {
1296 Some(Value::Number(n)) => Some(format!("{of}[{}]", num_text(n.as_f64().unwrap_or(0.0)))),
1297 Some(Value::String(k)) => Some(format!("{of}['{k}']")),
1298 _ => None,
1299 }
1300 }
1301 _ => None,
1302 }
1303}
1304
1305fn node_at(node: &Node) -> usize {
1307 match node {
1308 Node::Lit(_, at) | Node::Id(_, at) | Node::List(_, at) | Node::Map(_, at) | Node::Field(_, _, at) | Node::Index(_, _, at) | Node::Unary(_, _, at) | Node::Bin(_, _, _, at) | Node::Cond(_, _, _, at) | Node::Call(_, _, at) | Node::Macro(_, _, _, _, at) | Node::Has(_, at) => *at,
1309 }
1310}
1311
1312fn root_at(node: &Node) -> usize {
1313 match node {
1314 Node::Field(of, _, _) => root_at(of),
1315 _ => node_at(node),
1316 }
1317}
1318
1319pub fn parse_expression(source: &str) -> Result<Node, String> {
1322 let cs: Vec<char> = source.chars().collect();
1323 if cs.len() > MAX_LENGTH {
1324 return Err(format!("expression is longer than {MAX_LENGTH} characters"));
1325 }
1326 Parser { tokens: tokenize(&cs)?, i: 0, depth: 0, in_body: 0 }.parse_all()
1327}
1328
1329struct Scope<'a> {
1334 name: &'a str,
1335 value: &'a Value,
1336 parent: Option<&'a Scope<'a>>,
1337}
1338
1339struct Evaluator<'v> {
1340 vars: &'v Map<String, Value>,
1341 env: Env<'v>,
1342 steps: usize,
1343 max_steps: usize,
1344}
1345
1346impl Evaluator<'_> {
1347 fn step(&mut self) -> R<()> {
1348 self.steps += 1;
1349 if self.steps > self.max_steps {
1350 return Err("step limit exceeded".into());
1351 }
1352 Ok(())
1353 }
1354
1355 fn ev(&mut self, node: &Node, scope: Option<&Scope>) -> R<Value> {
1356 crate::deep(|| self.ev_inner(node, scope))
1357 }
1358
1359 fn ev_inner(&mut self, node: &Node, scope: Option<&Scope>) -> R<Value> {
1360 self.step()?;
1361 match node {
1362 Node::Lit(v, _) => Ok(v.clone()),
1363 Node::List(items, _) => items.iter().map(|i| self.ev(i, scope)).collect::<R<Vec<_>>>().map(Value::Array),
1364 Node::Map(entries, _) => {
1365 let mut out = Map::new();
1366 for (k, v) in entries {
1367 out.insert(k.clone(), self.ev(v, scope)?);
1368 }
1369 Ok(Value::Object(out))
1370 }
1371 Node::Id(name, _) => {
1372 let mut s = scope;
1373 while let Some(sc) = s {
1374 if sc.name == name {
1375 return Ok(sc.value.clone());
1376 }
1377 s = sc.parent;
1378 }
1379 self.vars.get(name).cloned().ok_or_else(|| format!("unknown name {name}"))
1380 }
1381 Node::Field(of, name, _) => match self.ev(of, scope)? {
1382 Value::Null => Ok(Value::Null),
1383 Value::Object(m) => Ok(m.get(name).cloned().unwrap_or(Value::Null)),
1384 other => Err(format!("no field {name} on {}", type_name(&other))),
1385 },
1386 Node::Has(field, _) => {
1387 let Node::Field(receiver, name, _) = field.as_ref() else { return Err("has needs a field: has(a.b)".into()) };
1388 match self.ev(receiver, scope)? {
1389 Value::Null => Ok(Value::Bool(false)),
1390 Value::Object(m) => Ok(Value::Bool(m.get(name).is_some_and(|v| !v.is_null()))),
1391 other => Err(format!("no field {name} on {}", type_name(&other))),
1392 }
1393 }
1394 Node::Index(of, index, _) => {
1395 let of = self.ev(of, scope)?;
1396 let index = self.ev(index, scope)?;
1397 match of {
1398 Value::Array(list) => {
1399 let Value::Number(n) = &index else { return Err(format!("a list index is a number, got {}", type_name(&index))) };
1400 let n = n.as_f64().unwrap_or(0.0);
1401 if n.fract() != 0.0 {
1402 return Err(format!("a list index is a whole number, got {}", num_text(n)));
1403 }
1404 if n < 0.0 || n >= list.len() as f64 {
1406 return Ok(Value::Null);
1407 }
1408 Ok(list[n as usize].clone())
1409 }
1410 Value::Object(m) => {
1411 let Value::String(k) = &index else { return Err(format!("a map key is a string, got {}", type_name(&index))) };
1412 Ok(m.get(k).cloned().unwrap_or(Value::Null))
1413 }
1414 Value::Null => Ok(Value::Null),
1415 other => Err(format!("cannot index {}", type_name(&other))),
1416 }
1417 }
1418 Node::Unary(op, arg, _) => {
1419 let v = self.ev(arg, scope)?;
1420 if *op == '-' {
1421 let Value::Number(n) = &v else { return Err(format!("cannot apply - to {}", type_name(&v))) };
1422 return Ok(json::number(num(-n.as_f64().unwrap_or(0.0))?));
1423 }
1424 let Value::Bool(b) = v else { return Err(format!("cannot apply ! to {}", type_name(&v))) };
1425 Ok(Value::Bool(!b))
1426 }
1427 Node::Cond(test, then, otherwise, _) => {
1428 let t = self.ev(test, scope)?;
1429 let Value::Bool(t) = t else { return Err(format!("cannot use {} as a condition", type_name(&t))) };
1430 self.ev(if t { then } else { otherwise }, scope)
1431 }
1432 Node::Bin(op, a, b, _) => self.binary(op, a, b, scope),
1433 Node::Call(name, args, _) => {
1434 let args = args.iter().map(|a| self.ev(a, scope)).collect::<R<Vec<_>>>()?;
1435 let d = def(name).ok_or_else(|| format!("unknown function {name}"))?;
1436 if let Some(e) = arity_error(name, &d, args.len()) {
1437 return Err(e);
1438 }
1439 run(name, &args, &self.env)
1440 }
1441 Node::Macro(f, of, var, body, _) => {
1442 let of = self.ev(of, scope)?;
1443 let Value::Array(list) = of else { return Err(format!("{f} needs a list, got {}", type_name(&of))) };
1444 let mut out = Vec::new();
1445 for item in &list {
1446 self.step()?;
1447 let inner = Scope { name: var, value: item, parent: scope };
1448 let v = self.ev(body, Some(&inner))?;
1449 if f == "map" {
1450 out.push(v);
1451 continue;
1452 }
1453 let Value::Bool(b) = v else { return Err(format!("{f} needs a bool, got {}", type_name(&v))) };
1454 match f.as_str() {
1455 "filter" if b => out.push(item.clone()),
1456 "all" if !b => return Ok(Value::Bool(false)),
1457 "exists" if b => return Ok(Value::Bool(true)),
1458 _ => {}
1459 }
1460 }
1461 Ok(match f.as_str() {
1462 "all" => Value::Bool(true),
1463 "exists" => Value::Bool(false),
1464 _ => Value::Array(out),
1465 })
1466 }
1467 }
1468 }
1469
1470 fn binary(&mut self, op: &str, a: &Node, b: &Node, scope: Option<&Scope>) -> R<Value> {
1471 if op == "&&" || op == "||" {
1472 let x = self.ev(a, scope)?;
1473 let Value::Bool(x) = x else { return Err(format!("cannot apply {op} to {}", type_name(&x))) };
1474 if (op == "&&" && !x) || (op == "||" && x) {
1475 return Ok(Value::Bool(x));
1476 }
1477 let y = self.ev(b, scope)?;
1478 let Value::Bool(_) = y else { return Err(format!("cannot apply {op} to {}", type_name(&y))) };
1479 return Ok(y);
1480 }
1481 let x = self.ev(a, scope)?;
1482 let y = self.ev(b, scope)?;
1483 match op {
1484 "==" => Ok(Value::Bool(deep_equal(&x, &y))),
1485 "!=" => Ok(Value::Bool(!deep_equal(&x, &y))),
1486 "<" | "<=" | ">" | ">=" => {
1487 let c = match (&x, &y) {
1488 (Value::Number(p), Value::Number(q)) => p.as_f64().partial_cmp(&q.as_f64()).unwrap_or(std::cmp::Ordering::Equal),
1489 (Value::String(p), Value::String(q)) => utf16_cmp(p, q),
1490 _ => return Err(format!("cannot compare {} and {}", type_name(&x), type_name(&y))),
1491 };
1492 use std::cmp::Ordering::*;
1493 Ok(Value::Bool(match op {
1494 "<" => c == Less,
1495 "<=" => c != Greater,
1496 ">" => c == Greater,
1497 _ => c != Less,
1498 }))
1499 }
1500 "+" => match (x, y) {
1501 (Value::Number(p), Value::Number(q)) => Ok(json::number(num(p.as_f64().unwrap_or(0.0) + q.as_f64().unwrap_or(0.0))?)),
1502 (Value::String(p), Value::String(q)) => Ok(Value::String(p + &q)),
1503 (Value::Array(mut p), Value::Array(q)) => {
1504 p.extend(q);
1505 Ok(Value::Array(p))
1506 }
1507 (x, y) => Err(format!("cannot apply + to {} and {}", type_name(&x), type_name(&y))),
1508 },
1509 _ => {
1510 let (Value::Number(p), Value::Number(q)) = (&x, &y) else { return Err(format!("cannot apply {op} to {} and {}", type_name(&x), type_name(&y))) };
1511 let (p, q) = (p.as_f64().unwrap_or(0.0), q.as_f64().unwrap_or(0.0));
1512 match op {
1513 "-" => Ok(json::number(num(p - q)?)),
1514 "*" => Ok(json::number(num(p * q)?)),
1515 _ => {
1516 if q == 0.0 {
1517 return Err(if op == "/" { "division by zero" } else { "modulo by zero" }.into());
1518 }
1519 Ok(json::number(num(if op == "/" { p / q } else { p % q })?))
1520 }
1521 }
1522 }
1523 }
1524 }
1525}
1526
1527pub fn evaluate_expression(source: &str, vars: &Map<String, Value>, env: &ExpressionEnv) -> Result<Value, String> {
1530 let ast = parse_expression(source)?;
1531 evaluate_node(&ast, vars, env)
1532}
1533
1534pub fn evaluate_node<'a>(ast: &Node, vars: &'a Map<String, Value>, env: &ExpressionEnv<'a>) -> Result<Value, String> {
1536 let mut e = Evaluator { vars, env: Env::of(env), steps: 0, max_steps: env.max_steps.unwrap_or(MAX_STEPS) };
1537 e.ev(ast, None)
1538}
1539
1540pub fn evaluate_counted<'a>(ast: &Node, vars: &'a Map<String, Value>, env: &ExpressionEnv<'a>) -> (Result<Value, String>, usize) {
1543 let mut e = Evaluator { vars, env: Env::of(env), steps: 0, max_steps: env.max_steps.unwrap_or(MAX_STEPS) };
1544 let result = e.ev(ast, None);
1545 (result, e.steps)
1546}
1547
1548pub fn is_optional(t: &Value) -> bool {
1555 matches!(t, Value::Object(m) if m.len() == 1 && m.contains_key("?"))
1556}
1557
1558pub fn optional_type(t: Value) -> Value {
1560 if is_optional(&t) || is(&t, "null") {
1561 return t;
1562 }
1563 let mut m = Map::new();
1564 m.insert("?".into(), t);
1565 Value::Object(m)
1566}
1567
1568pub fn required_type(t: &Value) -> Value {
1570 match t {
1571 Value::Object(m) if is_optional(t) => m["?"].clone(),
1572 _ => t.clone(),
1573 }
1574}
1575
1576pub fn type_display(t: &Value) -> String {
1579 match t {
1580 Value::Object(m) if is_optional(t) => format!("{}?", type_display(&m["?"])),
1581 Value::String(s) => s.clone(),
1582 Value::Array(_) => "list".into(),
1583 _ => "map".into(),
1584 }
1585}
1586
1587fn is_row(t: &Value) -> bool {
1588 t.is_object() && !is_optional(t)
1589}
1590fn is_textual(t: &Value) -> bool {
1591 matches!(t.as_str(), Some("string" | "date" | "timestamp"))
1592}
1593fn loose(t: &Value) -> bool {
1594 matches!(t.as_str(), Some("dyn" | "null"))
1595}
1596fn is(t: &Value, name: &str) -> bool {
1597 t.as_str() == Some(name)
1598}
1599fn ty(name: &str) -> Value {
1600 Value::String(name.into())
1601}
1602
1603fn accepts(param: Param, t: &Value) -> bool {
1604 if param == Param::Any || loose(t) {
1605 return true;
1606 }
1607 match param {
1608 Param::Number => is(t, "number"),
1609 Param::Text | Param::Temporal => is_textual(t),
1610 Param::Sized => is(t, "string") || is(t, "map") || t.is_array() || is_row(t),
1611 Param::List => t.is_array(),
1612 Param::Any => true,
1613 }
1614}
1615
1616fn param_name(p: Param) -> &'static str {
1617 match p {
1618 Param::Number => "a number",
1619 Param::Text => "a string",
1620 Param::Temporal => "a date",
1621 Param::Sized => "a list, string or map",
1622 Param::List => "a list",
1623 Param::Any => "a value",
1624 }
1625}
1626
1627struct TypeScope<'a> {
1628 name: &'a str,
1629 ty: Value,
1630 parent: Option<&'a TypeScope<'a>>,
1631}
1632
1633fn literal(node: &Node) -> Option<&Value> {
1634 match node {
1635 Node::Lit(v, _) => Some(v),
1636 _ => None,
1637 }
1638}
1639
1640fn guards(node: &Node) -> (Vec<String>, Vec<String>) {
1644 match node {
1645 Node::Has(field, _) => (path_text(field).into_iter().collect(), Vec::new()),
1646 Node::Bin(op @ ("==" | "!="), a, b, _) => {
1647 let side = if matches!(a.as_ref(), Node::Lit(Value::Null, _)) {
1648 Some(b)
1649 } else if matches!(b.as_ref(), Node::Lit(Value::Null, _)) {
1650 Some(a)
1651 } else {
1652 None
1653 };
1654 match side.and_then(|s| path_text(s)) {
1655 None => (Vec::new(), Vec::new()),
1656 Some(p) if *op == "!=" => (vec![p], Vec::new()),
1657 Some(p) => (Vec::new(), vec![p]),
1658 }
1659 }
1660 Node::Bin("&&", a, b, _) => {
1661 let (mut yes, _) = guards(a);
1662 yes.extend(guards(b).0);
1663 (yes, Vec::new())
1664 }
1665 Node::Bin("||", a, b, _) => {
1666 let (_, mut no) = guards(a);
1667 no.extend(guards(b).1);
1668 (Vec::new(), no)
1669 }
1670 Node::Unary('!', arg, _) => {
1671 let (yes, no) = guards(arg);
1672 (no, yes)
1673 }
1674 _ => (Vec::new(), Vec::new()),
1675 }
1676}
1677
1678fn missing<T>(node: &Node) -> R<T> {
1680 let p = path_text(node).unwrap_or_else(|| format!("the value at {}", node_at(node)));
1681 Err(format!("{p} may be missing: guard it with has(), a null check or orElse()"))
1682}
1683
1684struct Checker<'t> {
1685 types: &'t Map<String, Value>,
1686 present: std::cell::RefCell<Vec<String>>,
1688}
1689
1690impl Checker<'_> {
1691 fn within(&self, paths: Vec<String>, node: &Node, scope: Option<&TypeScope>) -> R<Value> {
1693 let before = self.present.borrow().clone();
1694 self.present.borrow_mut().extend(paths);
1695 let out = self.tc(node, scope);
1696 *self.present.borrow_mut() = before;
1697 out
1698 }
1699
1700 fn need(&self, node: &Node, scope: Option<&TypeScope>) -> R<Value> {
1702 let t = self.tc(node, scope)?;
1703 if is_optional(&t) {
1704 return missing(node);
1705 }
1706 Ok(t)
1707 }
1708
1709 fn tc(&self, node: &Node, scope: Option<&TypeScope>) -> R<Value> {
1710 let t = crate::deep(|| self.tc_inner(node, scope))?;
1711 let p = if matches!(node, Node::Id(..) | Node::Field(..) | Node::Index(..)) { path_text(node) } else { None };
1712 Ok(match p {
1713 Some(p) if self.present.borrow().contains(&p) => required_type(&t),
1714 _ => t,
1715 })
1716 }
1717
1718 fn tc_inner(&self, node: &Node, scope: Option<&TypeScope>) -> R<Value> {
1719 match node {
1720 Node::Lit(v, _) => Ok(ty(type_name(v))),
1721 Node::List(items, _) => {
1722 let items = items.iter().map(|i| self.tc(i, scope)).collect::<R<Vec<_>>>()?;
1723 Ok(Value::Array(vec![match items.first() {
1724 None => ty("dyn"),
1725 Some(first) if items.iter().all(|t| t == first) => first.clone(),
1726 Some(_) => ty("dyn"),
1727 }]))
1728 }
1729 Node::Map(entries, _) => {
1730 let mut out = Map::new();
1731 for (k, v) in entries {
1732 out.insert(k.clone(), self.tc(v, scope)?);
1733 }
1734 Ok(Value::Object(out))
1735 }
1736 Node::Id(name, _) => {
1737 let mut s = scope;
1738 while let Some(sc) = s {
1739 if sc.name == name {
1740 return Ok(sc.ty.clone());
1741 }
1742 s = sc.parent;
1743 }
1744 self.types.get(name).cloned().ok_or_else(|| format!("unknown name {name}"))
1745 }
1746 Node::Field(..) | Node::Has(..) => {
1747 let has = matches!(node, Node::Has(..));
1748 let field = match node {
1749 Node::Has(field, _) => field.as_ref(),
1750 _ => node,
1751 };
1752 let Node::Field(of, name, _) = field else { return Err("has needs a field: has(a.b)".into()) };
1753 let raw = self.tc(of, scope)?;
1754 let of = required_type(&raw);
1755 let t = if is(&of, "map") {
1757 optional_type(ty("dyn"))
1758 } else if matches!(of.as_str(), Some("dyn" | "null")) {
1759 ty("dyn")
1760 } else if let Value::Object(fields) = &of {
1761 fields.get(name).cloned().ok_or_else(|| format!("unknown field {name}"))?
1762 } else {
1763 return Err(format!("no field {name} on {}", type_display(&of)));
1764 };
1765 if has {
1766 return Ok(ty("bool"));
1767 }
1768 Ok(if is_optional(&raw) { optional_type(t) } else { t })
1769 }
1770 Node::Index(of, index, _) => {
1771 let raw = self.tc(of, scope)?;
1772 let of_t = required_type(&raw);
1773 let index_t = self.need(index, scope)?;
1774 let wrap = |t: Value| if is_optional(&raw) { optional_type(t) } else { t };
1775 if let Value::Array(item) = &of_t {
1776 if !loose(&index_t) && !is(&index_t, "number") {
1777 return Err(format!("a list index is a number, got {}", type_display(&index_t)));
1778 }
1779 return Ok(optional_type(item.first().cloned().unwrap_or_else(|| ty("dyn"))));
1781 }
1782 if is_row(&of_t) || is(&of_t, "map") {
1783 if !loose(&index_t) && !is_textual(&index_t) {
1784 return Err(format!("a map key is a string, got {}", type_display(&index_t)));
1785 }
1786 if let (Value::Object(fields), Some(Value::String(key))) = (&of_t, literal(index)) {
1787 return fields.get(key).cloned().map(wrap).ok_or_else(|| format!("unknown field {key}"));
1788 }
1789 return Ok(optional_type(ty("dyn")));
1790 }
1791 if loose(&of_t) {
1792 return Ok(wrap(ty("dyn")));
1793 }
1794 Err(format!("cannot index {}", type_display(&of_t)))
1795 }
1796 Node::Unary(op, arg, _) => {
1797 let t = self.need(arg, scope)?;
1798 let want = if *op == '-' { "number" } else { "bool" };
1799 if !loose(&t) && !is(&t, want) {
1800 return Err(format!("cannot apply {op} to {}", type_display(&t)));
1801 }
1802 Ok(ty(want))
1803 }
1804 Node::Cond(test, then, otherwise, _) => {
1805 let t = self.need(test, scope)?;
1806 if !loose(&t) && !is(&t, "bool") {
1807 return Err(format!("cannot use {} as a condition", type_display(&t)));
1808 }
1809 let (yes, no) = guards(test);
1810 let a = self.within(yes, then, scope)?;
1811 let b = self.within(no, otherwise, scope)?;
1812 Ok(if a == b {
1813 a
1814 } else if is(&a, "null") {
1815 optional_type(b)
1816 } else if is(&b, "null") {
1817 optional_type(a)
1818 } else if required_type(&a) == required_type(&b) {
1819 optional_type(required_type(&a))
1820 } else if is_optional(&a) || is_optional(&b) {
1821 optional_type(ty("dyn"))
1822 } else {
1823 ty("dyn")
1824 })
1825 }
1826 Node::Bin(op, a, b, _) => self.binary(op, a, b, scope),
1827 Node::Call(name, args, _) => self.call(name, args, scope),
1828 Node::Macro(f, of, var, body, _) => {
1829 let of = self.need(of, scope)?;
1830 if !of.is_array() && !loose(&of) {
1831 return Err(format!("{f} needs a list, got {}", type_display(&of)));
1832 }
1833 let item = of.as_array().and_then(|a| a.first().cloned()).unwrap_or_else(|| ty("dyn"));
1834 let inner = TypeScope { name: var, ty: item, parent: scope };
1835 let body_t = self.tc(body, Some(&inner))?;
1836 if f == "map" {
1837 return Ok(Value::Array(vec![body_t]));
1838 }
1839 if is_optional(&body_t) {
1840 return missing(body);
1841 }
1842 if !loose(&body_t) && !is(&body_t, "bool") {
1843 return Err(format!("{f} needs a bool, got {}", type_display(&body_t)));
1844 }
1845 Ok(if f == "filter" {
1846 if of.is_array() {
1847 of
1848 } else {
1849 Value::Array(vec![ty("dyn")])
1850 }
1851 } else {
1852 ty("bool")
1853 })
1854 }
1855 }
1856 }
1857
1858 fn binary(&self, op: &str, a: &Node, b: &Node, scope: Option<&TypeScope>) -> R<Value> {
1859 if op == "&&" || op == "||" {
1860 let x = self.need(a, scope)?;
1861 let (yes, no) = guards(a);
1862 let y = self.within(if op == "&&" { yes } else { no }, b, scope)?;
1863 if is_optional(&y) {
1864 return missing(b);
1865 }
1866 for t in [&x, &y] {
1867 if !loose(t) && !is(t, "bool") {
1868 return Err(format!("cannot apply {op} to {}", type_display(t)));
1869 }
1870 }
1871 return Ok(ty("bool"));
1872 }
1873 if op == "==" || op == "!=" {
1874 let x = required_type(&self.tc(a, scope)?);
1876 let y = required_type(&self.tc(b, scope)?);
1877 let (dx, dy) = (type_display(&x), type_display(&y));
1878 let comparable = loose(&x) || loose(&y) || (is_textual(&x) && is_textual(&y)) || dx == dy;
1879 if !comparable {
1880 return Err(format!("cannot compare {dx} and {dy}"));
1881 }
1882 return Ok(ty("bool"));
1883 }
1884 let x = self.need(a, scope)?;
1885 let y = self.need(b, scope)?;
1886 let (dx, dy) = (type_display(&x), type_display(&y));
1887 let numeric = |t: &Value| is(t, "number") || loose(t);
1888 match op {
1889 "<" | "<=" | ">" | ">=" => {
1890 let ok = |t: &Value| loose(t) || is(t, "number") || is_textual(t);
1891 if !ok(&x) || !ok(&y) || (is(&x, "number") && is_textual(&y)) || (is(&y, "number") && is_textual(&x)) {
1892 return Err(format!("cannot compare {dx} and {dy}"));
1893 }
1894 Ok(ty("bool"))
1895 }
1896 "+" => {
1897 if loose(&x) && loose(&y) {
1898 return Ok(ty("dyn"));
1899 }
1900 if numeric(&x) && numeric(&y) {
1901 return Ok(ty("number"));
1902 }
1903 if (is_textual(&x) || loose(&x)) && (is_textual(&y) || loose(&y)) {
1904 return Ok(ty("string"));
1905 }
1906 if (x.is_array() || loose(&x)) && (y.is_array() || loose(&y)) {
1907 return Ok(if x.is_array() && y.is_array() && x == y { x } else { Value::Array(vec![ty("dyn")]) });
1908 }
1909 Err(format!("cannot apply + to {dx} and {dy}"))
1910 }
1911 _ => {
1912 if !(numeric(&x) && numeric(&y)) {
1913 return Err(format!("cannot apply {op} to {dx} and {dy}"));
1914 }
1915 if (op == "/" || op == "%") && literal(b).is_some_and(|v| v.as_f64() == Some(0.0)) {
1916 return Err(if op == "/" { "division by zero" } else { "modulo by zero" }.into());
1917 }
1918 Ok(ty("number"))
1919 }
1920 }
1921 }
1922
1923 fn call(&self, name: &str, args: &[Node], scope: Option<&TypeScope>) -> R<Value> {
1924 let d = def(name).ok_or_else(|| format!("unknown function {name}"))?;
1925 if let Some(e) = arity_error(name, &d, args.len()) {
1926 return Err(e);
1927 }
1928 let types = args.iter().enumerate().map(|(i, a)| if name == "orElse" && i == 0 { self.tc(a, scope) } else { self.need(a, scope) }).collect::<R<Vec<_>>>()?;
1930 for (i, t) in types.iter().enumerate() {
1931 let param = d.params[i.min(d.params.len() - 1)];
1932 if !accepts(param, &required_type(t)) {
1933 return Err(format!("{name} needs {}, got {}", param_name(param), type_display(t)));
1934 }
1935 }
1936 if name == "orElse" {
1937 let (x, y) = (required_type(&types[0]), &types[1]);
1938 if !loose(&x) && !loose(y) && !(is_textual(&x) && is_textual(y)) && type_display(&x) != type_display(y) {
1939 return Err(format!("orElse needs a {} default, got {}", type_display(&x), type_display(y)));
1940 }
1941 }
1942 let lit = |i: usize| args.get(i).and_then(literal);
1943 let unit_at = match name {
1944 "dateAdd" | "dateDiff" => Some(2),
1945 "startOf" | "endOf" => Some(1),
1946 _ => None,
1947 };
1948 if let Some(Value::String(unit)) = unit_at.and_then(lit) {
1949 let units = if unit_at == Some(2) { DATE_UNITS } else { CALENDAR_UNITS };
1950 if !units.contains(&unit.as_str()) {
1951 return Err(format!("unknown unit {unit}"));
1952 }
1953 if name == "dateAdd" && is(&types[0], "date") && unit_ms(unit).is_some() {
1954 return Err(format!("unit {unit} needs a timestamp"));
1955 }
1956 }
1957 if name == "dateAdd" {
1958 if let Some(n) = lit(1).and_then(Value::as_f64) {
1959 if n.fract() != 0.0 {
1960 return Err(format!("dateAdd needs a whole number, got {}", num_text(n)));
1961 }
1962 }
1963 }
1964 if name == "round" && args.len() > 1 {
1965 if let Some(d) = lit(1).and_then(Value::as_f64) {
1966 if d.fract() != 0.0 || !(0.0..=10.0).contains(&d) {
1967 return Err("round needs 0 to 10 digits".into());
1968 }
1969 }
1970 }
1971 if name == "sortBy" {
1972 if let Some(Value::String(o)) = lit(2) {
1973 if o != "asc" && o != "desc" {
1974 return Err("sortBy order is asc or desc".into());
1975 }
1976 }
1977 }
1978 if name == "split" && matches!(lit(1), Some(Value::String(s)) if s.is_empty()) {
1979 return Err("split needs a separator".into());
1980 }
1981 if name == "div" && lit(1).and_then(Value::as_f64) == Some(0.0) {
1982 return Err("division by zero".into());
1983 }
1984 if name == "mod" && lit(1).and_then(Value::as_f64) == Some(0.0) {
1985 return Err("modulo by zero".into());
1986 }
1987 if name == "formatDuration" {
1988 if let Some(Value::String(f)) = lit(1) {
1989 if !DURATION_FORMATS.contains(&f.as_str()) {
1990 return Err(format!("unknown duration format {f}"));
1991 }
1992 }
1993 }
1994 Ok(match name {
1995 "dateAdd" | "startOf" | "endOf" => {
1996 if is(&types[0], "date") || is(&types[0], "timestamp") {
1997 types[0].clone()
1998 } else {
1999 ty("dyn")
2000 }
2001 }
2002 "today" => ty("date"),
2003 "now" | "dateTime" => ty("timestamp"),
2004 "formatDuration" | "string" | "trim" | "lower" | "upper" | "join" => ty("string"),
2005 "split" => Value::Array(vec![ty("string")]),
2006 "avgOf" | "minOf" | "maxOf" => optional_type(ty("number")),
2007 "sortBy" => {
2008 if types[0].is_array() {
2009 types[0].clone()
2010 } else {
2011 Value::Array(vec![ty("dyn")])
2012 }
2013 }
2014 "groupOf" => {
2015 let rows = if types[0].is_array() { types[0].clone() } else { Value::Array(vec![ty("dyn")]) };
2016 Value::Array(vec![serde_json::json!({"key": optional_type(ty("dyn")), "rows": rows})])
2017 }
2018 "orElse" => {
2019 let x = required_type(&types[0]);
2020 if is(&x, "null") {
2021 required_type(&types[1])
2022 } else {
2023 x
2024 }
2025 }
2026 _ => ty("number"),
2027 })
2028 }
2029}
2030
2031pub fn check_expression(source: &str, types: &Map<String, Value>) -> Result<Value, String> {
2036 let ast = parse_expression(source)?;
2037 Checker { types, present: std::cell::RefCell::new(Vec::new()) }.tc(&ast, None)
2038}