1use crate::formula::{BinaryOp, DimensionSpec, Expr, Formula, FuncId, UnaryOp};
109use crate::functions;
110use crate::ids::{CategoryId, MeasureId, Name};
111use crate::value::Value;
112use crate::Model;
113
114pub const FUNC_SUM: FuncId = FuncId(1);
115pub const FUNC_AVG: FuncId = FuncId(2);
116pub const FUNC_MIN: FuncId = FuncId(3);
117pub const FUNC_MAX: FuncId = FuncId(4);
118
119pub fn scalar_func(name: &str) -> Option<(FuncId, usize)> {
123 functions::callable(name).map(|d| (d.id, d.params.len()))
124}
125
126#[derive(Debug, Clone, PartialEq)]
129pub struct ParseError {
130 pub message: String,
131 pub position: Option<usize>,
132}
133
134impl ParseError {
135 fn new(message: impl Into<String>, position: Option<usize>) -> Self {
136 ParseError {
137 message: message.into(),
138 position,
139 }
140 }
141}
142
143impl std::fmt::Display for ParseError {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 match self.position {
146 Some(p) => write!(f, "parse error at {p}: {}", self.message),
147 None => write!(f, "parse error: {}", self.message),
148 }
149 }
150}
151
152impl std::error::Error for ParseError {}
153
154#[derive(Debug, Clone, PartialEq)]
156pub struct FormulaText {
157 pub target: Name,
158 pub formula: Formula,
159}
160
161#[derive(Debug, Clone, PartialEq)]
167pub enum Definition {
168 Formula(FormulaText),
170 Sql { target: Name, query: String },
173 Call {
177 target: Name,
178 func: String,
179 args: Vec<Name>,
180 },
181 Lambda(LambdaText),
184}
185
186#[derive(Debug, Clone, PartialEq)]
188pub struct LambdaText {
189 pub name: Name,
190 pub lambda: crate::NamedLambda,
191}
192
193#[derive(Debug, Clone, PartialEq)]
198enum Tok {
199 Ident(String),
200 QIdent(String),
204 Number(f64),
205 Str(String),
206 Date(chrono::DateTime<chrono::Utc>),
209 Op(String),
211}
212
213#[derive(Debug, Clone)]
215struct Spanned {
216 tok: Tok,
217 pos: usize,
218}
219
220fn parse_date_literal(raw: &str) -> Option<chrono::DateTime<chrono::Utc>> {
223 let raw = raw.trim();
224 if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(raw) {
225 return Some(dt.with_timezone(&chrono::Utc));
226 }
227 if let Ok(d) = chrono::NaiveDate::parse_from_str(raw, "%Y-%m-%d") {
228 return d.and_hms_opt(0, 0, 0).map(|ndt| ndt.and_utc());
229 }
230 None
231}
232
233fn tokenize(text: &str) -> Result<Vec<Spanned>, ParseError> {
234 let bytes = text.as_bytes();
235 let mut out = Vec::new();
236 let mut i = 0;
237 while i < bytes.len() {
238 let start = i;
239 let c = text[i..]
245 .chars()
246 .next()
247 .expect("i < len implies a char here");
248 match c {
249 c if c.is_whitespace() => i += c.len_utf8(),
250 '"' => {
251 i += 1;
253 let s0 = i;
254 while i < bytes.len() && bytes[i] != b'"' {
255 i += 1;
256 }
257 if i >= bytes.len() {
258 return Err(ParseError::new("unterminated string literal", Some(start)));
259 }
260 let s = text[s0..i].to_string();
261 i += 1; out.push(Spanned {
263 tok: Tok::Str(s),
264 pos: start,
265 });
266 }
267 '#' => {
268 i += 1;
270 let s0 = i;
271 while i < bytes.len() && bytes[i] != b'#' {
272 i += 1;
273 }
274 if i >= bytes.len() {
275 return Err(ParseError::new("unterminated date literal", Some(start)));
276 }
277 let raw = &text[s0..i];
278 i += 1; let dt = parse_date_literal(raw).ok_or_else(|| {
280 ParseError::new(format!("invalid date: {raw:?}"), Some(start))
281 })?;
282 out.push(Spanned {
283 tok: Tok::Date(dt),
284 pos: start,
285 });
286 }
287 c if c.is_ascii_digit() => {
288 while i < bytes.len()
292 && (bytes[i].is_ascii_digit()
293 || (bytes[i] == b'.' && bytes.get(i + 1).is_some_and(u8::is_ascii_digit)))
294 {
295 i += 1;
296 }
297 let raw = &text[start..i];
298 let n = raw
299 .parse::<f64>()
300 .map_err(|_| ParseError::new(format!("invalid number: {raw}"), Some(start)))?;
301 out.push(Spanned {
302 tok: Tok::Number(n),
303 pos: start,
304 });
305 }
306 '\'' => {
307 i += 1; let mut name = String::new();
312 let mut closed = false;
313 while i < bytes.len() {
314 let c = text[i..].chars().next().expect("i < len");
315 i += c.len_utf8();
316 if c == '\'' {
317 if text[i..].starts_with('\'') {
319 name.push('\'');
320 i += 1;
321 continue;
322 }
323 closed = true;
324 break;
325 }
326 name.push(c);
327 }
328 if !closed {
329 return Err(ParseError::new("unterminated quoted name", Some(start)));
330 }
331 if name.is_empty() {
332 return Err(ParseError::new(
333 "empty quoted name: '' names nothing",
334 Some(start),
335 ));
336 }
337 out.push(Spanned {
338 tok: Tok::QIdent(name),
339 pos: start,
340 });
341 }
342 c if c.is_alphabetic()
343 || c == '_'
344 || (c == '@' && text[i + 1..].starts_with(char::is_alphabetic)) =>
345 {
346 i += c.len_utf8();
347 let word_end = |mut i: usize| {
348 while i < bytes.len() {
349 let b = text[i..].chars().next().expect("i < len");
350 if b.is_alphanumeric() || b == '_' {
351 i += b.len_utf8();
352 } else {
353 break;
354 }
355 }
356 i
357 };
358 i = word_end(i);
359 let mut j = i;
363 let mut best = None;
364 let mut parts = 0;
367 while parts < 3
368 && text[j..].starts_with('.')
369 && text[j + 1..].starts_with(|c: char| c.is_alphanumeric() || c == '_')
370 {
371 parts += 1;
372 j = word_end(j + 1);
373 let rest = text[j..].trim_start();
374 if rest.starts_with('(') && functions::by_name(&text[start..j]).next().is_some()
375 {
376 best = Some(j);
377 }
378 }
379 if let Some(end) = best {
380 i = end;
381 }
382 out.push(Spanned {
383 tok: Tok::Ident(text[start..i].to_string()),
384 pos: start,
385 });
386 }
387 _ => {
389 let two = text.get(i..i + 2);
390 let op = match two {
391 Some("<=") | Some(">=") | Some("<>") | Some("==") | Some("!=") => {
392 i += 2;
393 two.unwrap().to_string()
394 }
395 _ => {
396 let single = "+-*/^=<>()[],.&%".find(c);
397 if single.is_none() {
398 return Err(ParseError::new(
399 format!("unexpected character: {c:?}"),
400 Some(start),
401 ));
402 }
403 i += c.len_utf8();
404 c.to_string()
405 }
406 };
407 out.push(Spanned {
408 tok: Tok::Op(op),
409 pos: start,
410 });
411 }
412 }
413 }
414 Ok(out)
415}
416
417#[derive(Default)]
423struct OverClause {
424 cats: Vec<CategoryId>,
425 all: bool,
427 except: Vec<CategoryId>,
428 window: Option<(Expr, Expr)>,
430 into: Vec<CategoryId>,
432 pred: Option<Expr>,
433}
434
435struct Parser<'a> {
436 toks: &'a [Spanned],
437 pos: usize,
438 model: &'a Model,
439 depth: usize,
443 scope: Vec<(String, Expr)>,
446 lambdas: u32,
449}
450
451const MAX_PARSE_DEPTH: usize = 100;
456
457impl<'a> Parser<'a> {
458 fn peek(&self) -> Option<&Tok> {
459 self.toks.get(self.pos).map(|s| &s.tok)
460 }
461
462 fn peek_pos(&self) -> Option<usize> {
463 self.toks.get(self.pos).map(|s| s.pos).or_else(|| {
464 self.toks.last().map(|s| s.pos)
466 })
467 }
468
469 fn bump(&mut self) -> Option<&Tok> {
470 let t = self.toks.get(self.pos).map(|s| &s.tok);
471 if t.is_some() {
472 self.pos += 1;
473 }
474 t
475 }
476
477 fn eat_op(&mut self, op: &str) -> bool {
479 if matches!(self.peek(), Some(Tok::Op(o)) if o == op) {
480 self.pos += 1;
481 true
482 } else {
483 false
484 }
485 }
486
487 fn peek_kw(&self, kw: &str) -> bool {
491 matches!(self.peek(), Some(Tok::Ident(w)) if w.eq_ignore_ascii_case(kw))
492 }
493
494 fn take_ident(&mut self, what: &str) -> Result<String, ParseError> {
497 match self.peek() {
498 Some(Tok::Ident(w) | Tok::QIdent(w)) => {
499 let w = w.clone();
500 self.pos += 1;
501 Ok(w)
502 }
503 _ => Err(self.err(format!("expected {what}"))),
504 }
505 }
506
507 fn parse_name(&mut self, what: &str) -> Result<(String, Option<usize>), ParseError> {
518 let pos = self.peek_pos();
519 let first = self.take_ident(what)?;
520 if !self.eat_op(".") {
521 return Ok((first, pos));
522 }
523 let leaf = self.take_ident(what)?;
524 Err(ParseError::new(
525 format!(
526 "unknown qualifier: {first} (no such matrix; `{first}.{leaf}` cannot be \
527 resolved — write the name alone)"
528 ),
529 pos,
530 ))
531 }
532
533 fn err(&self, msg: impl Into<String>) -> ParseError {
534 ParseError::new(msg, self.peek_pos())
535 }
536
537 fn measure_id(&self, name: &str, pos: Option<usize>) -> Result<MeasureId, ParseError> {
542 self.model
543 .measure_by_name(name)
544 .map(|m| m.id)
545 .ok_or_else(|| ParseError::new(format!("unknown measure: {name}"), pos))
546 }
547
548 fn category_id(&self, name: &str, pos: Option<usize>) -> Result<CategoryId, ParseError> {
549 if let Some(Expr::Ref(id, _)) = self.local(name) {
551 if let Some(i) = functions::lambda_param_of(id) {
552 return Ok(functions::lambda_category(i));
553 }
554 }
555 self.model
556 .category_by_name(name)
557 .map(|c| c.id)
558 .ok_or_else(|| ParseError::new(format!("unknown category: {name}"), pos))
559 }
560
561 fn parse_expr(&mut self) -> Result<Expr, ParseError> {
565 self.parse_or()
566 }
567
568 fn parse_or(&mut self) -> Result<Expr, ParseError> {
570 let mut lhs = self.parse_and()?;
571 while self.peek_kw("or") {
572 self.bump();
573 let rhs = self.parse_and()?;
574 lhs = Expr::BinaryOp(BinaryOp::Or, Box::new(lhs), Box::new(rhs));
575 }
576 Ok(lhs)
577 }
578
579 fn parse_and(&mut self) -> Result<Expr, ParseError> {
581 let mut lhs = self.parse_not()?;
582 while self.peek_kw("and") {
583 self.bump();
584 let rhs = self.parse_not()?;
585 lhs = Expr::BinaryOp(BinaryOp::And, Box::new(lhs), Box::new(rhs));
586 }
587 Ok(lhs)
588 }
589
590 fn parse_not(&mut self) -> Result<Expr, ParseError> {
597 let mut nots = 0usize;
598 while self.peek_kw("not") {
599 if self.depth + nots >= MAX_PARSE_DEPTH {
600 return Err(self.err("expression nested too deeply"));
601 }
602 self.bump();
603 nots += 1;
604 }
605 self.depth += nots;
606 let e = self.parse_comparison();
607 self.depth -= nots;
608 let mut e = e?;
609 for _ in 0..nots {
610 e = Expr::UnaryOp(UnaryOp::Not, Box::new(e));
611 }
612 Ok(e)
613 }
614
615 fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
617 let lhs = self.parse_concat()?;
618 let op = match self.peek() {
619 Some(Tok::Op(o)) => match o.as_str() {
620 "==" => Some(BinaryOp::Eq),
621 "<>" | "!=" => Some(BinaryOp::Ne),
622 "<" => Some(BinaryOp::Lt),
623 "<=" => Some(BinaryOp::Le),
624 ">" => Some(BinaryOp::Gt),
625 ">=" => Some(BinaryOp::Ge),
626 _ => None,
627 },
628 _ => None,
629 };
630 match op {
631 Some(op) => {
632 self.bump();
633 let rhs = self.parse_concat()?;
634 Ok(Expr::BinaryOp(op, Box::new(lhs), Box::new(rhs)))
635 }
636 None => Ok(lhs),
637 }
638 }
639
640 fn parse_concat(&mut self) -> Result<Expr, ParseError> {
642 let mut lhs = self.parse_additive()?;
643 while self.eat_op("&") {
644 let rhs = self.parse_additive()?;
645 lhs = Expr::BinaryOp(BinaryOp::Concat, Box::new(lhs), Box::new(rhs));
646 }
647 Ok(lhs)
648 }
649
650 fn parse_additive(&mut self) -> Result<Expr, ParseError> {
652 let mut lhs = self.parse_term()?;
653 loop {
654 let op = match self.peek() {
655 Some(Tok::Op(o)) if o == "+" => BinaryOp::Add,
656 Some(Tok::Op(o)) if o == "-" => BinaryOp::Sub,
657 _ => break,
658 };
659 self.bump();
660 let rhs = self.parse_term()?;
661 lhs = Expr::BinaryOp(op, Box::new(lhs), Box::new(rhs));
662 }
663 Ok(lhs)
664 }
665
666 fn parse_term(&mut self) -> Result<Expr, ParseError> {
668 let mut lhs = self.parse_unary()?;
669 loop {
670 let op = match self.peek() {
671 Some(Tok::Op(o)) if o == "*" => BinaryOp::Mul,
672 Some(Tok::Op(o)) if o == "/" => BinaryOp::Div,
673 _ => break,
674 };
675 self.bump();
676 let rhs = self.parse_unary()?;
677 lhs = Expr::BinaryOp(op, Box::new(lhs), Box::new(rhs));
678 }
679 Ok(lhs)
680 }
681
682 fn parse_unary(&mut self) -> Result<Expr, ParseError> {
686 let mut negs = 0usize;
687 while matches!(self.peek(), Some(Tok::Op(o)) if o == "-") {
688 if self.depth + negs >= MAX_PARSE_DEPTH {
689 return Err(self.err("expression nested too deeply"));
690 }
691 self.bump();
692 negs += 1;
693 }
694 self.depth += negs;
695 let e = self.parse_power();
696 self.depth -= negs;
697 let mut e = e?;
698 for _ in 0..negs {
699 e = Expr::UnaryOp(UnaryOp::Neg, Box::new(e));
700 }
701 Ok(e)
702 }
703
704 fn parse_power(&mut self) -> Result<Expr, ParseError> {
707 let base = self.parse_postfix()?;
708 if !self.eat_op("^") {
709 return Ok(base);
710 }
711 self.depth += 1;
712 if self.depth > MAX_PARSE_DEPTH {
713 self.depth -= 1;
714 return Err(self.err("expression nested too deeply"));
715 }
716 let exp = self.parse_unary();
717 self.depth -= 1;
718 Ok(Expr::BinaryOp(
719 BinaryOp::Pow,
720 Box::new(base),
721 Box::new(exp?),
722 ))
723 }
724
725 fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
727 let mut e = self.parse_primary()?;
728 while self.eat_op("%") {
729 e = match e {
730 Expr::Literal(Value::Number(n)) => Expr::Literal(Value::Number(n / 100.0)),
731 other => Expr::BinaryOp(
732 BinaryOp::Div,
733 Box::new(other),
734 Box::new(Expr::Literal(Value::Number(100.0))),
735 ),
736 };
737 }
738 Ok(e)
739 }
740
741 fn parse_primary(&mut self) -> Result<Expr, ParseError> {
743 self.depth += 1;
744 if self.depth > MAX_PARSE_DEPTH {
745 self.depth -= 1;
746 return Err(self.err("expression nested too deeply"));
747 }
748 let result = self.parse_primary_inner();
749 self.depth -= 1;
750 result
751 }
752
753 fn parse_primary_inner(&mut self) -> Result<Expr, ParseError> {
754 match self.peek() {
755 None => Err(self.err("unexpected end of input")),
756 Some(Tok::Op(o)) if o == "(" => {
757 self.bump();
758 let e = self.parse_expr()?;
759 if !self.eat_op(")") {
760 return Err(self.err("expected ')'"));
761 }
762 Ok(e)
763 }
764 Some(Tok::Number(n)) => {
765 let n = *n;
766 self.bump();
767 Ok(Expr::Literal(Value::Number(n)))
768 }
769 Some(Tok::Str(s)) => {
770 let s = s.clone();
771 self.bump();
772 Ok(Expr::Literal(Value::Text(s)))
773 }
774 Some(Tok::Date(dt)) => {
775 let dt = *dt;
776 self.bump();
777 Ok(Expr::Literal(Value::DateTime(dt)))
778 }
779 Some(Tok::Ident(_)) => self.parse_ident_primary(),
780 Some(Tok::QIdent(_)) => self.parse_measure_ref(),
782 Some(Tok::Op(o)) => Err(self.err(format!("unexpected operator: {o}"))),
783 }
784 }
785
786 fn parse_ident_primary(&mut self) -> Result<Expr, ParseError> {
788 let word = match self.peek() {
790 Some(Tok::Ident(w)) => w.clone(),
791 _ => return Err(self.err("expected identifier")),
792 };
793 let is_call =
795 matches!(self.toks.get(self.pos + 1).map(|s| &s.tok), Some(Tok::Op(o)) if o == "(");
796 if !is_call {
798 if let Some(e) = self.local(&word) {
799 self.bump();
800 return Ok(e);
801 }
802 }
803 if is_call && functions::callable(&word).is_none() {
804 if let Some((id, l)) = self.model.lambda_by_name(&word) {
805 let kinds = l.param_kinds().map_err(|e| self.err(e))?;
806 return self.parse_named_lambda_call(id, &kinds, &word);
807 }
808 }
809 if is_call {
810 match word.to_ascii_uppercase().as_str() {
811 "LET" => return self.parse_let(),
812 "LAMBDA" => return self.parse_lambda_call(),
813 "MAP" => return self.parse_map(),
814 "SEQUENCE" => return self.parse_sequence(),
815 "MAKEARRAY" => return self.parse_makearray(),
816 "BYROW" | "BYCOL" => return self.parse_byrow(&word),
817 "REDUCE" | "SCAN" => return self.parse_fold(&word),
818 "FIXPOINT" => return self.parse_fixpoint(),
819 _ => {}
820 }
821 }
822 if is_call && (word.eq_ignore_ascii_case("true") || word.eq_ignore_ascii_case("false")) {
823 return self.parse_call(&word);
824 }
825
826 if word.eq_ignore_ascii_case("true") {
827 self.bump();
828 return Ok(Expr::Literal(Value::Boolean(true)));
829 }
830 if word.eq_ignore_ascii_case("false") {
831 self.bump();
832 return Ok(Expr::Literal(Value::Boolean(false)));
833 }
834 if is_call
837 && (functions::aggregation(&word).is_some() || functions::callable(&word).is_some())
838 {
839 return self.parse_call(&word);
840 }
841 if is_call && self.model.measure_by_name(&word).is_none() {
844 let pos = self.toks.get(self.pos).map(|s| s.pos);
845 let near = functions::closest(&word, 3);
846 let hint = if near.is_empty() {
847 String::new()
848 } else {
849 format!("; did you mean {}?", near.join(", "))
850 };
851 return Err(ParseError::new(
852 format!("unknown function: {word}{hint}"),
853 pos,
854 ));
855 }
856 self.parse_measure_ref()
857 }
858
859 fn parse_call(&mut self, name: &str) -> Result<Expr, ParseError> {
864 self.bump(); if !self.eat_op("(") {
866 return Err(self.err("expected '(' after function name"));
867 }
868 let callable = functions::callable(name);
871 let mut args = Vec::new();
872 let mut over: Option<OverClause> = None;
873 let mut into: Vec<CategoryId> = Vec::new();
874 if !matches!(self.toks.get(self.pos).map(|s| &s.tok), Some(Tok::Op(o)) if o == ")") {
875 loop {
876 let i = args.len();
877 let is_cat = over.is_none()
878 && callable.is_some_and(|d| d.param(i) == functions::Arg::Category)
879 && functions::aggregation(name).is_none();
880 if is_cat {
881 args.push(Expr::Category(self.parse_category_name()?));
882 } else {
883 args.push(self.parse_expr()?);
884 }
885 if self.peek_kw("over") {
886 if over.is_some() {
887 return Err(self.err("only one OVER clause per aggregation"));
888 }
889 over = Some(self.parse_over_clause()?);
890 }
891 if self.peek_kw("into") {
892 self.bump();
893 if !into.is_empty() {
894 return Err(self.err("only one INTO clause per call"));
895 }
896 into.push(self.parse_category_name()?);
898 while self.comma_then_category() {
899 self.bump(); into.push(self.parse_category_name()?);
901 }
902 }
903 if self.eat_op(",") {
904 continue;
905 }
906 break;
907 }
908 }
909 if !self.eat_op(")") {
910 return Err(self.err("expected ')' to close function call"));
911 }
912 if over.is_none() && functions::aggregation(name).is_some() {
915 over = args.iter().find_map(|a| match a {
916 Expr::Ref(_, s) if !s.over.is_empty() => Some(OverClause {
917 cats: s.over.clone(),
918 ..OverClause::default()
919 }),
920 _ => None,
921 });
922 }
923 if over.is_some() || !into.is_empty() {
924 let mut clause = over.unwrap_or_default();
925 clause.into = into;
926 return self.finish_aggregation(name, args, clause);
927 }
928 let def = callable.ok_or_else(|| {
929 self.err(format!(
930 "{name} aggregates: write {name}(Measure OVER Category)"
931 ))
932 })?;
933 if !def.accepts(args.len()) {
934 return Err(self.err(format!(
935 "{} takes {}, got {} argument(s)",
936 def.name,
937 def.hint(),
938 args.len()
939 )));
940 }
941 Ok(Expr::Call(def.id, args))
942 }
943
944 fn parse_over_clause(&mut self) -> Result<OverClause, ParseError> {
949 self.bump(); let mut clause = OverClause::default();
951 if self.peek_kw("all") && !self.next_is_open_paren() {
952 self.bump();
953 clause.all = true;
954 if self.peek_kw("except") {
955 self.bump();
956 clause.except.push(self.parse_category_name()?);
957 while self.comma_then_category() {
958 self.bump(); clause.except.push(self.parse_category_name()?);
960 }
961 }
962 } else {
963 clause.cats.push(self.parse_category_name()?);
964 if self.eat_op("[") {
965 let from = self.parse_bound()?;
966 if !self.eat_op(".") || !self.eat_op(".") {
967 return Err(self.err("expected '..' in a window: OVER T[FIRST..THIS]"));
968 }
969 let to = self.parse_bound()?;
970 if !self.eat_op("]") {
971 return Err(self.err("expected ']' to close the window"));
972 }
973 clause.window = Some((from, to));
974 } else {
975 while self.comma_then_category() {
976 self.bump(); clause.cats.push(self.parse_category_name()?);
978 }
979 }
980 }
981 if self.peek_kw("where") {
982 self.bump();
983 clause.pred = Some(self.parse_expr()?);
984 }
985 Ok(clause)
986 }
987
988 fn parse_bound(&mut self) -> Result<Expr, ParseError> {
991 let word = match self.peek() {
992 Some(Tok::Ident(w)) => w.to_ascii_uppercase(),
993 _ => return Err(self.err("expected FIRST, LAST or THIS in a window")),
994 };
995 self.bump();
996 let lit = |v| Ok(Expr::Literal(v));
997 match word.as_str() {
998 "FIRST" | "LAST" => lit(Value::Text(word)),
999 "THIS" => {
1000 let sign = match self.peek() {
1001 Some(Tok::Op(o)) if o == "+" => 1.0,
1002 Some(Tok::Op(o)) if o == "-" => -1.0,
1003 _ => return lit(Value::Number(0.0)),
1004 };
1005 self.bump();
1006 match self.peek() {
1007 Some(Tok::Number(n)) if n.fract() == 0.0 => {
1008 let n = *n;
1009 self.bump();
1010 lit(Value::Number(sign * n))
1011 }
1012 _ => Err(self.err("expected a whole number after THIS+ / THIS-")),
1013 }
1014 }
1015 _ => Err(self.err("expected FIRST, LAST or THIS in a window")),
1016 }
1017 }
1018
1019 fn next_is_open_paren(&self) -> bool {
1020 matches!(self.toks.get(self.pos + 1).map(|s| &s.tok), Some(Tok::Op(o)) if o == "(")
1021 }
1022
1023 fn comma_then_category(&self) -> bool {
1026 let at = |k: usize| self.toks.get(self.pos + k).map(|s| &s.tok);
1027 let name = match (at(0), at(1)) {
1028 (Some(Tok::Op(c)), Some(Tok::Ident(n) | Tok::QIdent(n))) if c == "," => n,
1029 _ => return false,
1030 };
1031 let followed_ok = !matches!(at(2), Some(Tok::Op(o)) if o == "(" || o == ".");
1032 followed_ok
1033 && self.model.category_by_name(name).is_some()
1034 && self.model.measure_by_name(name).is_none()
1035 }
1036
1037 fn finish_aggregation(
1041 &mut self,
1042 name: &str,
1043 mut args: Vec<Expr>,
1044 clause: OverClause,
1045 ) -> Result<Expr, ParseError> {
1046 let def = functions::aggregation(name)
1047 .ok_or_else(|| self.err(format!("{name} does not aggregate OVER a category")))?;
1048 if !def.accepts(args.len()) {
1049 return Err(self.err(format!(
1050 "{} takes {}, got {} argument(s)",
1051 def.name,
1052 def.hint(),
1053 args.len()
1054 )));
1055 }
1056 if clause.window.is_some() && def.kind == functions::Kind::PerItem {
1057 return Err(self.err(format!("{} cannot take a window", def.name)));
1058 }
1059 let leading = match def.over {
1061 functions::Over::Leading(min) => {
1062 let n = args
1063 .iter()
1064 .take_while(|a| matches!(a, Expr::Ref(..)))
1065 .count();
1066 if n < min {
1067 return Err(self.err(format!(
1068 "{} needs at least {min} measure(s) before its options",
1069 def.name
1070 )));
1071 }
1072 Some(n)
1073 }
1074 _ => None,
1075 };
1076 for (i, a) in args.iter_mut().enumerate() {
1077 if !leading.map_or_else(|| def.over.contains(i), |n| i < n) {
1078 continue;
1079 }
1080 let Expr::Ref(id, spec) = a else {
1081 return Err(self.err(format!(
1082 "{} argument {} must be a measure reference (it is reduced OVER the \
1083 categories)",
1084 def.name,
1085 i + 1
1086 )));
1087 };
1088 let mut s = spec.clone();
1089 if clause.all {
1090 let m = self.model.measures.get(id);
1093 s.over = m
1094 .map(|m| {
1095 m.categories
1096 .iter()
1097 .copied()
1098 .filter(|c| !clause.except.contains(c))
1099 .collect()
1100 })
1101 .unwrap_or_default();
1102 } else {
1103 s.over = clause.cats.clone();
1104 }
1105 *a = Expr::Ref(*id, s);
1106 }
1107 if let Some(p) = clause.pred {
1108 args.push(Expr::Call(functions::WHERE, vec![p]));
1109 }
1110 if let Some((from, to)) = clause.window {
1111 args.push(Expr::Call(functions::WINDOW, vec![from, to]));
1112 }
1113 if !clause.into.is_empty() {
1114 args.push(Expr::Call(
1115 functions::INTO,
1116 clause.into.into_iter().map(Expr::Category).collect(),
1117 ));
1118 }
1119 Ok(Expr::Call(def.id, args))
1120 }
1121
1122 fn local(&self, name: &str) -> Option<Expr> {
1124 self.scope
1125 .iter()
1126 .rev()
1127 .find(|(n, _)| n.eq_ignore_ascii_case(name))
1128 .map(|(_, e)| e.clone())
1129 }
1130
1131 fn binding_name(&mut self) -> Result<String, ParseError> {
1133 match self.peek() {
1134 Some(Tok::Ident(w)) => {
1135 let w = w.clone();
1136 self.bump();
1137 Ok(w)
1138 }
1139 _ => Err(self.err("expected a name to bind")),
1140 }
1141 }
1142
1143 fn expect(&mut self, op: &str, what: &str) -> Result<(), ParseError> {
1144 if self.eat_op(op) {
1145 Ok(())
1146 } else {
1147 Err(self.err(format!("expected {what}")))
1148 }
1149 }
1150
1151 fn parse_let(&mut self) -> Result<Expr, ParseError> {
1155 self.bump(); self.expect("(", "'(' after LET")?;
1157 let base = self.scope.len();
1158 loop {
1159 let is_binding = matches!(self.peek(), Some(Tok::Ident(_)))
1161 && matches!(self.toks.get(self.pos + 1).map(|s| &s.tok), Some(Tok::Op(o)) if o == ",")
1162 && self.scope.len() - base < 126;
1163 if !is_binding && self.scope.len() > base {
1164 break;
1165 }
1166 let name = self.binding_name()?;
1167 self.expect(",", "',' after a LET name")?;
1168 let value = self.parse_expr()?;
1169 self.scope.push((name, value));
1170 self.expect(",", "',' then the LET body")?;
1171 }
1172 let body = self.parse_expr();
1173 self.scope.truncate(base);
1174 let body = body?;
1175 self.expect(")", "')' to close LET")?;
1176 Ok(body)
1177 }
1178
1179 fn parse_lambda_params(&mut self) -> Result<(Vec<String>, usize, usize), ParseError> {
1182 self.bump(); self.expect("(", "'(' after LAMBDA")?;
1184 let mut params = Vec::new();
1185 while matches!(self.peek(), Some(Tok::Ident(_)))
1187 && matches!(self.toks.get(self.pos + 1).map(|s| &s.tok), Some(Tok::Op(o)) if o == ",")
1188 {
1189 params.push(self.binding_name()?);
1190 self.bump(); }
1192 let start = self.pos;
1193 let mut level = 0usize;
1195 while let Some(t) = self.peek() {
1196 match t {
1197 Tok::Op(o) if o == "(" => level += 1,
1198 Tok::Op(o) if o == ")" && level == 0 => break,
1199 Tok::Op(o) if o == ")" => level -= 1,
1200 _ => {}
1201 }
1202 self.bump();
1203 }
1204 let end = self.pos;
1205 self.expect(")", "')' to close LAMBDA")?;
1206 if params.is_empty() {
1207 return Err(self.err("LAMBDA needs at least one parameter"));
1208 }
1209 Ok((params, start, end))
1210 }
1211
1212 fn lambda_body(
1214 &mut self,
1215 params: &[String],
1216 args: Vec<Expr>,
1217 start: usize,
1218 end: usize,
1219 ) -> Result<Expr, ParseError> {
1220 if params.len() != args.len() {
1221 return Err(self.err(format!(
1222 "this LAMBDA takes {} argument(s), got {}",
1223 params.len(),
1224 args.len()
1225 )));
1226 }
1227 let saved = self.pos;
1228 let base = self.scope.len();
1229 self.scope.extend(params.iter().cloned().zip(args));
1230 self.pos = start;
1231 let body = self.parse_expr();
1232 let stopped = self.pos;
1233 self.scope.truncate(base);
1234 self.pos = saved;
1235 let body = body?;
1236 if stopped != end {
1237 return Err(ParseError::new(
1238 "unexpected trailing input in the LAMBDA body",
1239 self.toks.get(stopped).map(|s| s.pos),
1240 ));
1241 }
1242 Ok(body)
1243 }
1244
1245 fn parse_named_lambda_call(
1250 &mut self,
1251 id: u32,
1252 kinds: &[crate::LambdaParam],
1253 name: &str,
1254 ) -> Result<Expr, ParseError> {
1255 self.bump(); self.expect("(", "'('")?;
1257 let n = kinds.len();
1258 let mut args = Vec::new();
1259 if !self.eat_op(")") {
1260 loop {
1261 let arg = match kinds.get(args.len()) {
1262 Some(crate::LambdaParam::Category) => {
1263 Expr::Category(self.parse_category_name()?)
1264 }
1265 kind => {
1266 let e = self.parse_expr()?;
1267 if kind == Some(&crate::LambdaParam::Reduced) && !matches!(e, Expr::Ref(..))
1268 {
1269 return Err(self.err(format!(
1270 "{name} reduces argument {} over a category: pass a measure, \
1271 not an expression",
1272 args.len() + 1
1273 )));
1274 }
1275 e
1276 }
1277 };
1278 args.push(arg);
1279 if !self.eat_op(",") {
1280 break;
1281 }
1282 }
1283 self.expect(")", "')' to close the call")?;
1284 }
1285 if args.len() != n {
1286 return Err(self.err(format!("{name} takes {n} argument(s), got {}", args.len())));
1287 }
1288 Ok(Expr::Call(functions::named_lambda(id), args))
1289 }
1290
1291 fn parse_lambda_call(&mut self) -> Result<Expr, ParseError> {
1293 let (params, start, end) = self.parse_lambda_params()?;
1294 if !self.eat_op("(") {
1295 return Err(self.err(
1296 "a LAMBDA must be called on the spot (LAMBDA(x, x + 1)(Price)) or passed \
1297 to MAP, REDUCE or SCAN",
1298 ));
1299 }
1300 let mut args = Vec::new();
1301 loop {
1302 args.push(self.parse_expr()?);
1303 if !self.eat_op(",") {
1304 break;
1305 }
1306 }
1307 self.expect(")", "')' to close the LAMBDA call")?;
1308 self.lambda_body(¶ms, args, start, end)
1309 }
1310
1311 fn parse_map(&mut self) -> Result<Expr, ParseError> {
1314 self.bump(); self.expect("(", "'(' after MAP")?;
1316 let mut args = Vec::new();
1317 while !self.peek_kw("lambda") {
1318 args.push(self.parse_expr()?);
1319 self.expect(",", "',' then a LAMBDA in MAP")?;
1320 }
1321 let (params, start, end) = self.parse_lambda_params()?;
1322 self.expect(")", "')' to close MAP")?;
1323 self.lambda_body(¶ms, args, start, end)
1324 }
1325
1326 fn position(cat: CategoryId) -> Expr {
1328 Expr::Call(functions::POSITION, vec![Expr::Category(cat)])
1329 }
1330
1331 fn parse_sequence(&mut self) -> Result<Expr, ParseError> {
1335 self.bump(); self.expect("(", "'(' after SEQUENCE")?;
1337 let cat = self.parse_category_name()?;
1338 let mut rest = Vec::new();
1339 while self.eat_op(",") {
1340 rest.push(self.parse_expr()?);
1341 }
1342 self.expect(")", "')' to close SEQUENCE")?;
1343 if rest.len() > 2 {
1344 return Err(self.err("SEQUENCE takes (Category, [start], [step])"));
1345 }
1346 let mut it = rest.into_iter();
1347 let one = || Expr::Literal(Value::Number(1.0));
1348 let (start, step) = (it.next().unwrap_or_else(one), it.next().unwrap_or_else(one));
1349 let index = Expr::BinaryOp(
1350 BinaryOp::Sub,
1351 Box::new(Self::position(cat)),
1352 Box::new(one()),
1353 );
1354 Ok(Expr::BinaryOp(
1355 BinaryOp::Add,
1356 Box::new(Expr::BinaryOp(
1357 BinaryOp::Mul,
1358 Box::new(index),
1359 Box::new(step),
1360 )),
1361 Box::new(start),
1362 ))
1363 }
1364
1365 fn parse_makearray(&mut self) -> Result<Expr, ParseError> {
1368 self.bump(); self.expect("(", "'(' after MAKEARRAY")?;
1370 let r = self.parse_category_name()?;
1371 self.expect(",", "',' after the row category")?;
1372 let c = self.parse_category_name()?;
1373 self.expect(",", "',' then LAMBDA(r, c, body)")?;
1374 if !self.peek_kw("lambda") {
1375 return Err(self.err("MAKEARRAY needs LAMBDA(r, c, body)"));
1376 }
1377 let (params, start, end) = self.parse_lambda_params()?;
1378 self.expect(")", "')' to close MAKEARRAY")?;
1379 self.lambda_body(
1380 ¶ms,
1381 vec![Self::position(r), Self::position(c)],
1382 start,
1383 end,
1384 )
1385 }
1386
1387 fn parse_byrow(&mut self, name: &str) -> Result<Expr, ParseError> {
1393 let name = name.to_ascii_uppercase();
1394 self.bump(); self.expect("(", "'(' after the function name")?;
1396 let x = self.parse_measure_ref()?;
1397 if !self.peek_kw("over") {
1398 return Err(self.err(format!("{name} needs x OVER Category (the row's category)")));
1399 }
1400 self.bump();
1401 let cat = self.parse_category_name()?;
1402 let row = match x {
1403 Expr::Ref(id, mut spec) => {
1404 spec.over = vec![cat];
1405 Expr::Ref(id, spec)
1406 }
1407 _ => return Err(self.err(format!("{name} takes a measure reference"))),
1408 };
1409 self.expect(",", "',' then LAMBDA(row, body)")?;
1410 if !self.peek_kw("lambda") {
1411 return Err(self.err(format!("{name} needs LAMBDA(row, body)")));
1412 }
1413 let (params, start, end) = self.parse_lambda_params()?;
1414 self.expect(")", "')' to close the call")?;
1415 let body = self.lambda_body(¶ms, vec![row], start, end)?;
1416 fn bare(e: &Expr) -> bool {
1417 match e {
1418 Expr::Ref(_, s) => !s.over.is_empty(),
1419 Expr::Call(f, _) if functions::by_id(*f).is_some_and(|d| d.kind.reduces()) => false,
1420 Expr::Call(_, a) => a.iter().any(bare),
1421 Expr::UnaryOp(_, a) => bare(a),
1422 Expr::BinaryOp(_, a, b) => bare(a) || bare(b),
1423 _ => false,
1424 }
1425 }
1426 if bare(&body) {
1427 return Err(self.err(format!(
1428 "{name}'s LAMBDA must reduce its row to one value (SUM(row), MAX(row), …)"
1429 )));
1430 }
1431 Ok(body)
1432 }
1433
1434 fn parse_fold(&mut self, name: &str) -> Result<Expr, ParseError> {
1439 let def = functions::callable(name).expect("REDUCE/SCAN are registered");
1440 self.bump(); self.expect("(", "'(' after the function name")?;
1442 let init = self.parse_expr()?;
1443 self.expect(",", "',' after the initial value")?;
1444 let x = self.parse_measure_ref()?;
1445 if !self.peek_kw("over") {
1446 return Err(self.err(format!(
1447 "{} folds a measure: write x OVER Category",
1448 def.name
1449 )));
1450 }
1451 self.bump();
1452 let cat = self.parse_category_name()?;
1453 let x = match x {
1454 Expr::Ref(id, mut spec) => {
1455 spec.over = vec![cat];
1456 Expr::Ref(id, spec)
1457 }
1458 _ => return Err(self.err(format!("{} folds a measure reference", def.name))),
1459 };
1460 self.expect(",", "',' then a LAMBDA(accumulator, value, body)")?;
1461 if !self.peek_kw("lambda") {
1462 return Err(self.err(format!(
1463 "{} needs LAMBDA(accumulator, value, body)",
1464 def.name
1465 )));
1466 }
1467 let (params, start, end) = self.parse_lambda_params()?;
1468 self.expect(")", "')' to close the fold")?;
1469 if params.len() != 2 {
1470 return Err(self.err(format!(
1471 "{}'s LAMBDA takes (accumulator, value), got {} parameter(s)",
1472 def.name,
1473 params.len()
1474 )));
1475 }
1476 let k = self.lambdas;
1477 self.lambdas += 1;
1478 let slot = |i| Expr::Ref(functions::lambda_slot(k, i), DimensionSpec::default());
1479 let body = self.lambda_body(¶ms, vec![slot(0), slot(1)], start, end)?;
1480 Ok(Expr::Call(def.id, vec![init, x, body]))
1481 }
1482
1483 fn parse_fixpoint(&mut self) -> Result<Expr, ParseError> {
1488 let def = functions::callable("FIXPOINT").expect("FIXPOINT is registered");
1489 self.bump(); self.expect("(", "'(' after FIXPOINT")?;
1491 let k = self.lambdas;
1492 self.lambdas += 1;
1493 let init = self.parse_expr()?;
1494 self.expect(",", "',' then LAMBDA(r, body)")?;
1495 if !self.peek_kw("lambda") {
1496 return Err(self.err("FIXPOINT needs LAMBDA(r, body)"));
1497 }
1498 let (params, start, end) = self.parse_lambda_params()?;
1499 if params.len() != 1 {
1500 return Err(self.err(format!(
1501 "FIXPOINT's LAMBDA takes one parameter (the iterate), got {}",
1502 params.len()
1503 )));
1504 }
1505 let r = Expr::Ref(functions::lambda_slot(k, 0), DimensionSpec::default());
1506 let body = self.lambda_body(¶ms, vec![r], start, end)?;
1507 let mut args = vec![init, body];
1508 while args.len() < 4 && self.eat_op(",") {
1509 args.push(self.parse_expr()?);
1510 }
1511 self.expect(")", "')' to close FIXPOINT")?;
1512 Ok(Expr::Call(def.id, args))
1513 }
1514
1515 fn parse_measure_ref(&mut self) -> Result<Expr, ParseError> {
1517 let (name, pos) = self.parse_name("a measure name")?;
1518 let id = self.measure_id(&name, pos)?;
1519 let mut spec = DimensionSpec::default();
1520 if self.eat_op("[") {
1521 spec.by.push(self.parse_category_name()?);
1522 while self.eat_op(",") {
1523 spec.by.push(self.parse_category_name()?);
1524 }
1525 if !self.eat_op("]") {
1526 return Err(self.err("expected ']' to close dimension list"));
1527 }
1528 }
1529 Ok(Expr::Ref(id, spec))
1530 }
1531
1532 fn parse_category_name(&mut self) -> Result<CategoryId, ParseError> {
1533 let (name, pos) = self.parse_name("a category name")?;
1534 self.category_id(&name, pos)
1535 }
1536}
1537
1538pub fn parse_formula(model: &Model, text: &str) -> Result<FormulaText, ParseError> {
1546 let toks = tokenize(text)?;
1547 let (target, rhs_start) = parse_lhs(&toks)?;
1548 let mut p = Parser {
1549 toks: &toks[rhs_start..],
1550 pos: 0,
1551 model,
1552 depth: 0,
1553 scope: Vec::new(),
1554 lambdas: 0,
1555 };
1556 let expr = p.parse_expr()?;
1557 if p.peek().is_some() {
1558 return Err(p.err("unexpected trailing input"));
1559 }
1560 Ok(FormulaText {
1561 target,
1562 formula: Formula::new(expr),
1563 })
1564}
1565
1566fn parse_lhs(toks: &[Spanned]) -> Result<(Name, usize), ParseError> {
1570 if toks.is_empty() {
1571 return Err(ParseError::new("empty formula", None));
1572 }
1573 let target = match &toks[0].tok {
1574 Tok::Ident(w) | Tok::QIdent(w) => Name(w.clone()),
1576 _ => {
1577 return Err(ParseError::new(
1578 "formula must start with a target measure name",
1579 Some(toks[0].pos),
1580 ))
1581 }
1582 };
1583 let mut idx = 1;
1584 if matches!(toks.get(idx).map(|s| &s.tok), Some(Tok::Op(o)) if o == "[") {
1585 idx += 1;
1586 while !matches!(toks.get(idx).map(|s| &s.tok), Some(Tok::Op(o)) if o == "]") {
1587 if toks.get(idx).is_none() {
1588 return Err(ParseError::new(
1589 "unterminated '[' in target dimension list",
1590 Some(toks[0].pos),
1591 ));
1592 }
1593 idx += 1;
1594 }
1595 idx += 1; }
1597 if !matches!(toks.get(idx).map(|s| &s.tok), Some(Tok::Op(o)) if o == "=") {
1598 return Err(ParseError::new(
1599 "expected '=' after target measure name",
1600 toks.get(idx).map(|s| s.pos),
1601 ));
1602 }
1603 Ok((target, idx + 1))
1604}
1605
1606pub fn parse_definition(model: &Model, text: &str) -> Result<Definition, ParseError> {
1612 let toks = tokenize(text)?;
1613 let (target, rhs) = parse_lhs(&toks)?;
1614 if let Some(Tok::Ident(head)) = toks.get(rhs).map(|s| &s.tok) {
1616 let is_open = matches!(toks.get(rhs + 1).map(|s| &s.tok), Some(Tok::Op(o)) if o == "(");
1617 if is_open && head.eq_ignore_ascii_case("sql") {
1618 return parse_sql_form(&toks, rhs, target);
1619 }
1620 if is_open && head.eq_ignore_ascii_case("call") {
1621 return parse_call_form(&toks, rhs, target);
1622 }
1623 if is_open && head.eq_ignore_ascii_case("lambda") {
1624 return parse_lambda_form(model, &toks, rhs, target);
1625 }
1626 }
1627 let mut p = Parser {
1629 toks: &toks[rhs..],
1630 pos: 0,
1631 model,
1632 depth: 0,
1633 scope: Vec::new(),
1634 lambdas: 0,
1635 };
1636 let expr = p.parse_expr()?;
1637 if p.peek().is_some() {
1638 return Err(p.err("unexpected trailing input"));
1639 }
1640 Ok(Definition::Formula(FormulaText {
1641 target,
1642 formula: Formula::new(expr),
1643 }))
1644}
1645
1646fn parse_lambda_form(
1650 model: &Model,
1651 toks: &[Spanned],
1652 rhs: usize,
1653 target: Name,
1654) -> Result<Definition, ParseError> {
1655 if functions::callable(&target.0).is_some() {
1656 return Err(ParseError::new(
1657 format!(
1658 "{} is a built-in function; name the LAMBDA differently",
1659 target.0
1660 ),
1661 None,
1662 ));
1663 }
1664 let mut m = model.clone();
1666 let own = m.lambda_by_name(&target.0).map(|(id, _)| id);
1667 if let Some(id) = own {
1668 m.lambdas.remove(&id);
1669 }
1670 let mut p = Parser {
1671 toks: &toks[rhs..],
1672 pos: 0,
1673 model: &m,
1674 depth: 0,
1675 scope: Vec::new(),
1676 lambdas: 0,
1677 };
1678 let (params, start, end) = p.parse_lambda_params()?;
1679 if p.peek().is_some() {
1680 return Err(p.err("unexpected input after the LAMBDA (a named LAMBDA is not called here)"));
1681 }
1682 let args = (0..params.len())
1683 .map(|i| Expr::Ref(functions::lambda_param(i), DimensionSpec::default()))
1684 .collect();
1685 let body = p.lambda_body(¶ms, args, start, end)?;
1686 let refs = body.referenced_measures();
1687 if refs
1688 .iter()
1689 .any(|r| functions::lambda_param_of(*r).is_none())
1690 {
1691 return Err(ParseError::new(
1692 "a named LAMBDA reads only its parameters: pass measures as arguments",
1693 None,
1694 ));
1695 }
1696 if refs.iter().any(|r| functions::lambda_slot_of(*r).is_some()) {
1697 return Err(ParseError::new(
1698 "a named LAMBDA cannot hold SCAN or REDUCE",
1699 None,
1700 ));
1701 }
1702 let lambda = crate::NamedLambda {
1703 name: target.clone(),
1704 params,
1705 body,
1706 };
1707 lambda.param_kinds().map_err(|e| ParseError::new(e, None))?;
1708 Ok(Definition::Lambda(LambdaText {
1709 name: target,
1710 lambda,
1711 }))
1712}
1713
1714fn parse_sql_form(toks: &[Spanned], rhs: usize, target: Name) -> Result<Definition, ParseError> {
1716 let query = match toks.get(rhs + 2).map(|s| &s.tok) {
1718 Some(Tok::Str(s)) => s.clone(),
1719 _ => {
1720 return Err(ParseError::new(
1721 "SQL(...) takes a single quoted query string",
1722 toks.get(rhs + 2).map(|s| s.pos),
1723 ))
1724 }
1725 };
1726 match toks.get(rhs + 3).map(|s| &s.tok) {
1727 Some(Tok::Op(o)) if o == ")" => {}
1728 _ => {
1729 return Err(ParseError::new(
1730 "expected ')' to close SQL(...)",
1731 toks.get(rhs + 3).map(|s| s.pos),
1732 ))
1733 }
1734 }
1735 if toks.get(rhs + 4).is_some() {
1736 return Err(ParseError::new(
1737 "unexpected trailing input after SQL(...)",
1738 toks.get(rhs + 4).map(|s| s.pos),
1739 ));
1740 }
1741 Ok(Definition::Sql { target, query })
1742}
1743
1744fn parse_call_form(toks: &[Spanned], rhs: usize, target: Name) -> Result<Definition, ParseError> {
1747 let func = match toks.get(rhs + 2).map(|s| &s.tok) {
1748 Some(Tok::Ident(w)) => w.clone(),
1749 _ => {
1750 return Err(ParseError::new(
1751 "CALL(...) takes a function name then argument measures",
1752 toks.get(rhs + 2).map(|s| s.pos),
1753 ))
1754 }
1755 };
1756 let mut i = rhs + 3;
1758 let mut args = Vec::new();
1759 loop {
1760 match toks.get(i).map(|s| &s.tok) {
1761 Some(Tok::Op(o)) if o == ")" => {
1762 i += 1;
1763 break;
1764 }
1765 Some(Tok::Op(o)) if o == "," => {
1766 i += 1;
1767 match toks.get(i).map(|s| &s.tok) {
1768 Some(Tok::Ident(w) | Tok::QIdent(w)) => {
1769 args.push(Name(w.clone()));
1770 i += 1;
1771 }
1772 _ => {
1773 return Err(ParseError::new(
1774 "expected an argument measure name after ',' in CALL(...)",
1775 toks.get(i).map(|s| s.pos),
1776 ))
1777 }
1778 }
1779 }
1780 other => {
1781 return Err(ParseError::new(
1782 if other.is_none() {
1783 "unterminated CALL(...)"
1784 } else {
1785 "expected ',' or ')' in CALL(...)"
1786 },
1787 toks.get(i).map(|s| s.pos),
1788 ))
1789 }
1790 }
1791 }
1792 if toks.get(i).is_some() {
1793 return Err(ParseError::new(
1794 "unexpected trailing input after CALL(...)",
1795 toks.get(i).map(|s| s.pos),
1796 ));
1797 }
1798 Ok(Definition::Call { target, func, args })
1799}
1800
1801pub fn parse_expr(model: &Model, text: &str) -> Result<Formula, ParseError> {
1803 let toks = tokenize(text)?;
1804 if toks.is_empty() {
1805 return Err(ParseError::new("empty expression", None));
1806 }
1807 let mut p = Parser {
1808 toks: &toks,
1809 pos: 0,
1810 model,
1811 depth: 0,
1812 scope: Vec::new(),
1813 lambdas: 0,
1814 };
1815 let expr = p.parse_expr()?;
1816 if p.peek().is_some() {
1817 return Err(p.err("unexpected trailing input"));
1818 }
1819 Ok(Formula::new(expr))
1820}
1821
1822#[cfg(test)]
1827#[cfg_attr(coverage_nightly, coverage(off))]
1828mod tests {
1829 use super::*;
1830 use crate::{Measure, MeasureKind, Name, ValueType};
1831
1832 const TIME: CategoryId = CategoryId(1);
1833 const PRODUCT: CategoryId = CategoryId(2);
1834 const PRICE: MeasureId = MeasureId(100);
1835 const QUANTITY: MeasureId = MeasureId(101);
1836 const REVENUE: MeasureId = MeasureId(102);
1837 const COST: MeasureId = MeasureId(103);
1838
1839 fn fixture() -> Model {
1840 let mut m = Model::new();
1841 m.add_category(TIME, "Time");
1842 m.add_category(PRODUCT, "Product");
1843 for (id, name, cats, vt) in [
1844 (PRICE, "Price", vec![PRODUCT], ValueType::Number),
1845 (QUANTITY, "Quantity", vec![TIME, PRODUCT], ValueType::Number),
1846 (REVENUE, "Revenue", vec![TIME, PRODUCT], ValueType::Number),
1847 (COST, "Cost", vec![TIME, PRODUCT], ValueType::Number),
1848 ] {
1849 m.add_measure(Measure {
1850 id,
1851 name: Name(name.into()),
1852 value_type: vt,
1853 categories: cats,
1854 kind: MeasureKind::Input,
1855 description: None,
1856 });
1857 }
1858 m
1859 }
1860
1861 fn refr(id: MeasureId) -> Expr {
1862 Expr::Ref(id, DimensionSpec::default())
1863 }
1864
1865 #[test]
1868 fn over_clauses_category_args_and_their_errors() {
1869 let m = fixture();
1870 let p = |s: &str| parse_expr(&m, s).map(|f| f.expr);
1871 let call = |f: u32, a: Vec<Expr>| Expr::Call(FuncId(f), a);
1872 let over = |id, cats: Vec<CategoryId>| {
1873 Expr::Ref(
1874 id,
1875 DimensionSpec {
1876 over: cats,
1877 ..Default::default()
1878 },
1879 )
1880 };
1881 assert_eq!(
1883 p("SUM(Quantity OVER Time, Product)"),
1884 Ok(call(1, vec![over(QUANTITY, vec![TIME, PRODUCT])]))
1885 );
1886 assert_eq!(
1887 p("TEXTJOIN(\"-\", TRUE, Quantity OVER Time)"),
1888 Ok(call(
1889 909,
1890 vec![
1891 Expr::Literal(Value::Text("-".into())),
1892 Expr::Literal(Value::Boolean(true)),
1893 over(QUANTITY, vec![TIME])
1894 ]
1895 ))
1896 );
1897 assert_eq!(
1899 p("SUM(Quantity OVER ALL)"),
1900 Ok(call(1, vec![over(QUANTITY, vec![TIME, PRODUCT])]))
1901 );
1902 assert_eq!(
1903 p("SUM(Quantity OVER ALL EXCEPT Product)"),
1904 Ok(call(1, vec![over(QUANTITY, vec![TIME])]))
1905 );
1906 assert_eq!(
1907 p("SUM(Quantity OVER ALL EXCEPT Product, Time)"),
1908 Ok(call(1, vec![over(QUANTITY, vec![])]))
1909 );
1910 let w = p("SUM(Quantity OVER Time WHERE Quantity > 1)").unwrap();
1912 assert!(
1913 matches!(&w, Expr::Call(_, a) if matches!(&a[1], Expr::Call(id, _) if *id == functions::WHERE))
1914 );
1915 for (src, from, to) in [
1916 (
1917 "SUM(Quantity OVER Time[FIRST..THIS])",
1918 Value::Text("FIRST".into()),
1919 Value::Number(0.0),
1920 ),
1921 (
1922 "SUM(Quantity OVER Time[THIS-2..THIS+1])",
1923 Value::Number(-2.0),
1924 Value::Number(1.0),
1925 ),
1926 (
1927 "SUM(Quantity OVER Time[THIS..LAST])",
1928 Value::Number(0.0),
1929 Value::Text("LAST".into()),
1930 ),
1931 ] {
1932 let e = p(src).unwrap();
1933 let Expr::Call(_, a) = e else { panic!() };
1934 assert_eq!(
1935 a[1],
1936 Expr::Call(
1937 functions::WINDOW,
1938 vec![Expr::Literal(from), Expr::Literal(to)]
1939 ),
1940 "{src}"
1941 );
1942 }
1943 assert_eq!(
1947 p("POSITION(Time)"),
1948 Ok(call(900, vec![Expr::Category(TIME)]))
1949 );
1950 assert_eq!(
1951 p("@ITEM(Product)"),
1952 Ok(call(901, vec![Expr::Category(PRODUCT)]))
1953 );
1954 assert_eq!(
1955 p("@itemcount(Time)"),
1956 Ok(call(903, vec![Expr::Category(TIME)]))
1957 );
1958 assert_eq!(
1959 p("PREV(Quantity, Time, 2, 0)"),
1960 Ok(call(
1961 904,
1962 vec![
1963 refr(QUANTITY),
1964 Expr::Category(TIME),
1965 Expr::Literal(Value::Number(2.0)),
1966 Expr::Literal(Value::Number(0.0))
1967 ]
1968 ))
1969 );
1970 assert_eq!(p("TRUE()"), Ok(call(39, vec![])));
1971 assert_eq!(p("ISO.CEILING(Price)"), Ok(call(210, vec![refr(PRICE)])));
1972 let joined = |src: &str| tokenize(src).unwrap().first().map(|t| t.tok.clone());
1974 assert_eq!(
1975 joined("NORM.S.DIST(1)"),
1976 Some(Tok::Ident("NORM.S.DIST".into()))
1977 );
1978 assert_eq!(
1979 joined("T.DIST.2T(1, 2)"),
1980 Some(Tok::Ident("T.DIST.2T".into()))
1981 );
1982 assert_eq!(
1983 joined("FLOOR.MATH (1)"),
1984 Some(Tok::Ident("FLOOR.MATH".into()))
1985 );
1986 assert_eq!(joined("Sheet.Price"), Some(Tok::Ident("Sheet".into())));
1987 for (src, want) in [
1989 ("SUM(Quantity OVER Time OVER Product)", "expected ')'"),
1990 (
1991 "SUM(Quantity OVER Time, Quantity OVER Product)",
1992 "only one OVER",
1993 ),
1994 ("SUM(Quantity OVER Time[FIRST.THIS])", "'..'"),
1995 ("SUM(Quantity OVER Time[FIRST..THIS)", "']'"),
1996 ("SUM(Quantity OVER Time[NOW..THIS])", "FIRST, LAST or THIS"),
1997 ("SUM(Quantity OVER Time[1..THIS])", "FIRST, LAST or THIS"),
1998 ("SUM(Quantity OVER Time[THIS+x..THIS])", "whole number"),
1999 ("SUM(Quantity OVER Time[THIS-1.5..THIS])", "whole number"),
2000 ("ABS(Price OVER Time)", "does not aggregate"),
2001 ("SUM(Price * 2 OVER Time)", "measure reference"),
2002 ("POSITION(Price)", "unknown category"),
2003 ("SUM(Quantity OVER Nope)", "unknown category"),
2004 ("SUM(Quantity OVER ALL EXCEPT Nope)", "unknown category"),
2005 ("TEXTJOIN(\"-\", Quantity OVER Time)", "takes"),
2006 ] {
2007 let e = p(src).unwrap_err();
2008 assert!(e.message.contains(want), "{src}: {}", e.message);
2009 }
2010 assert!(p("@ + 1").is_err());
2012 assert!(p("@1").is_err());
2013 assert!(p("FLOOR.NOPE(Price)").is_err());
2015 assert!(p("SUM(Quantity OVER ALL(Time))").is_err());
2017 }
2018
2019 #[test]
2025 fn named_lambda_category_parameters() {
2026 use crate::LambdaParam;
2027 let mut m = fixture();
2028 let def = |m: &Model, t: &str| match parse_definition(m, t) {
2029 Ok(Definition::Lambda(l)) => l.lambda,
2030 other => panic!("{t}: {other:?}"),
2031 };
2032 let p = |m: &Model, t: &str| parse_expr(m, t).unwrap().expr;
2033 let l = def(&m, "LOGNORM = LAMBDA(x, c, LN1P(x) / LN1P(MAX(x OVER c)))");
2034 assert_eq!(
2035 l.param_kinds(),
2036 Ok(vec![LambdaParam::Reduced, LambdaParam::Category])
2037 );
2038 m.lambdas.insert(1, l);
2039 let call = p(&m, "LOGNORM(Quantity, Time)");
2040 assert_eq!(
2041 call,
2042 Expr::Call(
2043 functions::named_lambda(1),
2044 vec![refr(QUANTITY), Expr::Category(TIME)]
2045 )
2046 );
2047 assert_eq!(
2048 m.expand_lambdas(&call),
2049 Ok(p(&m, "LN1P(Quantity) / LN1P(MAX(Quantity OVER Time))"))
2050 );
2051 let at = def(&m, "AT = LAMBDA(v, k, LOGNORM(v, k) * POSITION(k))");
2052 assert_eq!(
2053 at.param_kinds(),
2054 Ok(vec![LambdaParam::Value, LambdaParam::Category])
2055 );
2056 m.lambdas.insert(2, at);
2057 assert_eq!(
2058 m.expand_lambdas(&p(&m, "AT(Quantity, Product)")),
2059 Ok(p(
2060 &m,
2061 "LN1P(Quantity) / LN1P(MAX(Quantity OVER Product)) * POSITION(Product)"
2062 ))
2063 );
2064 for (text, why) in [
2065 ("LOGNORM(Quantity * 2, Time)", "pass a measure"),
2066 ("LOGNORM(Quantity, Nope)", "unknown category: Nope"),
2067 ("LOGNORM(Quantity)", "takes 2"),
2068 ] {
2069 let e = parse_expr(&m, text).unwrap_err();
2070 assert!(e.message.contains(why), "{text}: {}", e.message);
2071 }
2072 let e = parse_definition(&m, "Bad = LAMBDA(x, c, c + SUM(x OVER c))").unwrap_err();
2073 assert!(e.message.contains("both as a value and as a category"));
2074 let call = |args| Expr::Call(functions::named_lambda(1), args);
2076 let e = m.expand_lambdas(&call(vec![refr(QUANTITY), refr(PRICE)]));
2077 assert!(e
2078 .unwrap_err()
2079 .contains("LOGNORM: argument 2 must be a category"));
2080 let one = Expr::Literal(Value::Number(1.0));
2081 let e = m.expand_lambdas(&call(vec![one, Expr::Category(TIME)]));
2082 assert!(e.unwrap_err().contains("must be a measure"));
2083 }
2084
2085 #[test]
2086 fn let_and_lambda() {
2087 let m = fixture();
2088 let p = |s: &str| parse_expr(&m, s).map(|f| f.expr);
2089 assert_eq!(p("LET(x, Price, x + x)"), p("Price + Price"));
2090 assert_eq!(p("LET(x, Price, y, x * 2, y - x)"), p("Price * 2 - Price"));
2091 assert_eq!(p("LET(Price, 3, Price) + Price"), p("3 + Price"));
2093 assert_eq!(
2094 p("LAMBDA(a, a * a)(Price + 1)"),
2095 p("(Price + 1) * (Price + 1)")
2096 );
2097 assert_eq!(
2098 p("MAP(Price, Cost, LAMBDA(a, b, a - b))"),
2099 p("Price - Cost")
2100 );
2101 let fold = p("REDUCE(0, Quantity OVER Time, LAMBDA(acc, v, acc + v))").unwrap();
2102 let Expr::Call(_, args) = fold else { panic!() };
2103 assert_eq!(
2104 args[2],
2105 Expr::BinaryOp(
2106 BinaryOp::Add,
2107 Box::new(Expr::Ref(
2108 functions::lambda_slot(0, 0),
2109 DimensionSpec::default()
2110 )),
2111 Box::new(Expr::Ref(
2112 functions::lambda_slot(0, 1),
2113 DimensionSpec::default()
2114 )),
2115 )
2116 );
2117 assert_eq!(
2118 functions::lambda_slot_of(functions::lambda_slot(3, 1)),
2119 Some((3, 1))
2120 );
2121 assert_eq!(functions::lambda_slot_of(MeasureId(5)), None);
2122 let r = Expr::Ref(functions::lambda_slot(0, 0), DimensionSpec::default());
2124 let fix = functions::callable("FIXPOINT").unwrap().id;
2125 assert_eq!(
2126 p("FIXPOINT(Price, LAMBDA(r, r / 2 + 1), 50)"),
2127 Ok(Expr::Call(
2128 fix,
2129 vec![
2130 refr(PRICE),
2131 Expr::BinaryOp(
2132 BinaryOp::Add,
2133 Box::new(Expr::BinaryOp(
2134 BinaryOp::Div,
2135 Box::new(r.clone()),
2136 Box::new(Expr::Literal(Value::Number(2.0)))
2137 )),
2138 Box::new(Expr::Literal(Value::Number(1.0)))
2139 ),
2140 Expr::Literal(Value::Number(50.0)),
2141 ]
2142 ))
2143 );
2144 assert_eq!(
2145 p("FIXPOINT(1, LAMBDA(x, x))"),
2146 Ok(Expr::Call(fix, vec![Expr::Literal(Value::Number(1.0)), r]))
2147 );
2148 for (src, want) in [
2149 ("LET(x, Price)", "expected"),
2150 ("LET(1, Price, 2)", "a name to bind"),
2151 ("LET(x Price, x)", "',' after a LET name"),
2152 ("LET(x, Price, x", "')' to close LET"),
2153 ("LAMBDA(x, x)", "called on the spot"),
2154 ("LAMBDA(x)(1)", "at least one parameter"),
2155 ("LAMBDA(x, x + 1)(1, 2)", "takes 1 argument"),
2156 ("LAMBDA(x, x +)(1)", ""),
2157 ("LAMBDA(x, x", "')' to close LAMBDA"),
2158 ("MAP(Price LAMBDA(v, v))", "',' then a LAMBDA"),
2159 ("MAP(Price, LAMBDA(v, v)", "')' to close MAP"),
2160 ("SCAN(0, Quantity, LAMBDA(a, v, a))", "OVER"),
2161 ("SCAN(0, Quantity OVER Time, 1)", "needs LAMBDA"),
2162 (
2163 "SCAN(0, Quantity OVER Time LAMBDA(a, v, a))",
2164 "',' then a LAMBDA",
2165 ),
2166 (
2167 "SCAN(0, Quantity OVER Time, LAMBDA(v, v))",
2168 "takes (accumulator, value)",
2169 ),
2170 (
2171 "SCAN(0 Quantity OVER Time, LAMBDA(a, v, a))",
2172 "',' after the initial",
2173 ),
2174 (
2175 "SCAN(0, Quantity OVER Time, LAMBDA(a, v, a)",
2176 "')' to close the fold",
2177 ),
2178 ("SCAN 1", "unknown measure"),
2179 ("FIXPOINT(0, 1)", "needs LAMBDA"),
2180 ("FIXPOINT(0 LAMBDA(r, r))", "',' then LAMBDA"),
2181 ("FIXPOINT(0, LAMBDA(a, b, a))", "one parameter"),
2182 (
2183 "FIXPOINT(0, LAMBDA(r, r), 5, 0, 1)",
2184 "')' to close FIXPOINT",
2185 ),
2186 ("LET 1", "unknown measure"),
2187 ] {
2188 let e = p(src).unwrap_err();
2189 assert!(e.message.contains(want), "{src}: {}", e.message);
2190 }
2191 }
2192
2193 #[test]
2196 fn unknown_function_suggests() {
2197 let m = fixture();
2198 let e = parse_expr(&m, "1 + PMTT(1, 2, 3)").unwrap_err();
2199 assert!(
2200 e.message
2201 .starts_with("unknown function: PMTT; did you mean PMT"),
2202 "{}",
2203 e.message
2204 );
2205 assert_eq!(e.position, Some(4));
2206 let e = parse_expr(&m, "Qqqqqqqqqq(1)").unwrap_err();
2207 assert_eq!(e.message, "unknown function: Qqqqqqqqqq");
2208 }
2209
2210 #[test]
2211 fn parses_assignment_and_multiplication() {
2212 let m = fixture();
2213 let f = parse_formula(&m, "Revenue = Price * Quantity").unwrap();
2214 assert_eq!(f.target, Name("Revenue".into()));
2215 assert_eq!(
2216 f.formula.expr,
2217 Expr::BinaryOp(
2218 BinaryOp::Mul,
2219 Box::new(refr(PRICE)),
2220 Box::new(refr(QUANTITY))
2221 )
2222 );
2223 }
2224
2225 #[test]
2226 fn precedence_mul_over_add() {
2227 let m = fixture();
2229 let f = parse_expr(&m, "Price + Quantity * Revenue").unwrap();
2230 assert_eq!(
2231 f.expr,
2232 Expr::BinaryOp(
2233 BinaryOp::Add,
2234 Box::new(refr(PRICE)),
2235 Box::new(Expr::BinaryOp(
2236 BinaryOp::Mul,
2237 Box::new(refr(QUANTITY)),
2238 Box::new(refr(REVENUE)),
2239 )),
2240 )
2241 );
2242 }
2243
2244 #[test]
2245 fn parens_group() {
2246 let m = fixture();
2248 let f = parse_expr(&m, "(Price + Quantity) * Revenue").unwrap();
2249 assert_eq!(
2250 f.expr,
2251 Expr::BinaryOp(
2252 BinaryOp::Mul,
2253 Box::new(Expr::BinaryOp(
2254 BinaryOp::Add,
2255 Box::new(refr(PRICE)),
2256 Box::new(refr(QUANTITY)),
2257 )),
2258 Box::new(refr(REVENUE)),
2259 )
2260 );
2261 }
2262
2263 #[test]
2264 fn subtraction() {
2265 let m = fixture();
2266 let f = parse_formula(&m, "Profit = Revenue - Cost").unwrap();
2267 assert_eq!(f.target, Name("Profit".into()));
2268 assert_eq!(
2269 f.formula.expr,
2270 Expr::BinaryOp(BinaryOp::Sub, Box::new(refr(REVENUE)), Box::new(refr(COST)))
2271 );
2272 }
2273
2274 #[test]
2275 fn aggregation_sum_over_time() {
2276 let m = fixture();
2278 let f = parse_formula(&m, "TotalRevenue[Product] = SUM(Revenue OVER Time)").unwrap();
2279 assert_eq!(f.target, Name("TotalRevenue".into()));
2280 let expected = Expr::Call(
2281 FUNC_SUM,
2282 vec![Expr::Ref(
2283 REVENUE,
2284 DimensionSpec {
2285 over: vec![TIME],
2286 by: vec![],
2287 except: vec![],
2288 },
2289 )],
2290 );
2291 assert_eq!(f.formula.expr, expected);
2292 }
2293
2294 #[test]
2295 fn dim_list_sets_by() {
2296 let m = fixture();
2297 let f = parse_expr(&m, "Quantity[Time, Product]").unwrap();
2298 assert_eq!(
2299 f.expr,
2300 Expr::Ref(
2301 QUANTITY,
2302 DimensionSpec {
2303 by: vec![TIME, PRODUCT],
2304 over: vec![],
2305 except: vec![],
2306 }
2307 )
2308 );
2309 }
2310
2311 #[test]
2315 fn power_concat_and_percent_precedence() {
2316 let m = fixture();
2317 let p = |s: &str| parse_expr(&m, s).unwrap().expr;
2318 let num = |n: f64| Expr::Literal(Value::Number(n));
2319 let pow = |a, b| Expr::BinaryOp(BinaryOp::Pow, Box::new(a), Box::new(b));
2320 let neg = |a| Expr::UnaryOp(UnaryOp::Neg, Box::new(a));
2321 assert_eq!(p("-2^2"), neg(pow(num(2.0), num(2.0))));
2322 assert_eq!(p("2^3^2"), pow(num(2.0), pow(num(3.0), num(2.0))));
2323 assert_eq!(p("2^-1"), pow(num(2.0), neg(num(1.0))));
2324 assert_eq!(p("(-2)^2"), pow(neg(num(2.0)), num(2.0)));
2325 assert_eq!(p("2*3^2"), parse_expr(&m, "2*(3^2)").unwrap().expr);
2326 assert_eq!(p("50%"), num(0.5));
2327 assert_eq!(p("Price%"), parse_expr(&m, "Price / 100").unwrap().expr);
2328 assert_eq!(p("2^50%"), pow(num(2.0), num(0.5)));
2329 assert_eq!(
2330 p("Price + 1 & \"x\""),
2331 parse_expr(&m, "(Price + 1) & \"x\"").unwrap().expr
2332 );
2333 assert_eq!(
2334 p("\"a\" & \"b\" == \"ab\""),
2335 parse_expr(&m, "(\"a\" & \"b\") == \"ab\"").unwrap().expr
2336 );
2337 assert_eq!(p("--Price"), neg(neg(refr(PRICE))));
2338 assert!(parse_expr(&m, &format!("{}1", "-".repeat(10_000))).is_err());
2339 assert!(parse_expr(&m, "2^").is_err());
2340 }
2341
2342 #[test]
2346 fn not_binds_looser_than_comparison() {
2347 let m = fixture();
2348 assert_eq!(
2349 parse_expr(&m, "NOT Price > 15").unwrap(),
2350 parse_expr(&m, "NOT (Price > 15)").unwrap()
2351 );
2352 assert_eq!(
2353 parse_expr(&m, "NOT Price > 15 AND Price < 20").unwrap(),
2354 parse_expr(&m, "(NOT (Price > 15)) AND Price < 20").unwrap()
2355 );
2356 assert_eq!(
2357 parse_expr(&m, "NOT NOT Price > 15").unwrap(),
2358 parse_expr(&m, "NOT (NOT (Price > 15))").unwrap()
2359 );
2360 let deep = format!("{}Price > 1", "NOT ".repeat(10_000));
2362 assert!(parse_expr(&m, &deep).is_err());
2363 let ok = format!("{}Price > 1", "NOT ".repeat(50));
2364 assert!(parse_expr(&m, &ok).is_ok());
2365 }
2366
2367 #[test]
2368 fn comparison_and_logical() {
2369 let m = fixture();
2370 let f = parse_expr(&m, "Price > 10").unwrap();
2372 assert_eq!(
2373 f.expr,
2374 Expr::BinaryOp(
2375 BinaryOp::Gt,
2376 Box::new(refr(PRICE)),
2377 Box::new(Expr::Literal(Value::Number(10.0))),
2378 )
2379 );
2380
2381 let f = parse_expr(&m, "NOT (Price > 10)").unwrap();
2383 assert!(matches!(f.expr, Expr::UnaryOp(UnaryOp::Not, _)));
2384
2385 let f = parse_expr(&m, "Price > 10 AND Quantity < 5").unwrap();
2387 assert!(matches!(f.expr, Expr::BinaryOp(BinaryOp::And, _, _)));
2388
2389 let f = parse_expr(&m, "Price == 10").unwrap();
2391 assert!(matches!(f.expr, Expr::BinaryOp(BinaryOp::Eq, _, _)));
2392 assert!(matches!(
2394 parse_expr(&m, "Price <> 10").unwrap().expr,
2395 Expr::BinaryOp(BinaryOp::Ne, _, _)
2396 ));
2397 assert!(matches!(
2398 parse_expr(&m, "Price != 10").unwrap().expr,
2399 Expr::BinaryOp(BinaryOp::Ne, _, _)
2400 ));
2401 }
2402
2403 #[test]
2404 fn literals() {
2405 let m = fixture();
2406 assert_eq!(
2407 parse_expr(&m, "TRUE").unwrap().expr,
2408 Expr::Literal(Value::Boolean(true))
2409 );
2410 assert_eq!(
2411 parse_expr(&m, "false").unwrap().expr,
2412 Expr::Literal(Value::Boolean(false))
2413 );
2414 assert_eq!(
2415 parse_expr(&m, "2.5").unwrap().expr,
2416 Expr::Literal(Value::Number(2.5))
2417 );
2418 assert_eq!(
2419 parse_expr(&m, "\"hello\"").unwrap().expr,
2420 Expr::Literal(Value::Text("hello".into()))
2421 );
2422 }
2423
2424 #[test]
2425 fn unary_neg() {
2426 let m = fixture();
2427 let f = parse_expr(&m, "-Price").unwrap();
2428 assert_eq!(f.expr, Expr::UnaryOp(UnaryOp::Neg, Box::new(refr(PRICE))));
2429 }
2430
2431 #[test]
2432 fn errors_never_panic() {
2433 let m = fixture();
2434 assert!(parse_expr(&m, "Widgets * Price").is_err());
2436 assert!(parse_expr(&m, "Price[Region]").is_err());
2438 assert!(parse_expr(&m, "Price Quantity").is_err());
2440 assert!(parse_expr(&m, "(Price + Quantity").is_err());
2442 assert!(parse_formula(&m, "Revenue Price").is_err());
2444 assert!(parse_expr(&m, "").is_err());
2446 assert!(parse_expr(&m, "\"oops").is_err());
2448 assert!(parse_expr(&m, "Price ^").is_err());
2450 assert!(parse_expr(&m, "Price &").is_err());
2451 }
2452
2453 #[test]
2454 fn error_has_position_and_display() {
2455 let m = fixture();
2456 let e = parse_expr(&m, "Price + Widgets").unwrap_err();
2457 assert!(e.position.is_some());
2458 assert!(e.to_string().contains("unknown measure"));
2459 }
2460
2461 #[test]
2462 fn parses_named_scalar_call() {
2463 let m = fixture();
2464 let f = parse_expr(&m, "ABS(Price)").unwrap();
2466 assert_eq!(f.expr, Expr::Call(FuncId(10), vec![refr(PRICE)]));
2467
2468 let f = parse_expr(&m, "min2(Price, Cost - Revenue)").unwrap();
2470 match f.expr {
2471 Expr::Call(FuncId(20), args) => {
2472 assert_eq!(args.len(), 2);
2473 assert_eq!(args[0], refr(PRICE));
2474 }
2475 other => panic!("expected MIN2 call, got {other:?}"),
2476 }
2477 }
2478
2479 #[test]
2480 fn scalar_call_wrong_arity_errors() {
2481 let m = fixture();
2482 assert!(parse_expr(&m, "ABS(Price, Cost)").is_err()); assert!(parse_expr(&m, "MID(Price)").is_err()); assert!(parse_expr(&m, "ROUND()").is_err()); let e = parse_expr(&m, "ROUND(Price, 1, 2)").unwrap_err();
2486 assert!(
2487 e.message.contains("ROUND(number, [num_digits=0])"),
2488 "{}",
2489 e.message
2490 );
2491 assert_eq!(
2493 parse_expr(&m, "MIN2(Price)").unwrap().expr,
2494 Expr::Call(FuncId(20), vec![refr(PRICE)])
2495 );
2496 assert!(parse_expr(&m, "ABS(Price OVER Time)").is_err());
2498 assert_eq!(
2499 parse_expr(&m, "SUM(Price, Cost)").unwrap().expr,
2500 Expr::Call(FuncId(22), vec![refr(PRICE), refr(COST)])
2501 );
2502 assert_eq!(
2504 parse_expr(&m, "round(Price, 2)").unwrap().expr,
2505 Expr::Call(
2506 FuncId(11),
2507 vec![refr(PRICE), Expr::Literal(Value::Number(2.0))]
2508 )
2509 );
2510 assert_eq!(
2511 parse_expr(&m, "PI()").unwrap().expr,
2512 Expr::Call(FuncId(112), vec![])
2513 );
2514 assert_eq!(
2515 parse_expr(&m, "AVG(Price OVER Time)").unwrap().expr,
2516 parse_expr(&m, "AVERAGE(Price OVER Time)").unwrap().expr
2517 );
2518 }
2519
2520 #[test]
2521 fn scalar_name_without_paren_is_a_measure_ref() {
2522 let m = fixture();
2525 assert!(parse_expr(&m, "ABS").is_err()); }
2527
2528 #[test]
2529 fn definition_falls_through_to_ordinary_formula() {
2530 let m = fixture();
2531 match parse_definition(&m, "Revenue = Price * Quantity").unwrap() {
2532 Definition::Formula(ft) => {
2533 assert_eq!(ft.target, Name("Revenue".into()));
2534 }
2535 other => panic!("expected Formula, got {other:?}"),
2536 }
2537 }
2538
2539 #[test]
2540 fn parses_date_literal() {
2541 let m = fixture();
2542 let f = parse_expr(&m, "#2025-01-15#").unwrap();
2544 match f.expr {
2545 Expr::Literal(Value::DateTime(dt)) => {
2546 assert_eq!(dt.to_rfc3339(), "2025-01-15T00:00:00+00:00");
2547 }
2548 other => panic!("expected date literal, got {other:?}"),
2549 }
2550 let f = parse_expr(&m, "#2025-01-15T09:30:00Z#").unwrap();
2552 assert!(matches!(f.expr, Expr::Literal(Value::DateTime(_))));
2553 assert!(parse_expr(&m, "#not-a-date#").is_err());
2555 assert!(parse_expr(&m, "#2025-01-15").is_err()); }
2557
2558 #[test]
2559 fn definition_parses_sql_form() {
2560 let m = fixture();
2561 match parse_definition(&m, r#"Sales = SQL("select region, amount from sales")"#).unwrap() {
2562 Definition::Sql { target, query } => {
2563 assert_eq!(target, Name("Sales".into()));
2564 assert_eq!(query, "select region, amount from sales");
2565 }
2566 other => panic!("expected Sql, got {other:?}"),
2567 }
2568 assert!(parse_definition(&m, "X = SQL(Price)").is_err());
2570 assert!(parse_definition(&m, r#"X = SQL("a", "b")"#).is_err());
2571 }
2572
2573 #[test]
2574 fn definition_parses_call_form() {
2575 let m = fixture();
2576 match parse_definition(&m, "Now = CALL(now)").unwrap() {
2578 Definition::Call { target, func, args } => {
2579 assert_eq!(target, Name("Now".into()));
2580 assert_eq!(func, "now");
2581 assert!(args.is_empty());
2582 }
2583 other => panic!("expected Call, got {other:?}"),
2584 }
2585 match parse_definition(&m, "H = CALL(hypot, Price, Quantity)").unwrap() {
2587 Definition::Call { func, args, .. } => {
2588 assert_eq!(func, "hypot");
2589 assert_eq!(args, vec![Name("Price".into()), Name("Quantity".into())]);
2590 }
2591 other => panic!("expected Call, got {other:?}"),
2592 }
2593 assert!(parse_definition(&m, "X = CALL(f,)").is_err());
2595 assert!(parse_definition(&m, "X = CALL(f, Price").is_err()); assert!(parse_definition(&m, "X = CALL()").is_err()); }
2598
2599 #[test]
2600 fn call_and_sql_are_only_recognized_as_the_whole_rhs() {
2601 let m = fixture();
2605 assert!(parse_definition(&m, "X = Price + SQL(\"q\")").is_err());
2606 }
2607
2608 #[test]
2609 fn adversarial_inputs_never_panic() {
2610 let m = fixture();
2616 let mut inputs: Vec<String> = [
2617 "",
2618 " \t\n ",
2619 "((((((((((((((((((((((((((((((((",
2620 "\"unterminated",
2621 "'unterminated", "''", "'a''", "Sheet1.Price", ".",
2626 "#2025-01-01", "#not-a-date#",
2628 "999999999999999999999999999999999999999999999999",
2629 "1e999999999999999999999999999999",
2630 "+++++---***///",
2631 "CALL(",
2632 "CALL(f,)",
2633 "CALL(f, ,)",
2634 "SQL(",
2635 "SQL(\"\")",
2636 "SQL(1)",
2637 "\u{0}\u{0}\u{0}",
2638 "\u{1F4A9}\u{1F4A9}\u{1F4A9}", "Price[Product",
2640 "Price[,,,]",
2641 "SUM(Price OVER)",
2642 "SUM(Price OVER Product OVER Time)",
2643 "=",
2644 "X = = =",
2645 "X=",
2646 "X = CALL(CALL(CALL(CALL(f))))",
2647 ]
2648 .iter()
2649 .map(|s| s.to_string())
2650 .collect();
2651 inputs.push("a".repeat(10_000));
2652 inputs.push("(".repeat(5_000));
2653 for s in &inputs {
2654 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2655 let _ = parse_expr(&m, s);
2656 let _ = parse_formula(&m, s);
2657 let _ = parse_definition(&m, s);
2658 }))
2659 .unwrap_or_else(|e| panic!("parser panicked on input {s:?}: {e:?}"));
2660 }
2661 }
2662
2663 #[test]
2664 fn multi_byte_utf8_does_not_panic_the_tokenizer() {
2665 let m = fixture();
2671 assert!(parse_expr(&m, "\u{1F4A9}\u{1F4A9}\u{1F4A9}").is_err()); let _ = parse_expr(&m, "Price\u{00e9}"); }
2675
2676 fn odd_names() -> Model {
2682 let mut m = fixture();
2683 m.add_category(CategoryId(3), "Fiscal Year");
2684 for (id, name) in [
2685 (MeasureId(200), "Unit Price"),
2686 (MeasureId(201), "Cost-Per-Unit"),
2687 (MeasureId(202), "Revenue/Unit"),
2688 (MeasureId(203), "Margin (net)"),
2689 (MeasureId(204), "2024 Total"),
2690 (MeasureId(205), "Bob's Rate"),
2691 (MeasureId(206), "Umsatz \u{20AC} \u{5E74}\u{5EA6}"),
2692 (MeasureId(207), "Over"),
2693 (MeasureId(208), "SUM"),
2694 (MeasureId(209), "NOT"),
2695 ] {
2696 m.add_measure(Measure {
2697 id,
2698 name: Name(name.into()),
2699 value_type: ValueType::Number,
2700 categories: vec![TIME, PRODUCT],
2701 kind: MeasureKind::Input,
2702 description: None,
2703 });
2704 }
2705 m
2706 }
2707
2708 #[test]
2709 fn quoted_name_resolves_a_measure_with_spaces() {
2710 let m = odd_names();
2712 let f = parse_expr(&m, "'Unit Price' * Quantity").unwrap();
2713 assert_eq!(
2714 f.expr,
2715 Expr::BinaryOp(
2716 BinaryOp::Mul,
2717 Box::new(refr(MeasureId(200))),
2718 Box::new(refr(QUANTITY)),
2719 )
2720 );
2721 }
2722
2723 #[test]
2724 fn quoted_names_cover_punctuation_digits_utf8_and_keywords() {
2725 let m = odd_names();
2726 for (src, id) in [
2727 ("'Unit Price'", 200),
2728 ("'Cost-Per-Unit'", 201),
2729 ("'Revenue/Unit'", 202),
2730 ("'Margin (net)'", 203),
2731 ("'2024 Total'", 204),
2732 ("'Umsatz \u{20AC} \u{5E74}\u{5EA6}'", 206),
2733 ("'Over'", 207),
2735 ("'SUM'", 208),
2736 ("'NOT'", 209),
2737 ] {
2738 assert_eq!(
2739 parse_expr(&m, src).unwrap().expr,
2740 refr(MeasureId(id)),
2741 "{src}"
2742 );
2743 }
2744 assert!(matches!(
2747 parse_expr(&m, "'NOT' + 'SUM'").unwrap().expr,
2748 Expr::BinaryOp(BinaryOp::Add, _, _)
2749 ));
2750 assert!(parse_expr(&m, "'SUM'(Price)").is_err()); }
2753
2754 #[test]
2755 fn doubled_quote_escapes_an_embedded_quote() {
2756 let m = odd_names();
2759 let f = parse_expr(&m, "'Bob''s Rate' + Price").unwrap();
2760 assert_eq!(
2761 f.expr,
2762 Expr::BinaryOp(
2763 BinaryOp::Add,
2764 Box::new(refr(MeasureId(205))),
2765 Box::new(refr(PRICE)),
2766 )
2767 );
2768 assert!(parse_expr(&m, "'Bob's Rate'").is_err());
2771 }
2772
2773 #[test]
2774 fn quoted_names_work_everywhere_a_name_does() {
2775 let m = odd_names();
2776 let f = parse_formula(&m, "'Gross Margin' = 'Unit Price' - 'Cost-Per-Unit'").unwrap();
2778 assert_eq!(f.target, Name("Gross Margin".into()));
2779 let f = parse_expr(&m, "'Unit Price'[Time, Product]").unwrap();
2781 assert_eq!(
2782 f.expr,
2783 Expr::Ref(
2784 MeasureId(200),
2785 DimensionSpec {
2786 by: vec![TIME, PRODUCT],
2787 over: vec![],
2788 except: vec![],
2789 }
2790 )
2791 );
2792 let f = parse_expr(&m, "SUM('Unit Price' OVER 'Fiscal Year')").unwrap();
2793 assert_eq!(
2794 f.expr,
2795 Expr::Call(
2796 FUNC_SUM,
2797 vec![Expr::Ref(
2798 MeasureId(200),
2799 DimensionSpec {
2800 over: vec![CategoryId(3)],
2801 by: vec![],
2802 except: vec![],
2803 }
2804 )]
2805 )
2806 );
2807 assert!(parse_expr(&m, "ABS('Unit Price')").is_ok());
2809 match parse_definition(&m, "X = CALL(f, 'Unit Price')").unwrap() {
2810 Definition::Call { args, .. } => {
2811 assert_eq!(args, vec![Name("Unit Price".into())]);
2812 }
2813 other => panic!("expected Call, got {other:?}"),
2814 }
2815 }
2816
2817 #[test]
2818 fn quoted_name_errors_are_clear_not_panics() {
2819 let m = odd_names();
2820 let e = parse_expr(&m, "'No Such Measure' * Price").unwrap_err();
2822 assert!(
2823 e.to_string().contains("unknown measure: No Such Measure"),
2824 "{e}"
2825 );
2826 assert_eq!(e.position, Some(0));
2827 let e = parse_expr(&m, "SUM(Price OVER 'No Such Category')").unwrap_err();
2828 assert!(
2829 e.to_string().contains("unknown category: No Such Category"),
2830 "{e}"
2831 );
2832 let e = parse_expr(&m, "'Unit Price").unwrap_err();
2834 assert!(e.to_string().contains("unterminated quoted name"), "{e}");
2835 let e = parse_expr(&m, "'' + Price").unwrap_err();
2836 assert!(e.to_string().contains("empty quoted name"), "{e}");
2837 }
2838
2839 #[test]
2840 fn double_quotes_are_still_text_literals() {
2841 let m = odd_names();
2844 assert_eq!(
2845 parse_expr(&m, "\"Unit Price\"").unwrap().expr,
2846 Expr::Literal(Value::Text("Unit Price".into()))
2847 );
2848 assert_eq!(
2850 parse_expr(&m, "\"it's\"").unwrap().expr,
2851 Expr::Literal(Value::Text("it's".into()))
2852 );
2853 }
2854
2855 #[test]
2856 fn dotted_qualification_parses_but_names_the_unknown_qualifier() {
2857 let m = odd_names();
2861 for src in [
2862 "Sheet1.Price",
2863 "'Defined Input & Outputs'.'Income tax rate'",
2864 "Price + Sheet1.Quantity",
2865 "SUM(Price OVER Sheet1.Time)",
2866 ] {
2867 let e = parse_expr(&m, src).unwrap_err();
2868 assert!(e.to_string().contains("unknown qualifier"), "{src}: {e}");
2869 }
2870 let e = parse_expr(&m, "'Defined Input & Outputs'.'Income tax rate'").unwrap_err();
2872 assert!(e.to_string().contains("Defined Input & Outputs"), "{e}");
2873 assert!(parse_expr(&m, "Price.").is_err());
2875 assert!(parse_expr(&m, ".Price").is_err());
2876 assert!(parse_expr(&m, &"a.".repeat(5_000)).is_err());
2878 }
2879
2880 #[test]
2881 fn quoted_and_dotted_adversarial_inputs_never_panic() {
2882 let m = odd_names();
2885 let mut inputs: Vec<String> = [
2886 "'",
2887 "''",
2888 "'''",
2889 "''''",
2890 "'''''",
2891 "'unterminated",
2892 "'a\u{20AC}", "'\u{1F4A9}'", "'\u{1F4A9}", "'a''", "'\u{0}'",
2897 "'Unit Price'[",
2898 "'Unit Price'[']",
2899 ".",
2900 "..",
2901 "a..b",
2902 "'a'.'b'.'c'",
2903 "X = 'a'.'b'",
2904 "X = ''",
2905 "SUM('a' OVER 'b')",
2906 "CALL(f, 'a'",
2907 "SQL('q')",
2908 ]
2909 .iter()
2910 .map(|s| s.to_string())
2911 .collect();
2912 inputs.push(format!("'{}", "a".repeat(10_000))); inputs.push("'a''".repeat(5_000)); inputs.push(".".repeat(5_000));
2915 for s in &inputs {
2916 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2917 let _ = parse_expr(&m, s);
2918 let _ = parse_formula(&m, s);
2919 let _ = parse_definition(&m, s);
2920 }))
2921 .unwrap_or_else(|e| panic!("parser panicked on input {s:?}: {e:?}"));
2922 }
2923 }
2924
2925 #[test]
2926 fn deeply_nested_parens_error_instead_of_overflowing_the_stack() {
2927 let m = fixture();
2931 let deep = "(".repeat(5_000);
2932 assert!(parse_expr(&m, &deep).is_err());
2933 let reasonable = format!("{}Price{}", "(".repeat(50), ")".repeat(50));
2935 assert!(parse_expr(&m, &reasonable).is_ok());
2936 }
2937}