1use crate::core::{CompiledFormula, FormulaPart, RefType, Sheet, SheetSection, generate_unique_id};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
4pub enum Op {
5 Add,
6 Sub,
7 Mul,
8 Div,
9 Exp,
10 Eq,
11 Ne,
12 Lt,
13 Gt,
14 Le,
15 Ge,
16}
17
18#[derive(Debug, Clone, PartialEq)]
19pub enum Expr {
20 Number(f64),
21 String(String),
22 Boolean(bool),
23 Error(&'static str),
24 CellRef {
25 sheet: Option<String>,
26 row: usize,
27 col: usize,
28 row_abs: bool,
29 col_abs: bool,
30 },
31 RangeRef {
32 sheet: Option<String>,
33 start_row: usize,
34 start_col: usize,
35 end_row: usize,
36 end_col: usize,
37 start_row_abs: bool,
38 start_col_abs: bool,
39 end_row_abs: bool,
40 end_col_abs: bool,
41 },
42 List(Vec<Expr>),
43 Slice {
44 expr: Box<Expr>,
45 start: Option<Box<Expr>>,
46 end: Option<Box<Expr>>,
47 },
48 FunctionCall {
49 name: String,
50 args: Vec<Expr>,
51 },
52 BinaryOp {
53 op: Op,
54 left: Box<Expr>,
55 right: Box<Expr>,
56 },
57 UnaryOp {
58 op: Op,
59 expr: Box<Expr>,
60 },
61 Identifier(String),
62 StructuredRef {
63 sheet: Option<String>,
64 column: Option<String>,
65 is_this_row: bool,
66 section: SheetSection,
67 },
68}
69
70#[derive(Debug, Clone, PartialEq)]
71pub enum EvalToken {
72 Number(f64),
73 String(String),
74 Boolean(bool),
75 Identifier(String),
76 Error(&'static str),
77 Op(Op),
78 OpenParen,
79 CloseParen,
80 OpenBracket,
81 CloseBracket,
82 Comma,
83 Colon,
84 Exclamation,
85 Dot,
86 StructuredRef {
87 sheet: Option<String>,
88 column: Option<String>,
89 is_this_row: bool,
90 section: SheetSection,
91 },
92}
93
94pub fn col_idx_to_letters(mut col: usize) -> String {
97 let mut letters = String::new();
98 loop {
99 let remainder = col % 26;
100 letters.insert(0, (b'A' + remainder as u8) as char);
101 if col < 26 {
102 break;
103 }
104 col = col / 26 - 1;
105 }
106 letters
107}
108
109pub fn col_letters_to_idx(col_str: &str) -> usize {
112 let mut col = 0;
113 for c in col_str.chars() {
114 if c.is_ascii_alphabetic() {
115 col = col * 26 + (c.to_ascii_uppercase() as usize - 'A' as usize + 1);
116 }
117 }
118 if col > 0 { col - 1 } else { 0 }
119}
120
121pub fn parse_a1_coordinates(col_str: &str, row_str: &str) -> (usize, usize) {
129 let col_idx = col_letters_to_idx(col_str);
130
131 let row_val: usize = row_str.parse().unwrap_or(1);
132 let row_idx = if row_val > 0 { row_val - 1 } else { 0 };
133
134 (row_idx, col_idx)
135}
136
137pub fn parse_cell_ref(cell_str: &str) -> Result<(Option<String>, usize, usize), String> {
140 let trimmed = cell_str.trim();
141 if trimmed.is_empty() {
142 return Err("Cell reference cannot be empty".to_string());
143 }
144
145 let (sheet_part, cell_part) = split_sheet_reference(trimmed);
146 let (row_idx, col_idx, _, _) = parse_cell_ref_parts(cell_part)
147 .ok_or_else(|| format!("Invalid cell reference format: '{}'", cell_str))?;
148
149 Ok((sheet_part, row_idx, col_idx))
150}
151
152pub fn parse_range_ref(
155 range_str: &str,
156) -> Result<(Option<String>, usize, usize, usize, usize), String> {
157 let trimmed = range_str.trim();
158 if trimmed.is_empty() {
159 return Err("Range reference cannot be empty".to_string());
160 }
161
162 let (sheet_part, range_part) = split_sheet_reference(trimmed);
163
164 if let Some((start_str, end_str)) = range_part.split_once(':') {
165 let (_, start_row, start_col) = parse_cell_ref(start_str)?;
166 let (_, end_row, end_col) = parse_cell_ref(end_str)?;
167
168 Ok((
169 sheet_part,
170 start_row.min(end_row),
171 start_col.min(end_col),
172 start_row.max(end_row),
173 start_col.max(end_col),
174 ))
175 } else {
176 let (_, row_idx, col_idx) = parse_cell_ref(range_part)?;
177 Ok((sheet_part, row_idx, col_idx, row_idx, col_idx))
178 }
179}
180
181fn split_sheet_reference(reference: &str) -> (Option<String>, &str) {
182 if let Some(pos) = reference.rfind('!') {
183 (
184 Some(reference[..pos].trim_matches('\'').to_string()),
185 &reference[pos + 1..],
186 )
187 } else {
188 (None, reference)
189 }
190}
191
192fn parse_cell_ref_parts(s: &str) -> Option<(usize, usize, bool, bool)> {
193 let chars: Vec<char> = s.chars().collect();
194 let mut idx = 0;
195
196 let mut col_abs = false;
197 if idx < chars.len() && chars[idx] == '$' {
198 col_abs = true;
199 idx += 1;
200 }
201
202 let mut col_str = String::new();
203 while idx < chars.len() && chars[idx].is_ascii_alphabetic() {
204 col_str.push(chars[idx]);
205 idx += 1;
206 }
207 if col_str.is_empty() || col_str.len() > 3 {
208 return None;
209 }
210
211 let mut row_abs = false;
212 if idx < chars.len() && chars[idx] == '$' {
213 row_abs = true;
214 idx += 1;
215 }
216
217 let mut row_str = String::new();
218 while idx < chars.len() && chars[idx].is_ascii_digit() {
219 row_str.push(chars[idx]);
220 idx += 1;
221 }
222 if row_str.is_empty() {
223 return None;
224 }
225 if row_str.parse::<usize>().ok()? == 0 {
226 return None;
227 }
228
229 if idx < chars.len() {
230 return None;
231 }
232
233 let (row_idx, col_idx) = parse_a1_coordinates(&col_str, &row_str);
234 Some((row_idx, col_idx, row_abs, col_abs))
235}
236
237fn parse_column_ref(s: &str) -> Option<(usize, bool)> {
238 let chars: Vec<char> = s.chars().collect();
239 let mut idx = 0;
240 let mut col_abs = false;
241 if idx < chars.len() && chars[idx] == '$' {
242 col_abs = true;
243 idx += 1;
244 }
245 let mut col_str = String::new();
246 while idx < chars.len() && chars[idx].is_ascii_alphabetic() {
247 col_str.push(chars[idx]);
248 idx += 1;
249 }
250 if col_str.is_empty() || col_str.len() > 3 {
251 return None;
252 }
253 if idx < chars.len() {
254 return None;
255 }
256 let mut col = 0;
257 for c in col_str.chars() {
258 col = col * 26 + (c.to_ascii_uppercase() as usize - 'A' as usize + 1);
259 }
260 let col_idx = if col > 0 { col - 1 } else { 0 };
261 Some((col_idx, col_abs))
262}
263
264fn parse_row_ref(s: &str) -> Option<(usize, bool)> {
265 let chars: Vec<char> = s.chars().collect();
266 let mut idx = 0;
267 let mut row_abs = false;
268 if idx < chars.len() && chars[idx] == '$' {
269 row_abs = true;
270 idx += 1;
271 }
272 let mut row_str = String::new();
273 while idx < chars.len() && chars[idx].is_ascii_digit() {
274 row_str.push(chars[idx]);
275 idx += 1;
276 }
277 if row_str.is_empty() || idx < chars.len() {
278 return None;
279 }
280 let row_val: usize = row_str.parse().ok()?;
281 Some((row_val.saturating_sub(1), row_abs))
282}
283
284fn parse_cell_pattern(chars: &[char], mut idx: usize) -> Option<(usize, usize, bool, bool, usize)> {
285 let mut col_abs = false;
286 if idx < chars.len() && chars[idx] == '$' {
287 col_abs = true;
288 idx += 1;
289 }
290
291 let mut col_str = String::new();
292 while idx < chars.len() && chars[idx].is_ascii_alphabetic() {
293 col_str.push(chars[idx]);
294 idx += 1;
295 }
296 if col_str.is_empty() || col_str.len() > 3 {
297 return None;
298 }
299
300 let mut row_abs = false;
301 if idx < chars.len() && chars[idx] == '$' {
302 row_abs = true;
303 idx += 1;
304 }
305
306 let mut row_str = String::new();
307 while idx < chars.len() && chars[idx].is_ascii_digit() {
308 row_str.push(chars[idx]);
309 idx += 1;
310 }
311 if row_str.is_empty() {
312 return None;
313 }
314
315 if idx < chars.len() && (chars[idx].is_ascii_alphabetic() || chars[idx] == '_') {
316 return None;
317 }
318
319 let (row_idx, col_idx) = parse_a1_coordinates(&col_str, &row_str);
320 Some((row_idx, col_idx, row_abs, col_abs, idx))
321}
322
323fn parse_col_pattern(chars: &[char], mut idx: usize) -> Option<(usize, bool, usize)> {
324 let mut col_abs = false;
325 if idx < chars.len() && chars[idx] == '$' {
326 col_abs = true;
327 idx += 1;
328 }
329 let mut col_str = String::new();
330 while idx < chars.len() && chars[idx].is_ascii_alphabetic() {
331 col_str.push(chars[idx]);
332 idx += 1;
333 }
334 if col_str.is_empty() || col_str.len() > 3 {
335 return None;
336 }
337 if idx < chars.len() && chars[idx].is_ascii_digit() {
338 return None;
339 }
340 if idx < chars.len() && (chars[idx].is_ascii_alphabetic() || chars[idx] == '_') {
341 return None;
342 }
343 let mut col = 0;
344 for c in col_str.chars() {
345 col = col * 26 + (c.to_ascii_uppercase() as usize - 'A' as usize + 1);
346 }
347 let col_idx = if col > 0 { col - 1 } else { 0 };
348 Some((col_idx, col_abs, idx))
349}
350
351fn parse_row_pattern(chars: &[char], mut idx: usize) -> Option<(usize, bool, usize)> {
352 let mut row_abs = false;
353 if idx < chars.len() && chars[idx] == '$' {
354 row_abs = true;
355 idx += 1;
356 }
357 let mut row_str = String::new();
358 while idx < chars.len() && chars[idx].is_ascii_digit() {
359 row_str.push(chars[idx]);
360 idx += 1;
361 }
362 if row_str.is_empty() {
363 return None;
364 }
365 if idx < chars.len() && (chars[idx].is_ascii_alphabetic() || chars[idx] == '_') {
366 return None;
367 }
368 let row_val: usize = row_str.parse().ok()?;
369 Some((row_val.saturating_sub(1), row_abs, idx))
370}
371
372#[derive(Debug, Clone)]
373enum FoundRef {
374 Cell {
375 sheet: Option<String>,
376 row: usize,
377 col: usize,
378 row_abs: bool,
379 col_abs: bool,
380 },
381 Range {
382 sheet: Option<String>,
383 start_row: usize,
384 start_col: usize,
385 end_row: usize,
386 end_col: usize,
387 start_row_abs: bool,
388 start_col_abs: bool,
389 end_row_abs: bool,
390 end_col_abs: bool,
391 },
392 Structured {
393 sheet: Option<String>,
394 column: Option<String>,
395 is_this_row: bool,
396 section: SheetSection,
397 },
398}
399
400fn parse_bracketed_term(chars: &[char], mut idx: usize) -> Option<(String, usize)> {
401 if idx >= chars.len() || chars[idx] != '[' {
402 return None;
403 }
404 idx += 1;
405 while idx < chars.len() && chars[idx].is_whitespace() {
406 idx += 1;
407 }
408 let mut quote = None;
409 if idx < chars.len() && (chars[idx] == '"' || chars[idx] == '\'') {
410 quote = Some(chars[idx]);
411 idx += 1;
412 }
413 let mut term = String::new();
414 while idx < chars.len() {
415 if let Some(q) = quote {
416 if chars[idx] == q {
417 idx += 1;
418 break;
419 }
420 } else {
421 if chars[idx] == ']' {
422 break;
423 }
424 if chars[idx] == ','
425 || chars[idx] == ':'
426 || chars[idx] == '+'
427 || chars[idx] == '-'
428 || chars[idx] == '*'
429 || chars[idx] == '/'
430 {
431 return None;
432 }
433 }
434 term.push(chars[idx]);
435 idx += 1;
436 }
437 while idx < chars.len() && chars[idx].is_whitespace() {
438 idx += 1;
439 }
440 if idx < chars.len() && chars[idx] == ']' {
441 Some((term.trim().to_string(), idx + 1))
442 } else {
443 None
444 }
445}
446
447fn parse_structured_specifier(
448 chars: &[char],
449 mut idx: usize,
450) -> Option<(Option<String>, bool, SheetSection, usize)> {
451 if idx >= chars.len() || chars[idx] != '[' {
452 return None;
453 }
454 idx += 1;
455
456 while idx < chars.len() && chars[idx].is_whitespace() {
457 idx += 1;
458 }
459
460 let mut is_this_row = false;
461 let mut section = SheetSection::Data;
462 let mut column = None;
463
464 if idx < chars.len() && chars[idx] == '@' {
465 is_this_row = true;
466 idx += 1;
467 if idx < chars.len() && chars[idx] == '[' {
468 if let Some((col_name, next_idx)) = parse_bracketed_term(chars, idx) {
469 if !col_name.is_empty() {
470 column = Some(col_name);
471 }
472 idx = next_idx;
473 } else {
474 return None;
475 }
476 } else {
477 let mut col_name = String::new();
478 while idx < chars.len() && chars[idx] != ']' {
479 col_name.push(chars[idx]);
480 idx += 1;
481 }
482 let trimmed = col_name.trim().to_string();
483 column = if trimmed.is_empty() {
484 None
485 } else {
486 Some(trimmed)
487 };
488 }
489 if idx < chars.len() && chars[idx] == ']' {
490 return Some((column, is_this_row, section, idx + 1));
491 } else {
492 return None;
493 }
494 }
495
496 if idx < chars.len() && chars[idx] == '[' {
497 if let Some((term1, next_idx)) = parse_bracketed_term(chars, idx) {
498 idx = next_idx;
499 if term1.starts_with('#') {
500 match term1.as_str() {
501 "#This Row" => is_this_row = true,
502 "#Headers" => section = SheetSection::Headers,
503 "#Totals" => section = SheetSection::Totals,
504 "#Data" => section = SheetSection::Data,
505 "#All" => section = SheetSection::All,
506 _ => {}
507 }
508 while idx < chars.len() && (chars[idx].is_whitespace() || chars[idx] == ',') {
509 idx += 1;
510 }
511 if idx < chars.len() && chars[idx] == '[' {
512 if let Some((col_name, next_idx2)) = parse_bracketed_term(chars, idx) {
513 if !col_name.is_empty() {
514 column = Some(col_name);
515 }
516 idx = next_idx2;
517 } else {
518 return None;
519 }
520 }
521 } else if !term1.is_empty() {
522 column = Some(term1);
523 }
524 } else {
525 return None;
526 }
527 while idx < chars.len() && chars[idx].is_whitespace() {
528 idx += 1;
529 }
530 if idx < chars.len() && chars[idx] == ']' {
531 return Some((column, is_this_row, section, idx + 1));
532 } else {
533 return None;
534 }
535 }
536
537 let mut term = String::new();
538 let mut quote = None;
539 if idx < chars.len() && (chars[idx] == '"' || chars[idx] == '\'') {
540 quote = Some(chars[idx]);
541 idx += 1;
542 }
543 while idx < chars.len() {
544 if let Some(q) = quote {
545 if chars[idx] == q {
546 idx += 1;
547 break;
548 }
549 } else {
550 if chars[idx] == ']' {
551 break;
552 }
553 if chars[idx] == ','
554 || chars[idx] == ':'
555 || chars[idx] == '+'
556 || chars[idx] == '-'
557 || chars[idx] == '*'
558 || chars[idx] == '/'
559 {
560 return None;
561 }
562 }
563 term.push(chars[idx]);
564 idx += 1;
565 }
566 term = term.trim().to_string();
567 if term.starts_with('#') {
568 match term.as_str() {
569 "#This Row" => is_this_row = true,
570 "#Headers" => section = SheetSection::Headers,
571 "#Totals" => section = SheetSection::Totals,
572 "#Data" => section = SheetSection::Data,
573 "#All" => section = SheetSection::All,
574 _ => {}
575 }
576 } else if !term.is_empty() {
577 column = Some(term);
578 }
579
580 if idx < chars.len() && chars[idx] == ']' {
581 return Some((column, is_this_row, section, idx + 1));
582 }
583
584 None
585}
586
587fn try_parse_ref(chars: &[char], start_idx: usize) -> Option<(FoundRef, usize)> {
588 if start_idx > 0 {
589 let prev = chars[start_idx - 1];
590 if prev.is_ascii_alphanumeric() || prev == '_' {
591 return None;
592 }
593 }
594 let mut idx = start_idx;
595 let mut sheet = None;
596
597 let mut has_explicit_table = false;
598 if idx < chars.len() && chars[idx] == '\'' {
599 let mut t_name = String::new();
600 let mut j = idx + 1;
601 while j < chars.len() && chars[j] != '\'' {
602 t_name.push(chars[j]);
603 j += 1;
604 }
605 if j < chars.len() && chars[j] == '\'' {
606 if j + 1 < chars.len() && chars[j + 1] == '!' {
607 sheet = Some(t_name);
608 idx = j + 2;
609 has_explicit_table = true;
610 } else if j + 1 < chars.len() && chars[j + 1] == '[' {
611 sheet = Some(t_name);
612 idx = j + 1;
613 has_explicit_table = true;
614 }
615 }
616 }
617
618 if !has_explicit_table {
619 let mut t_name = String::new();
620 let mut j = idx;
621 if j < chars.len() && (chars[j].is_ascii_alphabetic() || chars[j] == '_') {
622 while j < chars.len()
623 && (chars[j].is_ascii_alphanumeric() || chars[j] == '_' || chars[j] == '.')
624 {
625 t_name.push(chars[j]);
626 j += 1;
627 }
628 if j < chars.len() && chars[j] == '!' {
629 if t_name != "iloc" && t_name != "get" {
630 sheet = Some(t_name);
631 idx = j + 1;
632 }
633 } else if j < chars.len() && chars[j] == '[' && t_name != "iloc" && t_name != "get" {
634 let mut k = j + 1;
635 let mut quote = None;
636 if k < chars.len() && (chars[k] == '"' || chars[k] == '\'') {
637 quote = Some(chars[k]);
638 k += 1;
639 }
640 let mut is_column = true;
641 let mut has_chars = false;
642 while k < chars.len() {
643 if let Some(q) = quote {
644 if chars[k] == q {
645 break;
646 }
647 } else {
648 if chars[k] == ']' {
649 break;
650 }
651 if chars[k] == ':'
652 || chars[k] == ','
653 || chars[k] == '+'
654 || chars[k] == '-'
655 || chars[k] == '*'
656 || chars[k] == '/'
657 {
658 is_column = false;
659 break;
660 }
661 }
662 has_chars = true;
663 k += 1;
664 }
665 if is_column && has_chars {
666 sheet = Some(t_name);
667 idx = j;
668 }
669 }
670 }
671 }
672
673 if idx < chars.len()
674 && chars[idx] == '['
675 && let Some((column, is_this_row, section, next_idx)) =
676 parse_structured_specifier(chars, idx)
677 {
678 return Some((
679 FoundRef::Structured {
680 sheet,
681 column,
682 is_this_row,
683 section,
684 },
685 next_idx,
686 ));
687 }
688
689 if let Some((start_col, start_col_abs, next_idx)) = parse_col_pattern(chars, idx)
690 && next_idx < chars.len()
691 && chars[next_idx] == ':'
692 && let Some((end_col, end_col_abs, end_idx)) = parse_col_pattern(chars, next_idx + 1)
693 {
694 return Some((
695 FoundRef::Range {
696 sheet: sheet.clone(),
697 start_row: 0,
698 start_col,
699 end_row: usize::MAX,
700 end_col,
701 start_row_abs: true,
702 start_col_abs,
703 end_row_abs: true,
704 end_col_abs,
705 },
706 end_idx,
707 ));
708 }
709
710 if let Some((start_row, start_row_abs, next_idx)) = parse_row_pattern(chars, idx)
711 && next_idx < chars.len()
712 && chars[next_idx] == ':'
713 && let Some((end_row, end_row_abs, end_idx)) = parse_row_pattern(chars, next_idx + 1)
714 {
715 return Some((
716 FoundRef::Range {
717 sheet,
718 start_row,
719 start_col: 0,
720 end_row,
721 end_col: usize::MAX,
722 start_row_abs,
723 start_col_abs: true,
724 end_row_abs,
725 end_col_abs: true,
726 },
727 end_idx,
728 ));
729 }
730
731 if let Some((start_row, start_col, start_row_abs, start_col_abs, next_idx)) =
732 parse_cell_pattern(chars, idx)
733 {
734 if next_idx < chars.len()
735 && chars[next_idx] == ':'
736 && let Some((end_row, end_col, end_row_abs, end_col_abs, end_idx)) =
737 parse_cell_pattern(chars, next_idx + 1)
738 {
739 return Some((
740 FoundRef::Range {
741 sheet,
742 start_row,
743 start_col,
744 end_row,
745 end_col,
746 start_row_abs,
747 start_col_abs,
748 end_row_abs,
749 end_col_abs,
750 },
751 end_idx,
752 ));
753 }
754
755 return Some((
756 FoundRef::Cell {
757 sheet,
758 row: start_row,
759 col: start_col,
760 row_abs: start_row_abs,
761 col_abs: start_col_abs,
762 },
763 next_idx,
764 ));
765 }
766
767 None
768}
769
770pub fn compile_formula(code: &str, sheets: &[Sheet]) -> CompiledFormula {
771 let chars: Vec<char> = code.chars().collect();
772 let mut parts = Vec::new();
773 let mut last_idx = 0;
774
775 let mut in_quote = None;
776 let mut i = 0;
777 while i < chars.len() {
778 let c = chars[i];
779 if let Some(q) = in_quote {
780 if c == '\\' {
781 i = (i + 2).min(chars.len());
782 continue;
783 } else if c == q {
784 in_quote = None;
785 }
786 i += 1;
787 continue;
788 }
789
790 if let Some((found_ref, next_i)) = try_parse_ref(&chars, i) {
791 if i > last_idx {
792 let text: String = chars[last_idx..i].iter().collect();
793 parts.push(FormulaPart::Text(text));
794 }
795
796 match found_ref {
797 FoundRef::Cell {
798 sheet,
799 row,
800 col,
801 row_abs,
802 col_abs,
803 } => {
804 let sheet_id = if let Some(ref name) = sheet {
805 if let Some(t) = sheets.iter().find(|t| t.name == *name) {
806 t.id
807 } else if !sheets.is_empty() {
808 sheets[0].id
809 } else {
810 0
811 }
812 } else if !sheets.is_empty() {
813 sheets[0].id
814 } else {
815 0
816 };
817
818 parts.push(FormulaPart::SheetReference {
819 sheet_id,
820 row,
821 col,
822 row_ref_type: if row_abs {
823 RefType::Absolute
824 } else {
825 RefType::Relative
826 },
827 col_ref_type: if col_abs {
828 RefType::Absolute
829 } else {
830 RefType::Relative
831 },
832 });
833 }
834 FoundRef::Range {
835 sheet,
836 start_row,
837 start_col,
838 end_row,
839 end_col,
840 start_row_abs,
841 start_col_abs,
842 end_row_abs,
843 end_col_abs,
844 } => {
845 let sheet_id = if let Some(ref name) = sheet {
846 if let Some(t) = sheets.iter().find(|t| t.name == *name) {
847 t.id
848 } else if !sheets.is_empty() {
849 sheets[0].id
850 } else {
851 0
852 }
853 } else if !sheets.is_empty() {
854 sheets[0].id
855 } else {
856 0
857 };
858
859 parts.push(FormulaPart::RangeReference {
860 sheet_id,
861 start_row,
862 start_col,
863 end_row,
864 end_col,
865 start_row_ref_type: if start_row_abs {
866 RefType::Absolute
867 } else {
868 RefType::Relative
869 },
870 start_col_ref_type: if start_col_abs {
871 RefType::Absolute
872 } else {
873 RefType::Relative
874 },
875 end_row_ref_type: if end_row_abs {
876 RefType::Absolute
877 } else {
878 RefType::Relative
879 },
880 end_col_ref_type: if end_col_abs {
881 RefType::Absolute
882 } else {
883 RefType::Relative
884 },
885 });
886 }
887 FoundRef::Structured {
888 sheet,
889 column,
890 is_this_row,
891 section,
892 } => {
893 let default_sheet_name = sheets.first().map(|s| s.name.clone());
894 let ref_name = sheet.clone().or(default_sheet_name);
895 let matches_real_table = ref_name.is_some_and(|name| {
896 sheets
897 .iter()
898 .any(|s| s.tables.iter().any(|t| t.name.eq_ignore_ascii_case(&name)))
899 });
900
901 if matches_real_table {
902 let text: String = chars[i..next_i].iter().collect();
903 parts.push(FormulaPart::Text(text));
904 } else {
905 let sheet_id = if let Some(ref name) = sheet {
906 if let Some(named_sheet) = sheets.iter().find(|s| s.name == *name) {
907 named_sheet.id
908 } else if !sheets.is_empty() {
909 sheets[0].id
910 } else {
911 0
912 }
913 } else if !sheets.is_empty() {
914 sheets[0].id
915 } else {
916 0
917 };
918
919 let col_id = if let Some(ref col_name) = column {
920 if let Some(sheet_obj) = sheets.iter().find(|s| s.id == sheet_id) {
921 if let Some(col) =
922 sheet_obj.columns.iter().find(|c| c.name == *col_name)
923 {
924 Some(col.id)
925 } else {
926 Some(generate_unique_id())
927 }
928 } else {
929 Some(generate_unique_id())
930 }
931 } else {
932 None
933 };
934
935 parts.push(FormulaPart::StructuredReference {
936 sheet_id,
937 col_id,
938 is_this_row,
939 section,
940 });
941 }
942 }
943 }
944
945 i = next_i;
946 last_idx = i;
947 } else {
948 if c == '"' || c == '\'' {
949 in_quote = Some(c);
950 }
951 i += 1;
952 }
953 }
954
955 if i > last_idx {
956 let text: String = chars[last_idx..i].iter().collect();
957 parts.push(FormulaPart::Text(text));
958 }
959
960 CompiledFormula { parts }
961}
962
963pub fn serialize_formula(formula: &CompiledFormula, sheets: &[Sheet]) -> String {
964 let get_sheet_name = |sheet_id: u64, sheets: &[Sheet]| -> String {
965 if let Some(sheet) = sheets.iter().find(|s| s.id == sheet_id) {
966 if sheet.name.contains(' ') {
967 format!("'{}'", sheet.name)
968 } else {
969 sheet.name.clone()
970 }
971 } else {
972 "table_deleted".to_string()
973 }
974 };
975
976 let mut result = String::new();
977 for part in &formula.parts {
978 match part {
979 FormulaPart::Text(s) => result.push_str(s),
980 FormulaPart::SheetReference {
981 sheet_id,
982 row,
983 col,
984 row_ref_type,
985 col_ref_type,
986 } => {
987 let col_letter = col_idx_to_letters(*col);
988 let r_prefix = match row_ref_type {
989 RefType::Absolute => "$",
990 RefType::Relative => "",
991 };
992 let c_prefix = match col_ref_type {
993 RefType::Absolute => "$",
994 RefType::Relative => "",
995 };
996
997 let has_prefix = if sheets.is_empty() {
998 false
999 } else {
1000 *sheet_id != sheets[0].id
1001 };
1002
1003 if has_prefix {
1004 let sheet_name = get_sheet_name(*sheet_id, sheets);
1005 result.push_str(&format!(
1006 "{}!{}{}{}{}",
1007 sheet_name,
1008 c_prefix,
1009 col_letter,
1010 r_prefix,
1011 row + 1
1012 ));
1013 } else {
1014 result.push_str(&format!(
1015 "{}{}{}{}",
1016 c_prefix,
1017 col_letter,
1018 r_prefix,
1019 row + 1
1020 ));
1021 }
1022 }
1023 FormulaPart::RangeReference {
1024 sheet_id,
1025 start_row,
1026 start_col,
1027 end_row,
1028 end_col,
1029 start_row_ref_type,
1030 start_col_ref_type,
1031 end_row_ref_type,
1032 end_col_ref_type,
1033 } => {
1034 let has_prefix = if sheets.is_empty() {
1035 false
1036 } else {
1037 *sheet_id != sheets[0].id
1038 };
1039
1040 if *end_row == usize::MAX {
1041 let start_col_letter = col_idx_to_letters(*start_col);
1042 let end_col_letter = col_idx_to_letters(*end_col);
1043 let sc_prefix = match start_col_ref_type {
1044 RefType::Absolute => "$",
1045 RefType::Relative => "",
1046 };
1047 let ec_prefix = match end_col_ref_type {
1048 RefType::Absolute => "$",
1049 RefType::Relative => "",
1050 };
1051 if has_prefix {
1052 let sheet_name = get_sheet_name(*sheet_id, sheets);
1053 result.push_str(&format!(
1054 "{}!{}{}:{}{}",
1055 sheet_name, sc_prefix, start_col_letter, ec_prefix, end_col_letter,
1056 ));
1057 } else {
1058 result.push_str(&format!(
1059 "{}{}:{}{}",
1060 sc_prefix, start_col_letter, ec_prefix, end_col_letter,
1061 ));
1062 }
1063 } else if *end_col == usize::MAX {
1064 let sr_prefix = match start_row_ref_type {
1065 RefType::Absolute => "$",
1066 RefType::Relative => "",
1067 };
1068 let er_prefix = match end_row_ref_type {
1069 RefType::Absolute => "$",
1070 RefType::Relative => "",
1071 };
1072 if has_prefix {
1073 let sheet_name = get_sheet_name(*sheet_id, sheets);
1074 result.push_str(&format!(
1075 "{}!{}{}:{}{}",
1076 sheet_name,
1077 sr_prefix,
1078 start_row + 1,
1079 er_prefix,
1080 end_row + 1,
1081 ));
1082 } else {
1083 result.push_str(&format!(
1084 "{}{}:{}{}",
1085 sr_prefix,
1086 start_row + 1,
1087 er_prefix,
1088 end_row + 1,
1089 ));
1090 }
1091 } else {
1092 let start_col_letter = col_idx_to_letters(*start_col);
1093 let end_col_letter = col_idx_to_letters(*end_col);
1094 let sc_prefix = match start_col_ref_type {
1095 RefType::Absolute => "$",
1096 RefType::Relative => "",
1097 };
1098 let ec_prefix = match end_col_ref_type {
1099 RefType::Absolute => "$",
1100 RefType::Relative => "",
1101 };
1102 let sr_prefix = match start_row_ref_type {
1103 RefType::Absolute => "$",
1104 RefType::Relative => "",
1105 };
1106 let er_prefix = match end_row_ref_type {
1107 RefType::Absolute => "$",
1108 RefType::Relative => "",
1109 };
1110
1111 if has_prefix {
1112 let sheet_name = get_sheet_name(*sheet_id, sheets);
1113 result.push_str(&format!(
1114 "{}!{}{}{}{}:{}{}{}{}",
1115 sheet_name,
1116 sc_prefix,
1117 start_col_letter,
1118 sr_prefix,
1119 start_row + 1,
1120 ec_prefix,
1121 end_col_letter,
1122 er_prefix,
1123 end_row + 1,
1124 ));
1125 } else {
1126 result.push_str(&format!(
1127 "{}{}{}{}:{}{}{}{}",
1128 sc_prefix,
1129 start_col_letter,
1130 sr_prefix,
1131 start_row + 1,
1132 ec_prefix,
1133 end_col_letter,
1134 er_prefix,
1135 end_row + 1,
1136 ));
1137 }
1138 }
1139 }
1140 FormulaPart::ColumnReference { sheet_id, col_id } => {
1141 let sheet_name = get_sheet_name(*sheet_id, sheets);
1142 let col_name = if let Some(sheet) = sheets.iter().find(|s| s.id == *sheet_id) {
1143 if let Some(col) = sheet.columns.iter().find(|c| c.id == *col_id) {
1144 col.name.clone()
1145 } else {
1146 "col_deleted".to_string()
1147 }
1148 } else {
1149 "col_deleted".to_string()
1150 };
1151 result.push_str(&format!("{}[{}]", sheet_name, col_name));
1152 }
1153 FormulaPart::StructuredReference {
1154 sheet_id,
1155 col_id,
1156 is_this_row,
1157 section,
1158 } => {
1159 let sheet_name = get_sheet_name(*sheet_id, sheets);
1160 let col_name = if let Some(col_id_val) = col_id {
1161 if let Some(sheet) = sheets.iter().find(|s| s.id == *sheet_id) {
1162 if let Some(col) = sheet.columns.iter().find(|c| c.id == *col_id_val) {
1163 col.name.clone()
1164 } else {
1165 "col_deleted".to_string()
1166 }
1167 } else {
1168 "col_deleted".to_string()
1169 }
1170 } else {
1171 String::new()
1172 };
1173
1174 let has_prefix = if sheets.is_empty() {
1175 false
1176 } else {
1177 *sheet_id != sheets[0].id
1178 };
1179
1180 let prefix = if has_prefix {
1181 sheet_name
1182 } else {
1183 String::new()
1184 };
1185
1186 result.push_str(&render_structured_ref_text(
1187 &prefix,
1188 &col_name,
1189 *is_this_row,
1190 *section,
1191 ));
1192 }
1193 }
1194 }
1195 result
1196}
1197
1198fn render_structured_ref_text(
1199 prefix: &str,
1200 col_name: &str,
1201 is_this_row: bool,
1202 section: SheetSection,
1203) -> String {
1204 if is_this_row {
1205 if col_name.is_empty() {
1206 format!("{}[@]", prefix)
1207 } else {
1208 format!("{}[@{}]", prefix, col_name)
1209 }
1210 } else {
1211 match section {
1212 SheetSection::Headers => {
1213 if col_name.is_empty() {
1214 format!("{}[#Headers]", prefix)
1215 } else {
1216 format!("{}[[#Headers], [{}]]", prefix, col_name)
1217 }
1218 }
1219 SheetSection::Totals => {
1220 if col_name.is_empty() {
1221 format!("{}[#Totals]", prefix)
1222 } else {
1223 format!("{}[[#Totals], [{}]]", prefix, col_name)
1224 }
1225 }
1226 SheetSection::Data => {
1227 if col_name.is_empty() {
1228 format!("{}[#Data]", prefix)
1229 } else {
1230 format!("{}[{}]", prefix, col_name)
1231 }
1232 }
1233 SheetSection::All => {
1234 if col_name.is_empty() {
1235 format!("{}[#All]", prefix)
1236 } else {
1237 format!("{}[[#All], [{}]]", prefix, col_name)
1238 }
1239 }
1240 }
1241 }
1242}
1243
1244pub fn rewrite_structured_table_reference(
1246 formula_src: &str,
1247 table_name: &str,
1248 new_table_name: Option<&str>,
1249 col_rename: Option<(&str, &str)>,
1250) -> Option<String> {
1251 if !formula_src.starts_with('=') {
1252 return None;
1253 }
1254 let chars: Vec<char> = formula_src.chars().collect();
1255 let mut result = String::new();
1256 let mut last_idx = 0;
1257 let mut changed = false;
1258
1259 let mut in_quote = None;
1260 let mut i = 0;
1261 while i < chars.len() {
1262 let c = chars[i];
1263 if let Some(q) = in_quote {
1264 if c == '\\' {
1265 i = (i + 2).min(chars.len());
1266 continue;
1267 } else if c == q {
1268 in_quote = None;
1269 }
1270 i += 1;
1271 continue;
1272 }
1273
1274 if let Some((found_ref, next_i)) = try_parse_ref(&chars, i) {
1275 if let FoundRef::Structured {
1276 sheet,
1277 column,
1278 is_this_row,
1279 section,
1280 } = &found_ref
1281 {
1282 let matches_table = sheet
1283 .as_deref()
1284 .is_some_and(|s| s.eq_ignore_ascii_case(table_name));
1285 if matches_table {
1286 let rendered_table_name = new_table_name.unwrap_or(table_name);
1287 let rendered_col_name = match (column, col_rename) {
1288 (Some(col), Some((old_col, new_col)))
1289 if col.eq_ignore_ascii_case(old_col) =>
1290 {
1291 new_col.to_string()
1292 }
1293 (Some(col), _) => col.clone(),
1294 (None, _) => String::new(),
1295 };
1296 let rendered = render_structured_ref_text(
1297 rendered_table_name,
1298 &rendered_col_name,
1299 *is_this_row,
1300 *section,
1301 );
1302
1303 let original: String = chars[i..next_i].iter().collect();
1304 if rendered != original {
1305 result.push_str(&chars[last_idx..i].iter().collect::<String>());
1306 result.push_str(&rendered);
1307 changed = true;
1308 last_idx = next_i;
1309 }
1310 }
1311 }
1312 i = next_i;
1313 } else {
1314 if c == '"' || c == '\'' {
1315 in_quote = Some(c);
1316 }
1317 i += 1;
1318 }
1319 }
1320
1321 if !changed {
1322 return None;
1323 }
1324 result.push_str(&chars[last_idx..].iter().collect::<String>());
1325 Some(result)
1326}
1327
1328fn take_number_exponent(chars: &[char], i: &mut usize, num_str: &mut String) {
1329 if *i >= chars.len() || !matches!(chars[*i], 'e' | 'E') {
1330 return;
1331 }
1332 let mut peek = *i + 1;
1333 if peek < chars.len() && matches!(chars[peek], '+' | '-') {
1334 peek += 1;
1335 }
1336 if peek >= chars.len() || !chars[peek].is_ascii_digit() {
1337 return;
1338 }
1339 num_str.push('E');
1340 if matches!(chars[*i + 1], '+' | '-') {
1341 num_str.push(chars[*i + 1]);
1342 }
1343 *i = peek;
1344 while *i < chars.len() && chars[*i].is_ascii_digit() {
1345 num_str.push(chars[*i]);
1346 *i += 1;
1347 }
1348}
1349
1350fn match_error_code(chars: &[char], start: usize) -> Option<&'static str> {
1351 crate::core::engine::result_data::EXCEL_ERROR_CODES
1352 .iter()
1353 .filter(|code| {
1354 let wanted: Vec<char> = code.chars().collect();
1355 chars.len() - start >= wanted.len()
1356 && chars[start..start + wanted.len()]
1357 .iter()
1358 .zip(&wanted)
1359 .all(|(a, b)| a.eq_ignore_ascii_case(b))
1360 })
1361 .max_by_key(|code| code.len())
1362 .copied()
1363}
1364
1365pub fn lex_eval(input: &str) -> Result<Vec<EvalToken>, String> {
1366 let chars: Vec<char> = input.chars().collect();
1367 let mut tokens = Vec::new();
1368 let mut i = 0;
1369
1370 while i < chars.len() {
1371 let c = chars[i];
1372 if c.is_whitespace() {
1373 i += 1;
1374 continue;
1375 }
1376
1377 if c == '"' || c == '\'' {
1378 let quote = c;
1379 i += 1;
1380 let mut s = String::new();
1381 while i < chars.len() {
1382 if chars[i] == '\\' && i + 1 < chars.len() {
1383 s.push(chars[i + 1]);
1384 i += 2;
1385 } else if chars[i] == quote {
1386 i += 1;
1387 break;
1388 } else {
1389 s.push(chars[i]);
1390 i += 1;
1391 }
1392 }
1393 tokens.push(EvalToken::String(s));
1394 continue;
1395 }
1396
1397 if c == '['
1398 && let Some((column, is_this_row, section, next_i)) =
1399 parse_structured_specifier(&chars, i)
1400 {
1401 let mut sheet = None;
1402 if let Some(EvalToken::Identifier(_) | EvalToken::String(_)) = tokens.last() {
1403 let last = tokens.pop().unwrap();
1404 sheet = match last {
1405 EvalToken::Identifier(s) => Some(s),
1406 EvalToken::String(s) => Some(s),
1407 _ => unreachable!(),
1408 };
1409 }
1410 tokens.push(EvalToken::StructuredRef {
1411 sheet,
1412 column,
1413 is_this_row,
1414 section,
1415 });
1416 i = next_i;
1417 continue;
1418 }
1419
1420 if c == '#'
1421 && let Some(code) = match_error_code(&chars, i)
1422 {
1423 i += code.chars().count();
1424 tokens.push(EvalToken::Error(code));
1425 continue;
1426 }
1427
1428 match c {
1429 '(' => {
1430 tokens.push(EvalToken::OpenParen);
1431 i += 1;
1432 continue;
1433 }
1434 ')' => {
1435 tokens.push(EvalToken::CloseParen);
1436 i += 1;
1437 continue;
1438 }
1439 '[' => {
1440 tokens.push(EvalToken::OpenBracket);
1441 i += 1;
1442 continue;
1443 }
1444 ']' => {
1445 tokens.push(EvalToken::CloseBracket);
1446 i += 1;
1447 continue;
1448 }
1449 ',' => {
1450 tokens.push(EvalToken::Comma);
1451 i += 1;
1452 continue;
1453 }
1454 ':' => {
1455 tokens.push(EvalToken::Colon);
1456 i += 1;
1457 continue;
1458 }
1459 _ => {}
1460 }
1461
1462 if c == '<' {
1463 if i + 1 < chars.len() && chars[i + 1] == '>' {
1464 tokens.push(EvalToken::Op(Op::Ne));
1465 i += 2;
1466 } else if i + 1 < chars.len() && chars[i + 1] == '=' {
1467 tokens.push(EvalToken::Op(Op::Le));
1468 i += 2;
1469 } else {
1470 tokens.push(EvalToken::Op(Op::Lt));
1471 i += 1;
1472 }
1473 continue;
1474 }
1475 if c == '>' {
1476 if i + 1 < chars.len() && chars[i + 1] == '=' {
1477 tokens.push(EvalToken::Op(Op::Ge));
1478 i += 2;
1479 } else {
1480 tokens.push(EvalToken::Op(Op::Gt));
1481 i += 1;
1482 }
1483 continue;
1484 }
1485 if c == '=' {
1486 if i + 1 < chars.len() && chars[i + 1] == '=' {
1487 tokens.push(EvalToken::Op(Op::Eq));
1488 i += 2;
1489 } else {
1490 tokens.push(EvalToken::Op(Op::Eq));
1491 i += 1;
1492 }
1493 continue;
1494 }
1495 if c == '!' {
1496 if i + 1 < chars.len() && chars[i + 1] == '=' {
1497 tokens.push(EvalToken::Op(Op::Ne));
1498 i += 2;
1499 } else {
1500 tokens.push(EvalToken::Exclamation);
1501 i += 1;
1502 }
1503 continue;
1504 }
1505
1506 match c {
1507 '+' => {
1508 tokens.push(EvalToken::Op(Op::Add));
1509 i += 1;
1510 continue;
1511 }
1512 '-' => {
1513 tokens.push(EvalToken::Op(Op::Sub));
1514 i += 1;
1515 continue;
1516 }
1517 '*' => {
1518 if i + 1 < chars.len() && chars[i + 1] == '*' {
1519 tokens.push(EvalToken::Op(Op::Exp));
1520 i += 2;
1521 } else {
1522 tokens.push(EvalToken::Op(Op::Mul));
1523 i += 1;
1524 }
1525 continue;
1526 }
1527 '/' => {
1528 tokens.push(EvalToken::Op(Op::Div));
1529 i += 1;
1530 continue;
1531 }
1532 '^' => {
1533 tokens.push(EvalToken::Op(Op::Exp));
1534 i += 1;
1535 continue;
1536 }
1537 _ => {}
1538 }
1539
1540 if c == '.' {
1541 if i + 1 < chars.len() && chars[i + 1].is_ascii_digit() {
1542 let mut num_str = String::new();
1543 num_str.push('.');
1544 i += 1;
1545 while i < chars.len() && chars[i].is_ascii_digit() {
1546 num_str.push(chars[i]);
1547 i += 1;
1548 }
1549 take_number_exponent(&chars, &mut i, &mut num_str);
1550 if let Ok(val) = num_str.parse::<f64>() {
1551 tokens.push(EvalToken::Number(val));
1552 } else {
1553 return Err(format!("Invalid number: {}", num_str));
1554 }
1555 } else {
1556 tokens.push(EvalToken::Dot);
1557 i += 1;
1558 }
1559 continue;
1560 }
1561
1562 if c.is_ascii_digit() {
1563 let mut num_str = String::new();
1564 while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
1565 num_str.push(chars[i]);
1566 i += 1;
1567 }
1568 take_number_exponent(&chars, &mut i, &mut num_str);
1569 if let Ok(val) = num_str.parse::<f64>() {
1570 tokens.push(EvalToken::Number(val));
1571 } else {
1572 return Err(format!("Invalid number: {}", num_str));
1573 }
1574 continue;
1575 }
1576
1577 if c.is_ascii_alphabetic() || c == '_' || c == '$' {
1578 let mut id_str = String::new();
1579 while i < chars.len()
1580 && (chars[i].is_ascii_alphanumeric()
1581 || chars[i] == '_'
1582 || chars[i] == '$'
1583 || chars[i] == '.')
1584 {
1585 id_str.push(chars[i]);
1586 i += 1;
1587 }
1588
1589 let upper = id_str.to_uppercase();
1590 let mut lookahead = i;
1591 while lookahead < chars.len() && chars[lookahead].is_whitespace() {
1592 lookahead += 1;
1593 }
1594 let followed_by_paren = lookahead < chars.len() && chars[lookahead] == '(';
1595 if upper == "TRUE" && !followed_by_paren {
1596 tokens.push(EvalToken::Boolean(true));
1597 } else if upper == "FALSE" && !followed_by_paren {
1598 tokens.push(EvalToken::Boolean(false));
1599 } else {
1600 tokens.push(EvalToken::Identifier(id_str));
1601 }
1602 continue;
1603 }
1604
1605 return Err(format!("Unexpected character: {}", c));
1606 }
1607
1608 Ok(tokens)
1609}
1610
1611fn ref_token_text(tok: &EvalToken) -> Option<String> {
1612 match tok {
1613 EvalToken::Identifier(s) => Some(s.clone()),
1614 EvalToken::Number(n) if n.is_finite() && n.fract() == 0.0 && *n >= 1.0 => {
1615 Some(format!("{n:.0}"))
1616 }
1617 _ => None,
1618 }
1619}
1620
1621fn range_ref_from_texts(sheet: Option<String>, start: &str, end: &str) -> Result<Expr, String> {
1622 if let (
1623 Some((s_row, s_col, s_row_abs, s_col_abs)),
1624 Some((e_row, e_col, e_row_abs, e_col_abs)),
1625 ) = (parse_cell_ref_parts(start), parse_cell_ref_parts(end))
1626 {
1627 Ok(Expr::RangeRef {
1628 sheet,
1629 start_row: s_row,
1630 start_col: s_col,
1631 end_row: e_row,
1632 end_col: e_col,
1633 start_row_abs: s_row_abs,
1634 start_col_abs: s_col_abs,
1635 end_row_abs: e_row_abs,
1636 end_col_abs: e_col_abs,
1637 })
1638 } else if let (Some((s_col, s_col_abs)), Some((e_col, e_col_abs))) =
1639 (parse_column_ref(start), parse_column_ref(end))
1640 {
1641 Ok(Expr::RangeRef {
1642 sheet,
1643 start_row: 0,
1644 start_col: s_col,
1645 end_row: usize::MAX,
1646 end_col: e_col,
1647 start_row_abs: true,
1648 start_col_abs: s_col_abs,
1649 end_row_abs: true,
1650 end_col_abs: e_col_abs,
1651 })
1652 } else if let (Some((s_row, s_row_abs)), Some((e_row, e_row_abs))) =
1653 (parse_row_ref(start), parse_row_ref(end))
1654 {
1655 Ok(Expr::RangeRef {
1656 sheet,
1657 start_row: s_row,
1658 start_col: 0,
1659 end_row: e_row,
1660 end_col: usize::MAX,
1661 start_row_abs: s_row_abs,
1662 start_col_abs: true,
1663 end_row_abs: e_row_abs,
1664 end_col_abs: true,
1665 })
1666 } else {
1667 Err(format!("Invalid range reference: {start}:{end}"))
1668 }
1669}
1670
1671struct Parser<'a> {
1672 tokens: &'a [EvalToken],
1673 pos: usize,
1674}
1675
1676impl<'a> Parser<'a> {
1677 fn new(tokens: &'a [EvalToken]) -> Self {
1678 Self { tokens, pos: 0 }
1679 }
1680
1681 fn peek(&self) -> Option<&EvalToken> {
1682 self.tokens.get(self.pos)
1683 }
1684
1685 fn next(&mut self) -> Option<&EvalToken> {
1686 if self.pos < self.tokens.len() {
1687 let tok = &self.tokens[self.pos];
1688 self.pos += 1;
1689 Some(tok)
1690 } else {
1691 None
1692 }
1693 }
1694
1695 fn consume(&mut self, expected: EvalToken) -> Result<(), String> {
1696 match self.next() {
1697 Some(tok) if *tok == expected => Ok(()),
1698 Some(tok) => Err(format!("Expected {:?}, got {:?}", expected, tok)),
1699 None => Err(format!("Expected {:?}, got EOF", expected)),
1700 }
1701 }
1702
1703 fn parse(&mut self) -> Result<Expr, String> {
1704 let mut lhs = self.parse_binary(0)?;
1705
1706 while let Some(tok) = self.peek() {
1707 match tok {
1708 EvalToken::OpenBracket => {
1709 self.next();
1710 let mut inner_tokens = Vec::new();
1711 let mut depth = 1;
1712 while let Some(t) = self.next() {
1713 if *t == EvalToken::OpenBracket {
1714 depth += 1;
1715 inner_tokens.push(t.clone());
1716 } else if *t == EvalToken::CloseBracket {
1717 depth -= 1;
1718 if depth == 0 {
1719 break;
1720 }
1721 inner_tokens.push(t.clone());
1722 } else {
1723 inner_tokens.push(t.clone());
1724 }
1725 }
1726
1727 if let Some(colon_pos) =
1728 inner_tokens.iter().position(|t| *t == EvalToken::Colon)
1729 {
1730 let start_toks = &inner_tokens[0..colon_pos];
1731 let end_toks = &inner_tokens[colon_pos + 1..];
1732
1733 let start_val = if start_toks.is_empty() {
1734 None
1735 } else {
1736 let mut p = Parser::new(start_toks);
1737 Some(p.parse()?)
1738 };
1739
1740 let end_val = if end_toks.is_empty() {
1741 None
1742 } else {
1743 let mut p = Parser::new(end_toks);
1744 Some(p.parse()?)
1745 };
1746
1747 lhs = Expr::Slice {
1748 expr: Box::new(lhs),
1749 start: start_val.map(Box::new),
1750 end: end_val.map(Box::new),
1751 };
1752 } else {
1753 let mut p = Parser::new(&inner_tokens);
1754 let index_expr = p.parse()?;
1755
1756 if let Expr::Identifier(ref sheet_name) = lhs {
1757 let col_name_opt = match &index_expr {
1758 Expr::String(s) => Some(s.clone()),
1759 Expr::Identifier(s) => Some(s.clone()),
1760 _ => None,
1761 };
1762 if let Some(col_name) = col_name_opt {
1763 lhs = Expr::StructuredRef {
1764 sheet: Some(sheet_name.clone()),
1765 column: Some(col_name),
1766 is_this_row: false,
1767 section: SheetSection::Data,
1768 };
1769 continue;
1770 }
1771 }
1772
1773 lhs = Expr::FunctionCall {
1774 name: "INDEX".to_string(),
1775 args: vec![lhs, index_expr],
1776 };
1777 }
1778 }
1779 EvalToken::Dot => {
1780 return Err("Dot member access is not supported".to_string());
1781 }
1782 _ => break,
1783 }
1784 }
1785
1786 Ok(lhs)
1787 }
1788
1789 fn parse_binary(&mut self, min_prec: u8) -> Result<Expr, String> {
1790 let mut lhs = self.parse_prefix()?;
1791
1792 while let Some(tok) = self.peek() {
1793 let op = match tok {
1794 EvalToken::Op(o) => *o,
1795 _ => break,
1796 };
1797
1798 let prec = op_precedence(op);
1799 if prec < min_prec {
1800 break;
1801 }
1802
1803 self.next();
1804
1805 let rhs = self.parse_binary(prec + 1)?;
1806 lhs = Expr::BinaryOp {
1807 op,
1808 left: Box::new(lhs),
1809 right: Box::new(rhs),
1810 };
1811 }
1812
1813 Ok(lhs)
1814 }
1815
1816 fn parse_prefix(&mut self) -> Result<Expr, String> {
1817 let tok = self
1818 .next()
1819 .ok_or_else(|| "Unexpected EOF".to_string())?
1820 .clone();
1821 match tok {
1822 EvalToken::Number(val) => {
1823 if self.peek() == Some(&EvalToken::Colon)
1824 && val.is_finite()
1825 && val.fract() == 0.0
1826 && val >= 1.0
1827 {
1828 self.next();
1829 let end_tok = self
1830 .next()
1831 .ok_or_else(|| "Expected row reference after `:`".to_string())?;
1832 let start_str = format!("{val:.0}");
1833 let end_str = ref_token_text(end_tok).ok_or_else(|| {
1834 format!("Expected row reference after `:`, got {:?}", end_tok)
1835 })?;
1836 return range_ref_from_texts(None, &start_str, &end_str);
1837 }
1838 Ok(Expr::Number(val))
1839 }
1840 EvalToken::String(val) => {
1841 if self.peek() == Some(&EvalToken::Exclamation) {
1842 self.next();
1843 let target_tok = self
1844 .next()
1845 .ok_or_else(|| "Expected cell or column reference after `!`".to_string())?
1846 .clone();
1847 let target_str = ref_token_text(&target_tok).ok_or_else(|| {
1848 format!(
1849 "Expected cell, row, or column reference after `!`, got {:?}",
1850 target_tok
1851 )
1852 })?;
1853
1854 if self.peek() == Some(&EvalToken::Colon) {
1855 self.next();
1856 let end_tok = self
1857 .next()
1858 .ok_or_else(|| {
1859 "Expected cell, row, or column reference after `:`".to_string()
1860 })?
1861 .clone();
1862 let end_str = ref_token_text(&end_tok).ok_or_else(|| {
1863 format!(
1864 "Expected cell, row, or column reference after `:`, got {:?}",
1865 end_tok
1866 )
1867 })?;
1868
1869 return range_ref_from_texts(Some(val), &target_str, &end_str);
1870 } else {
1871 let (row, col, row_abs, col_abs) = parse_cell_ref_parts(&target_str)
1872 .ok_or_else(|| format!("Invalid cell: {}", target_str))?;
1873 return Ok(Expr::CellRef {
1874 sheet: Some(val),
1875 row,
1876 col,
1877 row_abs,
1878 col_abs,
1879 });
1880 }
1881 }
1882 Ok(Expr::String(val.clone()))
1883 }
1884 EvalToken::Boolean(val) => Ok(Expr::Boolean(val)),
1885 EvalToken::Error(code) => Ok(Expr::Error(code)),
1886 EvalToken::OpenParen => {
1887 let expr = self.parse()?;
1888 self.consume(EvalToken::CloseParen)?;
1889 Ok(expr)
1890 }
1891 EvalToken::OpenBracket => {
1892 let mut list = Vec::new();
1893 if self.peek() != Some(&EvalToken::CloseBracket) {
1894 loop {
1895 list.push(self.parse()?);
1896 if self.peek() == Some(&EvalToken::Comma) {
1897 self.next();
1898 } else {
1899 break;
1900 }
1901 }
1902 }
1903 self.consume(EvalToken::CloseBracket)?;
1904 Ok(Expr::List(list))
1905 }
1906 EvalToken::Op(Op::Sub) => {
1907 let expr = self.parse_binary(100)?;
1908 Ok(Expr::UnaryOp {
1909 op: Op::Sub,
1910 expr: Box::new(expr),
1911 })
1912 }
1913 EvalToken::Op(Op::Add) => {
1914 let expr = self.parse_binary(100)?;
1915 Ok(expr)
1916 }
1917 EvalToken::Identifier(id_name) => {
1918 if self.peek() == Some(&EvalToken::OpenParen) {
1919 self.next();
1920 let mut args = Vec::new();
1921 if self.peek() != Some(&EvalToken::CloseParen) {
1922 loop {
1923 args.push(self.parse()?);
1924 if self.peek() == Some(&EvalToken::Comma) {
1925 self.next();
1926 } else {
1927 break;
1928 }
1929 }
1930 }
1931 self.consume(EvalToken::CloseParen)?;
1932 return Ok(Expr::FunctionCall {
1933 name: id_name.clone(),
1934 args,
1935 });
1936 }
1937
1938 if self.peek() == Some(&EvalToken::Exclamation) {
1939 self.next();
1940 let target_tok = self
1941 .next()
1942 .ok_or_else(|| "Expected cell or column reference after `!`".to_string())?;
1943 let target_str = ref_token_text(target_tok).ok_or_else(|| {
1944 format!(
1945 "Expected cell, row, or column reference after `!`, got {:?}",
1946 target_tok
1947 )
1948 })?;
1949
1950 if self.peek() == Some(&EvalToken::Colon) {
1951 self.next();
1952 let end_tok = self.next().ok_or_else(|| {
1953 "Expected cell, row, or column reference after `:`".to_string()
1954 })?;
1955 let end_str = ref_token_text(end_tok).ok_or_else(|| {
1956 format!(
1957 "Expected cell, row, or column reference after `:`, got {:?}",
1958 end_tok
1959 )
1960 })?;
1961
1962 return range_ref_from_texts(Some(id_name.clone()), &target_str, &end_str);
1963 } else {
1964 let (row, col, row_abs, col_abs) = parse_cell_ref_parts(&target_str)
1965 .ok_or_else(|| format!("Invalid cell: {}", target_str))?;
1966 return Ok(Expr::CellRef {
1967 sheet: Some(id_name.clone()),
1968 row,
1969 col,
1970 row_abs,
1971 col_abs,
1972 });
1973 }
1974 }
1975
1976 if let Some((row, col, row_abs, col_abs)) = parse_cell_ref_parts(&id_name) {
1977 if self.peek() == Some(&EvalToken::Colon) {
1978 self.next();
1979 let end_tok = self
1980 .next()
1981 .ok_or_else(|| "Expected cell reference after `:`".to_string())?;
1982 let end_str = match end_tok {
1983 EvalToken::Identifier(s) => s.clone(),
1984 _ => {
1985 return Err(format!(
1986 "Expected cell reference after `:`, got {:?}",
1987 end_tok
1988 ));
1989 }
1990 };
1991 let (e_row, e_col, e_row_abs, e_col_abs) =
1992 parse_cell_ref_parts(&end_str)
1993 .ok_or_else(|| format!("Invalid end cell: {}", end_str))?;
1994 return Ok(Expr::RangeRef {
1995 sheet: None,
1996 start_row: row,
1997 start_col: col,
1998 end_row: e_row,
1999 end_col: e_col,
2000 start_row_abs: row_abs,
2001 start_col_abs: col_abs,
2002 end_row_abs: e_row_abs,
2003 end_col_abs: e_col_abs,
2004 });
2005 }
2006
2007 return Ok(Expr::CellRef {
2008 sheet: None,
2009 row,
2010 col,
2011 row_abs,
2012 col_abs,
2013 });
2014 }
2015
2016 if let Some((col, col_abs)) = parse_column_ref(&id_name)
2017 && self.peek() == Some(&EvalToken::Colon)
2018 {
2019 self.next();
2020 let end_tok = self
2021 .next()
2022 .ok_or_else(|| "Expected column reference after `:`".to_string())?;
2023 let end_str = match end_tok {
2024 EvalToken::Identifier(s) => s.clone(),
2025 _ => {
2026 return Err(format!(
2027 "Expected column reference after `:`, got {:?}",
2028 end_tok
2029 ));
2030 }
2031 };
2032 let (e_col, e_col_abs) = parse_column_ref(&end_str)
2033 .ok_or_else(|| format!("Invalid end column: {}", end_str))?;
2034 return Ok(Expr::RangeRef {
2035 sheet: None,
2036 start_row: 0,
2037 start_col: col,
2038 end_row: usize::MAX,
2039 end_col: e_col,
2040 start_row_abs: true,
2041 start_col_abs: col_abs,
2042 end_row_abs: true,
2043 end_col_abs: e_col_abs,
2044 });
2045 }
2046
2047 if let Some((row, row_abs)) = parse_row_ref(&id_name)
2048 && self.peek() == Some(&EvalToken::Colon)
2049 {
2050 self.next();
2051 let end_tok = self
2052 .next()
2053 .ok_or_else(|| "Expected row reference after `:`".to_string())?;
2054 let end_str = ref_token_text(end_tok).ok_or_else(|| {
2055 format!("Expected row reference after `:`, got {:?}", end_tok)
2056 })?;
2057 let (e_row, e_row_abs) = parse_row_ref(&end_str)
2058 .ok_or_else(|| format!("Invalid end row: {}", end_str))?;
2059 return Ok(Expr::RangeRef {
2060 sheet: None,
2061 start_row: row,
2062 start_col: 0,
2063 end_row: e_row,
2064 end_col: usize::MAX,
2065 start_row_abs: row_abs,
2066 start_col_abs: true,
2067 end_row_abs: e_row_abs,
2068 end_col_abs: true,
2069 });
2070 }
2071
2072 Ok(Expr::Identifier(id_name.clone()))
2073 }
2074 EvalToken::StructuredRef {
2075 sheet,
2076 column,
2077 is_this_row,
2078 section,
2079 } => Ok(Expr::StructuredRef {
2080 sheet: sheet.clone(),
2081 column: column.clone(),
2082 is_this_row,
2083 section,
2084 }),
2085 _ => Err(format!("Unexpected token: {:?}", tok)),
2086 }
2087 }
2088}
2089
2090fn op_precedence(op: Op) -> u8 {
2091 match op {
2092 Op::Add | Op::Sub => 10,
2093 Op::Mul | Op::Div => 20,
2094 Op::Exp => 30,
2095 Op::Eq | Op::Ne | Op::Lt | Op::Gt | Op::Le | Op::Ge => 5,
2096 }
2097}
2098
2099pub fn parse_excel_formula(input: &str) -> Result<Expr, String> {
2100 let tokens = lex_eval(input)?;
2101 let mut parser = Parser::new(&tokens);
2102 let expr = parser.parse()?;
2103 if parser.peek().is_some() {
2104 return Err("Trailing tokens after expression".to_string());
2105 }
2106 Ok(expr)
2107}
2108
2109#[cfg(test)]
2110mod tests {
2111 use super::*;
2112
2113 #[test]
2114 fn test_parse_cell_ref() {
2115 let (sheet, row, col) = parse_cell_ref("A1").unwrap();
2116 assert_eq!(sheet, None);
2117 assert_eq!(row, 0);
2118 assert_eq!(col, 0);
2119
2120 let (sheet, row, col) = parse_cell_ref("Sheet1!C5").unwrap();
2121 assert_eq!(sheet, Some("Sheet1".to_string()));
2122 assert_eq!(row, 4);
2123 assert_eq!(col, 2);
2124 }
2125
2126 #[test]
2127 fn test_parse_range_ref() {
2128 let (sheet, s_row, s_col, e_row, e_col) = parse_range_ref("A1:C10").unwrap();
2129 assert_eq!(sheet, None);
2130 assert_eq!((s_row, s_col), (0, 0));
2131 assert_eq!((e_row, e_col), (9, 2));
2132
2133 let (sheet, s_row, s_col, e_row, e_col) = parse_range_ref("'Data Sheet'!B2:D4").unwrap();
2134 assert_eq!(sheet, Some("Data Sheet".to_string()));
2135 assert_eq!((s_row, s_col), (1, 1));
2136 assert_eq!((e_row, e_col), (3, 3));
2137 }
2138
2139 #[test]
2140 fn test_col_conversions() {
2141 assert_eq!(col_idx_to_letters(0), "A");
2142 assert_eq!(col_idx_to_letters(25), "Z");
2143 assert_eq!(col_idx_to_letters(26), "AA");
2144 assert_eq!(col_idx_to_letters(27), "AB");
2145
2146 assert_eq!(col_letters_to_idx("A"), 0);
2147 assert_eq!(col_letters_to_idx("z"), 25);
2148 assert_eq!(col_letters_to_idx("AA"), 26);
2149 assert_eq!(col_letters_to_idx("AB"), 27);
2150 }
2151
2152 #[test]
2153 fn test_lex_scientific_notation_literals() {
2154 for (src, want) in [
2155 ("1E5", 1e5),
2156 ("1E+5", 1e5),
2157 ("1e5", 1e5),
2158 ("2.5E-3", 2.5e-3),
2159 (".5E3", 0.5e3),
2160 ("1E+300", 1e300),
2161 ] {
2162 match lex_eval(src).unwrap().as_slice() {
2163 [EvalToken::Number(got)] => {
2164 assert_eq!(*got, want, "{src} lexed to the wrong value")
2165 }
2166 other => panic!("{src} did not lex as a single number: {other:?}"),
2167 }
2168 }
2169 }
2170
2171 #[test]
2172 fn test_lex_does_not_eat_e_that_starts_a_reference() {
2173 let tokens = lex_eval("A1:E5").unwrap();
2174 assert_eq!(
2175 tokens.len(),
2176 3,
2177 "A1:E5 must stay ident/colon/ident, got {tokens:?}"
2178 );
2179 assert!(matches!(
2180 lex_eval("1E").unwrap().as_slice(),
2181 [EvalToken::Number(n), _] if *n == 1.0
2182 ));
2183 }
2184
2185 #[test]
2186 fn test_compile_and_serialize() {
2187 let sheet1 = Sheet::new(crate::core::SheetInit {
2188 id: Some(123),
2189 name: Some("Sheet1".to_string()),
2190 rows: 5,
2191 cols: 5,
2192 });
2193 let sheet2 = Sheet::new(crate::core::SheetInit {
2194 id: Some(456),
2195 name: Some("Sheet2".to_string()),
2196 rows: 5,
2197 cols: 5,
2198 });
2199
2200 let sheets = vec![sheet1, sheet2];
2201
2202 let formula = compile_formula("=Sheet2!B1 + 10", &sheets);
2203 assert_eq!(formula.parts.len(), 3);
2204 match &formula.parts[1] {
2205 FormulaPart::SheetReference {
2206 sheet_id, row, col, ..
2207 } => {
2208 assert_eq!(*sheet_id, 456);
2209 assert_eq!(*row, 0);
2210 assert_eq!(*col, 1);
2211 }
2212 _ => panic!("Expected SheetReference"),
2213 }
2214
2215 let mut renamed_sheet2 = sheets[1].clone();
2216 renamed_sheet2.name = "Sheet3".to_string();
2217
2218 let serialized = serialize_formula(&formula, &[sheets[0].clone(), renamed_sheet2]);
2219 assert_eq!(serialized, "=Sheet3!B1 + 10");
2220 }
2221
2222 #[test]
2223 fn test_table_names_with_spaces() {
2224 let sheet1 = Sheet::new(crate::core::SheetInit {
2225 id: Some(123),
2226 name: Some("Sheet1".to_string()),
2227 rows: 5,
2228 cols: 5,
2229 });
2230 let sheet2 = Sheet::new(crate::core::SheetInit {
2231 id: Some(456),
2232 name: Some("My Sheet".to_string()),
2233 rows: 5,
2234 cols: 5,
2235 });
2236
2237 let sheets = vec![sheet1, sheet2];
2238
2239 let formula_quote = compile_formula("='My Sheet'!B1 + 10", &sheets);
2240 match &formula_quote.parts[1] {
2241 FormulaPart::SheetReference {
2242 sheet_id, row, col, ..
2243 } => {
2244 assert_eq!(*sheet_id, 456);
2245 assert_eq!(*row, 0);
2246 assert_eq!(*col, 1);
2247 }
2248 _ => panic!("Expected SheetReference"),
2249 }
2250
2251 let serialized_quote = serialize_formula(&formula_quote, &sheets);
2252 assert_eq!(serialized_quote, "='My Sheet'!B1 + 10");
2253
2254 let ast_quote = parse_excel_formula("'My Sheet'!B1 + 10").unwrap();
2255 match ast_quote {
2256 Expr::BinaryOp { left, .. } => match *left {
2257 Expr::CellRef {
2258 sheet, row, col, ..
2259 } => {
2260 assert_eq!(sheet, Some("My Sheet".to_string()));
2261 assert_eq!(row, 0);
2262 assert_eq!(col, 1);
2263 }
2264 _ => panic!("Expected CellRef"),
2265 },
2266 _ => panic!("Expected BinaryOp"),
2267 }
2268 }
2269
2270 #[test]
2271 fn test_row_ranges_compile_serialize_and_parse() {
2272 let sheet1 = Sheet::new(crate::core::SheetInit {
2273 id: Some(123),
2274 name: Some("Sheet1".to_string()),
2275 rows: 5,
2276 cols: 5,
2277 });
2278 let sheet2 = Sheet::new(crate::core::SheetInit {
2279 id: Some(456),
2280 name: Some("Sheet2".to_string()),
2281 rows: 5,
2282 cols: 5,
2283 });
2284 let sheets = vec![sheet1, sheet2];
2285
2286 let formula = compile_formula("=SUM(1:3)", &sheets);
2287 match &formula.parts[1] {
2288 FormulaPart::RangeReference {
2289 sheet_id,
2290 start_row,
2291 start_col,
2292 end_row,
2293 end_col,
2294 ..
2295 } => {
2296 assert_eq!(*sheet_id, 123);
2297 assert_eq!((*start_row, *start_col), (0, 0));
2298 assert_eq!((*end_row, *end_col), (2, usize::MAX));
2299 }
2300 _ => panic!("Expected whole-row RangeReference"),
2301 }
2302 assert_eq!(serialize_formula(&formula, &sheets), "=SUM(1:3)");
2303
2304 let cross = compile_formula("=SUM(Sheet2!$2:$4)", &sheets);
2305 assert_eq!(serialize_formula(&cross, &sheets), "=SUM(Sheet2!$2:$4)");
2306
2307 let ast = parse_excel_formula("SUM(Sheet2!1:3)").unwrap();
2308 match ast {
2309 Expr::FunctionCall { args, .. } => match &args[0] {
2310 Expr::RangeRef {
2311 sheet,
2312 start_row,
2313 start_col,
2314 end_row,
2315 end_col,
2316 ..
2317 } => {
2318 assert_eq!(sheet.as_deref(), Some("Sheet2"));
2319 assert_eq!((*start_row, *start_col), (0, 0));
2320 assert_eq!((*end_row, *end_col), (2, usize::MAX));
2321 }
2322 _ => panic!("Expected whole-row RangeRef"),
2323 },
2324 _ => panic!("Expected FunctionCall"),
2325 }
2326 }
2327
2328 #[test]
2329 fn test_table_names_with_periods() {
2330 let sheet1 = Sheet::new(crate::core::SheetInit {
2331 id: Some(123),
2332 name: Some("Model_SL_5.5Yr".to_string()),
2333 rows: 5,
2334 cols: 5,
2335 });
2336
2337 let sheets = vec![sheet1];
2338
2339 let formula = compile_formula("=Model_SL_5.5Yr!B10 + 10", &sheets);
2340 assert_eq!(formula.parts.len(), 3);
2341 match &formula.parts[1] {
2342 FormulaPart::SheetReference {
2343 sheet_id, row, col, ..
2344 } => {
2345 assert_eq!(*sheet_id, 123);
2346 assert_eq!(*row, 9);
2347 assert_eq!(*col, 1);
2348 }
2349 _ => panic!("Expected SheetReference"),
2350 }
2351
2352 let ast = parse_excel_formula("Model_SL_5.5Yr!B10 + 10").unwrap();
2353 match ast {
2354 Expr::BinaryOp { left, .. } => match *left {
2355 Expr::CellRef {
2356 sheet, row, col, ..
2357 } => {
2358 assert_eq!(sheet, Some("Model_SL_5.5Yr".to_string()));
2359 assert_eq!(row, 9);
2360 assert_eq!(col, 1);
2361 }
2362 _ => panic!("Expected CellRef"),
2363 },
2364 _ => panic!("Expected BinaryOp"),
2365 }
2366 }
2367
2368 #[test]
2369 fn test_column_ranges() {
2370 let sheet1 = Sheet::new(crate::core::SheetInit {
2371 id: Some(123),
2372 name: Some("Sheet1".to_string()),
2373 rows: 5,
2374 cols: 5,
2375 });
2376 let sheets = vec![sheet1];
2377
2378 let formula = compile_formula("=SUM(A:B)", &sheets);
2379 assert_eq!(formula.parts.len(), 3);
2380 match &formula.parts[1] {
2381 FormulaPart::RangeReference {
2382 sheet_id,
2383 start_row,
2384 start_col,
2385 end_row,
2386 end_col,
2387 ..
2388 } => {
2389 assert_eq!(*sheet_id, 123);
2390 assert_eq!(*start_row, 0);
2391 assert_eq!(*start_col, 0);
2392 assert_eq!(*end_row, usize::MAX);
2393 assert_eq!(*end_col, 1);
2394 }
2395 _ => panic!("Expected RangeReference"),
2396 }
2397
2398 let serialized = serialize_formula(&formula, &sheets);
2399 assert_eq!(serialized, "=SUM(A:B)");
2400
2401 let sheet2 = Sheet::new(crate::core::SheetInit {
2402 id: Some(456),
2403 name: Some("Sheet2".to_string()),
2404 rows: 5,
2405 cols: 5,
2406 });
2407 let sheets_multi = vec![sheets[0].clone(), sheet2];
2408 let formula_cross = compile_formula("=SUM(Sheet2!$A:$C)", &sheets_multi);
2409 match &formula_cross.parts[1] {
2410 FormulaPart::RangeReference {
2411 sheet_id,
2412 start_row,
2413 start_col,
2414 end_row,
2415 end_col,
2416 start_col_ref_type,
2417 end_col_ref_type,
2418 ..
2419 } => {
2420 assert_eq!(*sheet_id, 456);
2421 assert_eq!(*start_row, 0);
2422 assert_eq!(*start_col, 0);
2423 assert_eq!(*end_row, usize::MAX);
2424 assert_eq!(*end_col, 2);
2425 assert_eq!(*start_col_ref_type, RefType::Absolute);
2426 assert_eq!(*end_col_ref_type, RefType::Absolute);
2427 }
2428 _ => panic!("Expected RangeReference for cross sheet"),
2429 }
2430
2431 let serialized_cross = serialize_formula(&formula_cross, &sheets_multi);
2432 assert_eq!(serialized_cross, "=SUM(Sheet2!$A:$C)");
2433
2434 let ast = parse_excel_formula("SUM(A:A)").unwrap();
2435 match ast {
2436 Expr::FunctionCall { name, args } => {
2437 assert_eq!(name, "SUM");
2438 assert_eq!(args.len(), 1);
2439 match &args[0] {
2440 Expr::RangeRef {
2441 sheet,
2442 start_row,
2443 start_col,
2444 end_row,
2445 end_col,
2446 ..
2447 } => {
2448 assert_eq!(*sheet, None);
2449 assert_eq!(*start_row, 0);
2450 assert_eq!(*start_col, 0);
2451 assert_eq!(*end_row, usize::MAX);
2452 assert_eq!(*end_col, 0);
2453 }
2454 _ => panic!("Expected RangeRef inside SUM"),
2455 }
2456 }
2457 _ => panic!("Expected FunctionCall"),
2458 }
2459 }
2460
2461 #[test]
2462 fn test_excel_structured_references() {
2463 let mut sheet1 = Sheet::new(crate::core::SheetInit {
2464 id: Some(123),
2465 name: Some("Sheet1".to_string()),
2466 rows: 5,
2467 cols: 2,
2468 });
2469 sheet1.columns[0].name = "Sales".to_string();
2470 sheet1.columns[0].id = 1;
2471 sheet1.columns[1].name = "Cost".to_string();
2472 sheet1.columns[1].id = 2;
2473
2474 let sheets = vec![sheet1];
2475
2476 let f1 = compile_formula("=Sheet1[Sales]", &sheets);
2477 assert_eq!(f1.parts.len(), 2);
2478 match &f1.parts[1] {
2479 FormulaPart::StructuredReference {
2480 sheet_id,
2481 col_id,
2482 is_this_row,
2483 section,
2484 } => {
2485 assert_eq!(*sheet_id, 123);
2486 assert_eq!(*col_id, Some(1));
2487 assert!(!is_this_row);
2488 assert_eq!(*section, SheetSection::Data);
2489 }
2490 _ => panic!("Expected StructuredReference, got {:?}", f1.parts[1]),
2491 }
2492
2493 let s1 = serialize_formula(&f1, &sheets);
2494 assert_eq!(s1, "=[Sales]");
2495
2496 let f2 = compile_formula("=[@Cost]", &sheets);
2497 match &f2.parts[1] {
2498 FormulaPart::StructuredReference {
2499 sheet_id,
2500 col_id,
2501 is_this_row,
2502 section,
2503 } => {
2504 assert_eq!(*sheet_id, 123);
2505 assert_eq!(*col_id, Some(2));
2506 assert!(is_this_row);
2507 assert_eq!(*section, SheetSection::Data);
2508 }
2509 _ => panic!("Expected StructuredReference"),
2510 }
2511 let s2 = serialize_formula(&f2, &sheets);
2512 assert_eq!(s2, "=[@Cost]");
2513
2514 let f3 = compile_formula("=Sheet1[[#Headers],[Sales]]", &sheets);
2515 match &f3.parts[1] {
2516 FormulaPart::StructuredReference {
2517 sheet_id,
2518 col_id,
2519 section,
2520 ..
2521 } => {
2522 assert_eq!(*sheet_id, 123);
2523 assert_eq!(*col_id, Some(1));
2524 assert_eq!(*section, SheetSection::Headers);
2525 }
2526 _ => panic!("Expected StructuredReference"),
2527 }
2528 let s3 = serialize_formula(&f3, &sheets);
2529 assert_eq!(s3, "=[[#Headers], [Sales]]");
2530
2531 let mut sheet2 = Sheet::new(crate::core::SheetInit {
2532 id: Some(456),
2533 name: Some("Sheet2".to_string()),
2534 rows: 5,
2535 cols: 2,
2536 });
2537 sheet2.columns[0].name = "Revenue".to_string();
2538 sheet2.columns[0].id = 3;
2539 sheet2.columns[1].name = "Expenses".to_string();
2540 sheet2.columns[1].id = 4;
2541
2542 let multi_sheets = vec![sheets[0].clone(), sheet2];
2543 let f4 = compile_formula("=Sheet2[Revenue]", &multi_sheets);
2544 let s4 = serialize_formula(&f4, &multi_sheets);
2545 assert_eq!(s4, "=Sheet2[Revenue]");
2546
2547 let ast = parse_excel_formula("Sheet1[@Sales] + 10").unwrap();
2548 match ast {
2549 Expr::BinaryOp { op, left, right: _ } => {
2550 assert_eq!(op, Op::Add);
2551 match &*left {
2552 Expr::StructuredRef {
2553 sheet,
2554 column,
2555 is_this_row,
2556 ..
2557 } => {
2558 assert_eq!(sheet.as_deref(), Some("Sheet1"));
2559 assert_eq!(column.as_deref(), Some("Sales"));
2560 assert!(is_this_row);
2561 }
2562 _ => panic!("Expected StructuredRef"),
2563 }
2564 }
2565 _ => panic!("Expected BinaryOp"),
2566 }
2567 }
2568
2569 #[test]
2570 fn test_structured_reference_whole_row_no_column() {
2571 let mut sheet1 = Sheet::new(crate::core::SheetInit {
2572 id: Some(123),
2573 name: Some("Sheet1".to_string()),
2574 rows: 5,
2575 cols: 2,
2576 });
2577 sheet1.columns[0].name = "Sales".to_string();
2578 sheet1.columns[0].id = 1;
2579 sheet1.columns[1].name = "Cost".to_string();
2580 sheet1.columns[1].id = 2;
2581 let sheets = vec![sheet1];
2582
2583 let f = compile_formula("=[@]", &sheets);
2584 match &f.parts[1] {
2585 FormulaPart::StructuredReference {
2586 col_id,
2587 is_this_row,
2588 section,
2589 ..
2590 } => {
2591 assert_eq!(*col_id, None);
2592 assert!(is_this_row);
2593 assert_eq!(*section, SheetSection::Data);
2594 }
2595 _ => panic!("Expected StructuredReference, got {:?}", f.parts[1]),
2596 }
2597 assert_eq!(serialize_formula(&f, &sheets), "=[@]");
2598
2599 let ast = parse_excel_formula("[@]").unwrap();
2600 match ast {
2601 Expr::StructuredRef {
2602 column,
2603 is_this_row,
2604 ..
2605 } => {
2606 assert_eq!(column, None);
2607 assert!(is_this_row);
2608 }
2609 _ => panic!("Expected StructuredRef"),
2610 }
2611 }
2612
2613 #[test]
2614 fn test_structured_reference_whole_table_sections_no_column() {
2615 let mut sheet1 = Sheet::new(crate::core::SheetInit {
2616 id: Some(123),
2617 name: Some("Sheet1".to_string()),
2618 rows: 5,
2619 cols: 2,
2620 });
2621 sheet1.columns[0].name = "Sales".to_string();
2622 sheet1.columns[1].name = "Cost".to_string();
2623 let sheets = vec![sheet1];
2624
2625 for (input, expected_section) in [
2626 ("=[#Data]", SheetSection::Data),
2627 ("=[#All]", SheetSection::All),
2628 ("=[#Headers]", SheetSection::Headers),
2629 ("=[#Totals]", SheetSection::Totals),
2630 ] {
2631 let f = compile_formula(input, &sheets);
2632 match &f.parts[1] {
2633 FormulaPart::StructuredReference {
2634 col_id,
2635 is_this_row,
2636 section,
2637 ..
2638 } => {
2639 assert_eq!(*col_id, None, "input: {input}");
2640 assert!(!is_this_row, "input: {input}");
2641 assert_eq!(*section, expected_section, "input: {input}");
2642 }
2643 _ => panic!(
2644 "Expected StructuredReference for {input}, got {:?}",
2645 f.parts[1]
2646 ),
2647 }
2648 assert_eq!(serialize_formula(&f, &sheets), input);
2649 }
2650 }
2651
2652 #[test]
2653 fn test_rewrite_structured_table_reference_renames_table() {
2654 let rewritten = rewrite_structured_table_reference(
2655 "=SUM(Sales[Amount])",
2656 "Sales",
2657 Some("Revenue"),
2658 None,
2659 );
2660 assert_eq!(rewritten.as_deref(), Some("=SUM(Revenue[Amount])"));
2661 }
2662
2663 #[test]
2664 fn test_rewrite_structured_table_reference_renames_column() {
2665 let rewritten = rewrite_structured_table_reference(
2666 "=SUM(Sales[Amount])",
2667 "Sales",
2668 None,
2669 Some(("Amount", "Total")),
2670 );
2671 assert_eq!(rewritten.as_deref(), Some("=SUM(Sales[Total])"));
2672 }
2673
2674 #[test]
2675 fn test_rewrite_structured_table_reference_handles_multiple_refs_and_forms() {
2676 let rewritten = rewrite_structured_table_reference(
2677 "=Sales[@Amount] + SUM(Sales[Amount]) + Sales[[#Headers],[Amount]]",
2678 "Sales",
2679 Some("Revenue"),
2680 Some(("Amount", "Total")),
2681 );
2682 assert_eq!(
2683 rewritten.as_deref(),
2684 Some("=Revenue[@Total] + SUM(Revenue[Total]) + Revenue[[#Headers], [Total]]")
2685 );
2686 }
2687
2688 #[test]
2689 fn test_rewrite_structured_table_reference_ignores_other_tables_and_columns() {
2690 let rewritten = rewrite_structured_table_reference(
2691 "=SUM(Other[Amount]) + A1",
2692 "Sales",
2693 Some("Revenue"),
2694 None,
2695 );
2696 assert_eq!(rewritten, None);
2697
2698 let rewritten2 = rewrite_structured_table_reference(
2699 "=Sales[Quantity]",
2700 "Sales",
2701 None,
2702 Some(("Amount", "Total")),
2703 );
2704 assert_eq!(rewritten2, None);
2705 }
2706
2707 #[test]
2708 fn test_rewrite_structured_table_reference_ignores_non_formula_cells() {
2709 let rewritten = rewrite_structured_table_reference(
2710 "Sales[Amount] is a great product",
2711 "Sales",
2712 Some("Revenue"),
2713 None,
2714 );
2715 assert_eq!(rewritten, None);
2716 }
2717
2718 #[test]
2719 fn test_compile_formula_never_panics_on_unterminated_quote_ending_in_backslash() {
2720 let _ = compile_formula("=\"\\", &[]);
2721 let _ = compile_formula("=\"unterminated\\", &[]);
2722 }
2723
2724 #[test]
2725 fn test_rewrite_structured_table_reference_never_panics_on_unterminated_quote() {
2726 let _ = rewrite_structured_table_reference("=Sales[Amount]&\"\\", "Sales", None, None);
2727 }
2728
2729 #[test]
2730 fn an_error_value_lexes_as_a_literal_rather_than_as_punctuation() {
2731 assert_eq!(lex_eval("#REF!").unwrap(), vec![EvalToken::Error("#REF!")]);
2732 assert_eq!(
2733 lex_eval("#DIV/0!").unwrap(),
2734 vec![EvalToken::Error("#DIV/0!")]
2735 );
2736 assert_eq!(lex_eval("#N/A").unwrap(), vec![EvalToken::Error("#N/A")]);
2737 assert_eq!(lex_eval("#ref!").unwrap(), vec![EvalToken::Error("#REF!")]);
2738 assert_eq!(
2739 lex_eval("1+#REF!").unwrap(),
2740 vec![
2741 EvalToken::Number(1.0),
2742 EvalToken::Op(Op::Add),
2743 EvalToken::Error("#REF!"),
2744 ]
2745 );
2746 }
2747
2748 #[test]
2749 fn a_hash_that_starts_nothing_recognisable_is_left_alone() {
2750 assert!(match_error_code(&"#NOPE".chars().collect::<Vec<_>>(), 0).is_none());
2751 assert!(match_error_code(&"#RE".chars().collect::<Vec<_>>(), 0).is_none());
2752 }
2753
2754 #[test]
2755 fn test_bracket_slice_parsing() {
2756 let ast = parse_excel_formula("arr[1:3]").unwrap();
2757 assert_eq!(
2758 ast,
2759 Expr::Slice {
2760 expr: Box::new(Expr::Identifier("arr".to_string())),
2761 start: Some(Box::new(Expr::Number(1.0))),
2762 end: Some(Box::new(Expr::Number(3.0))),
2763 }
2764 );
2765
2766 let ast_no_end = parse_excel_formula("arr[2:]").unwrap();
2767 assert_eq!(
2768 ast_no_end,
2769 Expr::Slice {
2770 expr: Box::new(Expr::Identifier("arr".to_string())),
2771 start: Some(Box::new(Expr::Number(2.0))),
2772 end: None,
2773 }
2774 );
2775
2776 let ast_no_start = parse_excel_formula("arr[:2]").unwrap();
2777 assert_eq!(
2778 ast_no_start,
2779 Expr::Slice {
2780 expr: Box::new(Expr::Identifier("arr".to_string())),
2781 start: None,
2782 end: Some(Box::new(Expr::Number(2.0))),
2783 }
2784 );
2785 }
2786
2787 #[test]
2788 fn test_sheet_column_bracket_indexing() {
2789 let ast = parse_excel_formula("Sheet1[\"Sales\"]").unwrap();
2790 assert_eq!(
2791 ast,
2792 Expr::StructuredRef {
2793 sheet: Some("Sheet1".to_string()),
2794 column: Some("Sales".to_string()),
2795 is_this_row: false,
2796 section: SheetSection::Data,
2797 }
2798 );
2799 }
2800}