1use crate::messages::{raise, Refusal};
27use crate::refers::{self, is_ident_char, FormulaRef, Kind, Shape};
28use crate::sheet::{MAX_COLUMN, MAX_ROW};
29
30use super::value::ErrorKind;
31
32#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct RefExpr {
36 pub sheet: Option<String>,
38 pub last_sheet: Option<String>,
40 pub top: u32,
41 pub left: u32,
42 pub bottom: u32,
43 pub right: u32,
44 pub written: String,
46 pub shape: Shape,
51 pub corners: Corners,
52 pub found: Option<(u8, u8)>,
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub struct Corners {
72 pub row1: u32,
73 pub col1: u32,
74 pub row2: u32,
75 pub col2: u32,
76 pub row_abs1: bool,
77 pub col_abs1: bool,
78 pub row_abs2: bool,
79 pub col_abs2: bool,
80}
81
82impl RefExpr {
83 pub fn is_cell(&self) -> bool {
85 self.top == self.bottom && self.left == self.right && self.last_sheet.is_none()
86 }
87
88 pub fn moved(&self, d_row: i64, d_col: i64) -> Option<(u32, u32, u32, u32)> {
97 if d_row == 0 && d_col == 0 {
98 return Some((self.top, self.left, self.bottom, self.right));
99 }
100 let c = &self.corners;
101 let row = |n: u32, abs: bool| shifted(n, abs, d_row, MAX_ROW);
102 let col = |n: u32, abs: bool| shifted(n, abs, d_col, MAX_COLUMN);
103 match self.shape {
104 Shape::Cell => {
105 let k = col(c.col1, c.col_abs1)?;
106 let r = row(c.row1, c.row_abs1)?;
107 Some((r, k, r, k))
108 }
109 Shape::Range => {
110 let k1 = col(c.col1, c.col_abs1)?;
111 let r1 = row(c.row1, c.row_abs1)?;
112 let k2 = col(c.col2, c.col_abs2)?;
113 let r2 = row(c.row2, c.row_abs2)?;
114 Some((r1.min(r2), k1.min(k2), r1.max(r2), k1.max(k2)))
115 }
116 Shape::Column => {
117 let k1 = col(c.col1, c.col_abs1)?;
118 let k2 = col(c.col2, c.col_abs2)?;
119 Some((1, k1.min(k2), MAX_ROW, k1.max(k2)))
120 }
121 Shape::Row => {
122 let r1 = row(c.row1, c.row_abs1)?;
123 let r2 = row(c.row2, c.row_abs2)?;
124 Some((r1.min(r2), 1, r1.max(r2), MAX_COLUMN))
125 }
126 Shape::None => None,
127 }
128 }
129}
130
131fn shifted(n: u32, is_abs: bool, d: i64, max: u32) -> Option<u32> {
134 if is_abs {
135 return Some(n);
136 }
137 let m = i64::from(n) + d;
138 (m >= 1 && m <= i64::from(max)).then_some(m as u32)
139}
140
141#[derive(Clone, Copy, Debug, PartialEq, Eq)]
143pub enum BinOp {
144 Add,
145 Sub,
146 Mul,
147 Div,
148 Pow,
149 Concat,
150 Eq,
151 Ne,
152 Lt,
153 Gt,
154 Le,
155 Ge,
156}
157
158#[derive(Clone, Debug, PartialEq)]
160pub enum Expr {
161 Number(f64),
162 Text(String),
163 Bool(bool),
164 Error(ErrorKind),
165 Empty,
167 Ref(RefExpr),
168 Name {
170 name: String,
171 sheet: Option<String>,
172 },
173 Call(String, Vec<Expr>),
175 Neg(Box<Expr>),
176 Pos(Box<Expr>),
177 Percent(Box<Expr>),
178 Binary(BinOp, Box<Expr>, Box<Expr>),
179}
180
181#[derive(Clone, Copy, Debug, PartialEq)]
184pub enum Read<'e> {
185 Ref(&'e RefExpr),
186 Name {
187 name: &'e str,
188 sheet: Option<&'e str>,
189 },
190}
191
192impl Expr {
193 pub fn each_read<'e>(&'e self, f: &mut dyn FnMut(Read<'e>)) {
197 let mut stack = vec![self];
198 while let Some(e) = stack.pop() {
199 match e {
200 Expr::Ref(r) => f(Read::Ref(r)),
201 Expr::Name { name, sheet } => f(Read::Name {
202 name,
203 sheet: sheet.as_deref(),
204 }),
205 other => other.push_children(&mut stack),
206 }
207 }
208 }
209
210 pub fn each_call<'e>(&'e self, f: &mut dyn FnMut(&'e str)) {
213 let mut stack = vec![self];
214 while let Some(e) = stack.pop() {
215 if let Expr::Call(name, _) = e {
216 f(name);
217 }
218 e.push_children(&mut stack);
219 }
220 }
221
222 fn push_children<'e>(&'e self, stack: &mut Vec<&'e Expr>) {
228 match self {
229 Expr::Call(_, args) => stack.extend(args.iter().rev()),
230 Expr::Neg(x) | Expr::Pos(x) | Expr::Percent(x) => stack.push(x),
231 Expr::Binary(_, a, b) => {
232 stack.push(b);
233 stack.push(a);
234 }
235 Expr::Ref(_)
236 | Expr::Name { .. }
237 | Expr::Number(_)
238 | Expr::Text(_)
239 | Expr::Bool(_)
240 | Expr::Error(_)
241 | Expr::Empty => {}
242 }
243 }
244}
245
246#[derive(Clone, Debug, PartialEq)]
247enum Tok {
248 Num(f64),
249 Str(String),
250 Bool(bool),
251 Err(ErrorKind),
252 Ref(RefExpr),
253 Name {
254 name: String,
255 sheet: Option<String>,
256 },
257 Func(String),
260 Op(BinOp),
261 Percent,
262 LParen,
263 RParen,
264 Comma,
265 Other(String),
267}
268
269pub fn ref_expr(r: &FormulaRef) -> Option<RefExpr> {
273 if !matches!(
274 r.kind,
275 Kind::Cell | Kind::Range | Kind::Column | Kind::Row | Kind::ThreeD
276 ) {
277 return None;
278 }
279 let (top, left, bottom, right) = match r.shape {
280 Shape::Cell => (r.row1, r.col1, r.row1, r.col1),
281 Shape::Range => (
282 r.row1.min(r.row2),
283 r.col1.min(r.col2),
284 r.row1.max(r.row2),
285 r.col1.max(r.col2),
286 ),
287 Shape::Column => (1, r.col1.min(r.col2), MAX_ROW, r.col1.max(r.col2)),
288 Shape::Row => (r.row1.min(r.row2), 1, r.row1.max(r.row2), MAX_COLUMN),
289 Shape::None => return None,
290 };
291 let (sheet, last_sheet) = if r.sheet_name.is_empty() {
292 (None, None)
293 } else {
294 match r.sheet_name.split_once(':') {
295 Some((first, last)) => (Some(first.to_string()), Some(last.to_string())),
296 None => (Some(r.sheet_name.clone()), None),
297 }
298 };
299 Some(RefExpr {
300 sheet,
301 last_sheet,
302 top,
303 left,
304 bottom,
305 right,
306 written: r.written.clone(),
307 shape: r.shape,
308 corners: Corners {
309 row1: r.row1,
310 col1: r.col1,
311 row2: r.row2,
312 col2: r.col2,
313 row_abs1: r.row_abs1,
314 col_abs1: r.col_abs1,
315 row_abs2: r.row_abs2,
316 col_abs2: r.col_abs2,
317 },
318 found: None,
319 })
320}
321
322fn tokenize(text: &str) -> Vec<Tok> {
323 let s: Vec<char> = text.chars().collect();
324 let refs: Vec<FormulaRef> = refers::scan(text)
325 .into_iter()
326 .filter(|r| r.kind != Kind::Unreadable)
327 .collect();
328 let mut toks = Vec::new();
329 let mut i = usize::from(s.first() == Some(&'='));
330 let mut next_ref = 0;
331 while i < s.len() {
332 while next_ref < refs.len() && refs[next_ref].pos < i {
333 next_ref += 1;
334 }
335 if next_ref < refs.len() && refs[next_ref].pos == i {
336 let r = &refs[next_ref];
337 next_ref += 1;
338 toks.push(match r.kind {
339 Kind::Cell | Kind::Range | Kind::Column | Kind::Row | Kind::ThreeD => {
340 match ref_expr(r) {
341 Some(re) => Tok::Ref(re),
342 None => Tok::Other(r.written.clone()),
343 }
344 }
345 Kind::Name => Tok::Name {
346 name: r.part.clone(),
347 sheet: (!r.sheet_name.is_empty()).then(|| r.sheet_name.clone()),
348 },
349 Kind::Broken => Tok::Err(ErrorKind::Ref),
350 Kind::Structured | Kind::External | Kind::Spill | Kind::Unreadable => {
351 Tok::Other(r.written.clone())
352 }
353 });
354 i += r.span.max(1);
355 continue;
356 }
357 let c = s[i];
358 if c.is_whitespace() {
359 i += 1;
360 } else if c == '"' {
361 let (text, end) = read_string(&s, i);
362 toks.push(Tok::Str(text));
363 i = end;
364 } else if c.is_ascii_digit()
365 || (c == '.' && s.get(i + 1).is_some_and(|d| d.is_ascii_digit()))
366 {
367 let (tok, end) = read_number(&s, i);
368 toks.push(tok);
369 i = end;
370 } else if c == '#' {
371 let mut k = i + 1;
372 while k < s.len() && (s[k].is_ascii_alphanumeric() || s[k] == '/' || s[k] == '_') {
373 k += 1;
374 }
375 if matches!(s.get(k), Some('!') | Some('?')) {
376 k += 1;
377 }
378 let tok: String = s[i..k].iter().collect();
379 toks.push(match ErrorKind::parse(&tok) {
380 Some(kind) => Tok::Err(kind),
381 None => Tok::Other(tok),
382 });
383 i = k;
384 } else if c.is_ascii_alphabetic() || c == '_' {
385 let mut k = i;
386 while k < s.len() && is_ident_char(s[k]) {
387 k += 1;
388 }
389 let tok: String = s[i..k].iter().collect();
390 if s.get(k) == Some(&'(') {
391 toks.push(Tok::Func(tok.to_ascii_uppercase()));
392 } else {
393 match tok.to_ascii_lowercase().as_str() {
394 "true" => toks.push(Tok::Bool(true)),
395 "false" => toks.push(Tok::Bool(false)),
396 _ => toks.push(Tok::Other(tok)),
397 }
398 }
399 i = k;
400 } else {
401 let two: String = s[i..(i + 2).min(s.len())].iter().collect();
402 let (tok, len) = match two.as_str() {
403 "<>" => (Tok::Op(BinOp::Ne), 2),
404 "<=" => (Tok::Op(BinOp::Le), 2),
405 ">=" => (Tok::Op(BinOp::Ge), 2),
406 _ => match c {
407 '<' => (Tok::Op(BinOp::Lt), 1),
408 '>' => (Tok::Op(BinOp::Gt), 1),
409 '=' => (Tok::Op(BinOp::Eq), 1),
410 '+' => (Tok::Op(BinOp::Add), 1),
411 '-' => (Tok::Op(BinOp::Sub), 1),
412 '*' => (Tok::Op(BinOp::Mul), 1),
413 '/' => (Tok::Op(BinOp::Div), 1),
414 '^' => (Tok::Op(BinOp::Pow), 1),
415 '&' => (Tok::Op(BinOp::Concat), 1),
416 '%' => (Tok::Percent, 1),
417 '(' => (Tok::LParen, 1),
418 ')' => (Tok::RParen, 1),
419 ',' => (Tok::Comma, 1),
420 other => (Tok::Other(other.to_string()), 1),
421 },
422 };
423 toks.push(tok);
424 i += len;
425 }
426 }
427 toks
428}
429
430fn read_string(s: &[char], i: usize) -> (String, usize) {
434 let mut out = String::new();
435 let mut k = i + 1;
436 while k < s.len() {
437 if s[k] == '"' {
438 if s.get(k + 1) == Some(&'"') {
439 out.push('"');
440 k += 2;
441 } else {
442 return (out, k + 1);
443 }
444 } else {
445 out.push(s[k]);
446 k += 1;
447 }
448 }
449 (out, k)
450}
451
452fn read_number(s: &[char], i: usize) -> (Tok, usize) {
455 let mut k = i;
456 while k < s.len() && s[k].is_ascii_digit() {
457 k += 1;
458 }
459 if k < s.len() && s[k] == '.' {
460 k += 1;
461 while k < s.len() && s[k].is_ascii_digit() {
462 k += 1;
463 }
464 }
465 if k < s.len() && (s[k] == 'e' || s[k] == 'E') {
466 let mut j = k + 1;
467 if j < s.len() && (s[j] == '+' || s[j] == '-') {
468 j += 1;
469 }
470 let first = j;
471 while j < s.len() && s[j].is_ascii_digit() {
472 j += 1;
473 }
474 if j > first {
475 k = j;
476 }
477 }
478 let text: String = s[i..k].iter().collect();
479 match text.parse::<f64>() {
480 Ok(v) if v.is_finite() => (Tok::Num(v), k),
481 _ => (Tok::Err(ErrorKind::Num), k),
482 }
483}
484
485pub const NESTING_LIMIT: u32 = 64;
492
493pub const LENGTH_LIMIT: usize = 8192;
497
498pub const TOO_DEEP: &str = "nesting deeper than 64";
500
501pub const TOO_LONG: &str = "longer than 8,192 characters";
503
504pub fn unread_refusal(cell: &str, construct: &str) -> Refusal {
509 match construct {
510 TOO_DEEP => raise(
511 "calc-formula-too-deep",
512 &[("cell", cell), ("limit", &NESTING_LIMIT.to_string())],
513 ),
514 TOO_LONG => raise(
515 "calc-formula-too-long",
516 &[("cell", cell), ("limit", "8,192")],
517 ),
518 _ => raise(
519 "calc-construct-not-read",
520 &[("cell", cell), ("construct", construct)],
521 ),
522 }
523}
524
525struct Parser {
526 toks: Vec<Tok>,
527 at: usize,
528 depth: u32,
530}
531
532impl Parser {
533 fn nested<T>(&mut self, f: impl FnOnce(&mut Self) -> Result<T, String>) -> Result<T, String> {
535 if self.depth >= NESTING_LIMIT {
536 return Err(TOO_DEEP.to_string());
537 }
538 self.depth += 1;
539 let r = f(self);
540 self.depth -= 1;
541 r
542 }
543
544 fn peek(&self) -> Option<&Tok> {
545 self.toks.get(self.at)
546 }
547
548 fn take(&mut self) -> Option<Tok> {
549 let t = self.toks.get(self.at).cloned();
550 if t.is_some() {
551 self.at += 1;
552 }
553 t
554 }
555
556 fn peek_op(&self) -> Option<BinOp> {
557 match self.peek() {
558 Some(Tok::Op(op)) => Some(*op),
559 _ => None,
560 }
561 }
562
563 fn label(t: &Tok) -> String {
565 match t {
566 Tok::Other(s) => s.clone(),
567 Tok::Op(op) => match op {
568 BinOp::Add => "+",
569 BinOp::Sub => "-",
570 BinOp::Mul => "*",
571 BinOp::Div => "/",
572 BinOp::Pow => "^",
573 BinOp::Concat => "&",
574 BinOp::Eq => "=",
575 BinOp::Ne => "<>",
576 BinOp::Lt => "<",
577 BinOp::Gt => ">",
578 BinOp::Le => "<=",
579 BinOp::Ge => ">=",
580 }
581 .to_string(),
582 Tok::Percent => "%".to_string(),
583 Tok::LParen => "(".to_string(),
584 Tok::RParen => ")".to_string(),
585 Tok::Comma => ",".to_string(),
586 Tok::Num(v) => format!("{v}"),
587 Tok::Str(s) => format!("\"{s}\""),
588 Tok::Bool(b) => if *b { "TRUE" } else { "FALSE" }.to_string(),
589 Tok::Err(k) => k.text().to_string(),
590 Tok::Ref(r) => r.written.clone(),
591 Tok::Name { name, .. } => name.clone(),
592 Tok::Func(name) => name.clone(),
593 }
594 }
595
596 fn starts_operand(t: &Tok) -> bool {
597 matches!(
598 t,
599 Tok::Num(_)
600 | Tok::Str(_)
601 | Tok::Bool(_)
602 | Tok::Err(_)
603 | Tok::Ref(_)
604 | Tok::Name { .. }
605 | Tok::Func(_)
606 | Tok::LParen
607 )
608 }
609
610 fn comparison(&mut self) -> Result<Expr, String> {
611 let mut left = self.concat()?;
612 while let Some(op) = self.peek_op() {
613 if !matches!(
614 op,
615 BinOp::Eq | BinOp::Ne | BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge
616 ) {
617 break;
618 }
619 self.at += 1;
620 let right = self.concat()?;
621 left = Expr::Binary(op, Box::new(left), Box::new(right));
622 }
623 Ok(left)
624 }
625
626 fn concat(&mut self) -> Result<Expr, String> {
627 let mut left = self.additive()?;
628 while self.peek_op() == Some(BinOp::Concat) {
629 self.at += 1;
630 let right = self.additive()?;
631 left = Expr::Binary(BinOp::Concat, Box::new(left), Box::new(right));
632 }
633 Ok(left)
634 }
635
636 fn additive(&mut self) -> Result<Expr, String> {
637 let mut left = self.term()?;
638 while let Some(op @ (BinOp::Add | BinOp::Sub)) = self.peek_op() {
639 self.at += 1;
640 let right = self.term()?;
641 left = Expr::Binary(op, Box::new(left), Box::new(right));
642 }
643 Ok(left)
644 }
645
646 fn term(&mut self) -> Result<Expr, String> {
647 let mut left = self.power()?;
648 while let Some(op @ (BinOp::Mul | BinOp::Div)) = self.peek_op() {
649 self.at += 1;
650 let right = self.power()?;
651 left = Expr::Binary(op, Box::new(left), Box::new(right));
652 }
653 Ok(left)
654 }
655
656 fn power(&mut self) -> Result<Expr, String> {
657 let mut left = self.unary()?;
658 while self.peek_op() == Some(BinOp::Pow) {
659 self.at += 1;
660 let right = self.unary()?;
661 left = Expr::Binary(BinOp::Pow, Box::new(left), Box::new(right));
662 }
663 Ok(left)
664 }
665
666 fn unary(&mut self) -> Result<Expr, String> {
667 match self.peek_op() {
668 Some(BinOp::Sub) => {
669 self.at += 1;
670 Ok(Expr::Neg(Box::new(self.nested(Self::unary)?)))
671 }
672 Some(BinOp::Add) => {
673 self.at += 1;
674 Ok(Expr::Pos(Box::new(self.nested(Self::unary)?)))
675 }
676 _ => self.postfix(),
677 }
678 }
679
680 fn postfix(&mut self) -> Result<Expr, String> {
681 let mut e = self.primary()?;
682 let mut percents = 0;
683 while self.peek() == Some(&Tok::Percent) {
684 if self.depth + percents >= NESTING_LIMIT {
687 return Err(TOO_DEEP.to_string());
688 }
689 percents += 1;
690 self.at += 1;
691 e = Expr::Percent(Box::new(e));
692 }
693 if let Some(t) = self.peek() {
694 if Self::starts_operand(t) {
695 return Err("intersection".to_string());
698 }
699 }
700 Ok(e)
701 }
702
703 fn primary(&mut self) -> Result<Expr, String> {
704 let Some(t) = self.take() else {
705 return Err("end of formula".to_string());
706 };
707 match t {
708 Tok::Num(v) => Ok(Expr::Number(v)),
709 Tok::Str(s) => Ok(Expr::Text(s)),
710 Tok::Bool(b) => Ok(Expr::Bool(b)),
711 Tok::Err(k) => Ok(Expr::Error(k)),
712 Tok::Ref(r) => Ok(Expr::Ref(r)),
713 Tok::Name { name, sheet } => Ok(Expr::Name { name, sheet }),
714 Tok::Func(name) => {
715 if self.take() != Some(Tok::LParen) {
716 return Err(name);
717 }
718 self.nested(|p| p.call(name))
719 }
720 Tok::LParen => {
721 let e = self.nested(Self::comparison)?;
722 match self.take() {
723 Some(Tok::RParen) => Ok(e),
724 Some(Tok::Comma) => Err("union".to_string()),
725 Some(other) => Err(Self::label(&other)),
726 None => Err("end of formula".to_string()),
727 }
728 }
729 other => Err(Self::label(&other)),
730 }
731 }
732
733 fn call(&mut self, name: String) -> Result<Expr, String> {
735 let mut args = Vec::new();
736 if self.peek() == Some(&Tok::RParen) {
737 self.at += 1;
738 return Ok(Expr::Call(name, args));
739 }
740 loop {
741 match self.peek() {
742 Some(Tok::Comma) | Some(Tok::RParen) => args.push(Expr::Empty),
743 _ => args.push(self.comparison()?),
744 }
745 match self.take() {
746 Some(Tok::Comma) => continue,
747 Some(Tok::RParen) => break,
748 Some(other) => return Err(Self::label(&other)),
749 None => return Err("end of formula".to_string()),
750 }
751 }
752 Ok(Expr::Call(name, args))
753 }
754}
755
756pub fn parse(text: &str) -> Result<Expr, String> {
760 if text.strip_prefix('=').unwrap_or(text).chars().count() > LENGTH_LIMIT {
761 return Err(TOO_LONG.to_string());
762 }
763 let mut p = Parser {
764 toks: tokenize(text),
765 at: 0,
766 depth: 0,
767 };
768 if p.toks.is_empty() {
769 return Err("end of formula".to_string());
770 }
771 let e = p.comparison()?;
772 match p.peek() {
773 None => Ok(e),
774 Some(t) => Err(Parser::label(t)),
775 }
776}
777
778pub fn calls(text: &str) -> Vec<String> {
782 tokenize(text)
783 .into_iter()
784 .filter_map(|t| match t {
785 Tok::Func(name) => Some(name),
786 _ => None,
787 })
788 .collect()
789}
790
791#[cfg(test)]
792mod tests {
793 use super::*;
794
795 fn cell(written: &str, row: u32, col: u32) -> Expr {
796 Expr::Ref(RefExpr {
797 sheet: None,
798 last_sheet: None,
799 top: row,
800 left: col,
801 bottom: row,
802 right: col,
803 written: written.to_string(),
804 shape: Shape::Cell,
805 corners: Corners {
806 row1: row,
807 col1: col,
808 row2: 0,
809 col2: 0,
810 row_abs1: false,
811 col_abs1: false,
812 row_abs2: false,
813 col_abs2: false,
814 },
815 found: None,
816 })
817 }
818
819 #[test]
820 fn literals_and_references_are_read() {
821 assert_eq!(parse("=5"), Ok(Expr::Number(5.0)));
822 assert_eq!(parse("1.5e3"), Ok(Expr::Number(1500.0)));
823 assert_eq!(parse(".5"), Ok(Expr::Number(0.5)));
824 assert_eq!(parse("=\"a\"\"b\""), Ok(Expr::Text("a\"b".to_string())));
825 assert_eq!(parse("=TRUE"), Ok(Expr::Bool(true)));
826 assert_eq!(parse("=false"), Ok(Expr::Bool(false)));
827 assert_eq!(parse("=#N/A"), Ok(Expr::Error(ErrorKind::NA)));
828 assert_eq!(parse("=#REF!"), Ok(Expr::Error(ErrorKind::Ref)));
829 assert_eq!(parse("=B2"), Ok(cell("B2", 2, 2)));
830 assert_eq!(
831 parse("=Rate"),
832 Ok(Expr::Name {
833 name: "Rate".to_string(),
834 sheet: None
835 })
836 );
837 assert_eq!(
838 parse("=Model!Local"),
839 Ok(Expr::Name {
840 name: "Local".to_string(),
841 sheet: Some("Model".to_string())
842 })
843 );
844 let r = match parse("='Q1 Data'!A:A").unwrap() {
845 Expr::Ref(r) => r,
846 other => panic!("{other:?}"),
847 };
848 assert_eq!(r.sheet.as_deref(), Some("Q1 Data"));
849 assert_eq!((r.top, r.left, r.bottom, r.right), (1, 1, MAX_ROW, 1));
850 let span = match parse("=Model:Scratch!B1").unwrap() {
851 Expr::Ref(r) => r,
852 other => panic!("{other:?}"),
853 };
854 assert_eq!(span.sheet.as_deref(), Some("Model"));
855 assert_eq!(span.last_sheet.as_deref(), Some("Scratch"));
856 assert_eq!((span.top, span.left, span.bottom, span.right), (1, 2, 1, 2));
857 assert!(
858 !span.is_cell(),
859 "a span is one cell on each of its sheets, not one cell"
860 );
861 }
862
863 #[test]
864 fn precedence_is_excel_s() {
865 assert_eq!(
867 parse("=-2^2"),
868 Ok(Expr::Binary(
869 BinOp::Pow,
870 Box::new(Expr::Neg(Box::new(Expr::Number(2.0)))),
871 Box::new(Expr::Number(2.0))
872 ))
873 );
874 assert_eq!(
876 parse("=1+2*3"),
877 Ok(Expr::Binary(
878 BinOp::Add,
879 Box::new(Expr::Number(1.0)),
880 Box::new(Expr::Binary(
881 BinOp::Mul,
882 Box::new(Expr::Number(2.0)),
883 Box::new(Expr::Number(3.0))
884 ))
885 ))
886 );
887 assert_eq!(
888 parse("=2^3^2"),
889 Ok(Expr::Binary(
890 BinOp::Pow,
891 Box::new(Expr::Binary(
892 BinOp::Pow,
893 Box::new(Expr::Number(2.0)),
894 Box::new(Expr::Number(3.0))
895 )),
896 Box::new(Expr::Number(2.0))
897 ))
898 );
899 assert_eq!(
901 parse("=1&2=\"12\""),
902 Ok(Expr::Binary(
903 BinOp::Eq,
904 Box::new(Expr::Binary(
905 BinOp::Concat,
906 Box::new(Expr::Number(1.0)),
907 Box::new(Expr::Number(2.0))
908 )),
909 Box::new(Expr::Text("12".to_string()))
910 ))
911 );
912 assert_eq!(
913 parse("=50%"),
914 Ok(Expr::Percent(Box::new(Expr::Number(50.0))))
915 );
916 assert_eq!(
917 parse("=(1+2)*3").map(|e| matches!(e, Expr::Binary(BinOp::Mul, ..))),
918 Ok(true)
919 );
920 }
921
922 #[test]
923 fn calls_take_their_arguments_empty_ones_included() {
924 assert_eq!(
925 parse("=IF(B2>3,\"big\",\"small\")"),
926 Ok(Expr::Call(
927 "IF".to_string(),
928 vec![
929 Expr::Binary(
930 BinOp::Gt,
931 Box::new(cell("B2", 2, 2)),
932 Box::new(Expr::Number(3.0))
933 ),
934 Expr::Text("big".to_string()),
935 Expr::Text("small".to_string()),
936 ]
937 ))
938 );
939 assert_eq!(
940 parse("=sum(A1,,2)"),
941 Ok(Expr::Call(
942 "SUM".to_string(),
943 vec![cell("A1", 1, 1), Expr::Empty, Expr::Number(2.0)]
944 ))
945 );
946 assert_eq!(parse("=PI()"), Ok(Expr::Call("PI".to_string(), vec![])));
947 assert_eq!(parse("=row()"), Ok(Expr::Call("ROW".to_string(), vec![])));
948 assert_eq!(
949 calls("=IF(SUM(A1:A3)>0,INDIRECT(\"B1\"),offset(A1,1,0))"),
950 vec!["IF", "SUM", "INDIRECT", "OFFSET"]
951 );
952 assert_eq!(calls("=\"SUM(\"&A1"), Vec::<String>::new());
953 }
954
955 #[test]
956 fn a_construct_this_slice_does_not_read_is_named_as_written() {
957 assert_eq!(parse("={1,2;3,4}"), Err("{".to_string()));
958 assert_eq!(parse("=@A1:A3"), Err("@".to_string()));
959 assert_eq!(parse("=A1 B1"), Err("intersection".to_string()));
960 assert_eq!(parse("=SUM((A1,B2))"), Err("union".to_string()));
961 assert_eq!(parse("=A1#"), Err("A1#".to_string()));
962 assert_eq!(
963 parse("=SUM(Sales[Amount])"),
964 Err("Sales[Amount]".to_string())
965 );
966 assert_eq!(
967 parse("=[Rates.xlsx]Sheet1!A1"),
968 Err("[Rates.xlsx]Sheet1!A1".to_string())
969 );
970 assert_eq!(parse("=#SPILL!"), Err("#SPILL!".to_string()));
971 assert_eq!(parse("="), Err("end of formula".to_string()));
972 assert_eq!(parse("=1+"), Err("end of formula".to_string()));
973 assert_eq!(parse("=)"), Err(")".to_string()));
974 }
975
976 #[test]
977 fn excel_s_two_limits_are_labels_of_their_own() {
978 let wrap =
981 |n: usize, open: &str, close: &str| format!("={}1{}", open.repeat(n), close.repeat(n));
982 for (open, close) in [("(", ")"), ("ABS(", ")"), ("-", ""), ("+", ""), ("", "%")] {
983 assert!(
984 parse(&wrap(64, open, close)).is_ok(),
985 "{open}...{close} at 64"
986 );
987 assert_eq!(
988 parse(&wrap(65, open, close)),
989 Err(TOO_DEEP.to_string()),
990 "{open}...{close} at 65"
991 );
992 }
993 let chain = format!("={}11", "1+".repeat(4095));
996 assert_eq!(chain.len() - 1, LENGTH_LIMIT);
997 assert!(parse(&chain).is_ok());
998 assert_eq!(parse(&format!("{chain}1")), Err(TOO_LONG.to_string()));
999 }
1000}