1use crate::ParserLimits;
2use crate::structured_ref;
3use crate::tokenizer::{
4 Associativity, Token, TokenSpan, TokenStream, TokenSubType, TokenType, TokenizerError,
5};
6use crate::types::{FormulaDialect, ParsingError};
7use crate::{ExcelError, LiteralValue};
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12use crate::hasher::FormulaHasher;
13use formualizer_common::coord::{
14 col_index_from_letters_1based, col_letters_from_1based, parse_a1_1based,
15};
16use formualizer_common::{
17 AxisBound, RelativeCoord, SheetCellRef, SheetLocator, SheetRangeRef, SheetRef,
18};
19use once_cell::sync::Lazy;
20use smallvec::SmallVec;
21use std::borrow::Cow;
22use std::error::Error;
23use std::fmt::{self, Display};
24use std::hash::{Hash, Hasher};
25use std::str::FromStr;
26use std::sync::Arc;
27
28type VolatilityFn = dyn Fn(&str) -> bool + Send + Sync + 'static;
29type VolatilityClassifierBox = Box<VolatilityFn>;
30type VolatilityClassifierArc = Arc<VolatilityFn>;
31
32#[derive(Debug)]
34pub struct ParserError {
35 pub message: String,
36 pub position: Option<usize>,
39}
40
41impl Display for ParserError {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 if let Some(pos) = self.position {
44 write!(f, "ParserError at position {}: {}", pos, self.message)
45 } else {
46 write!(f, "ParserError: {}", self.message)
47 }
48 }
49}
50
51impl Error for ParserError {}
52
53static COLUMN_LOOKUP: Lazy<Vec<String>> = Lazy::new(|| {
55 let mut cols = Vec::with_capacity(702);
56 for c in b'A'..=b'Z' {
58 cols.push(String::from(c as char));
59 }
60 for c1 in b'A'..=b'Z' {
62 for c2 in b'A'..=b'Z' {
63 cols.push(format!("{}{}", c1 as char, c2 as char));
64 }
65 }
66 cols
67});
68
69#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
71#[derive(Debug, Clone, PartialEq, Hash)]
72pub enum TableSpecifier {
73 All,
75 Data,
77 Headers,
79 Totals,
81 Row(TableRowSpecifier),
83 Column(String),
85 ColumnRange(String, String),
87 SpecialItem(SpecialItem),
89 Combination(Vec<Box<TableSpecifier>>),
91}
92
93#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
95#[derive(Debug, Clone, PartialEq, Hash)]
96pub enum TableRowSpecifier {
97 Current,
99 All,
101 Data,
103 Headers,
105 Totals,
107 Index(u32),
109}
110
111#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
113#[derive(Debug, Clone, PartialEq, Hash)]
114pub enum SpecialItem {
115 Headers,
117 Data,
119 Totals,
121 All,
123 ThisRow,
125}
126
127#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
129#[derive(Debug, Clone, PartialEq, Hash)]
130pub struct TableReference {
131 pub name: String,
133 pub specifier: Option<TableSpecifier>,
135}
136
137#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
138#[derive(Debug, Clone, PartialEq, Hash)]
139pub enum ExternalBookRef {
140 Token(String),
141}
142
143impl ExternalBookRef {
144 pub fn token(&self) -> &str {
145 match self {
146 ExternalBookRef::Token(s) => s,
147 }
148 }
149}
150
151#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
153pub enum ExternalRefKind {
154 Cell {
155 row: u32,
156 col: u32,
157 row_abs: bool,
158 col_abs: bool,
159 },
160 Range {
161 start_row: Option<u32>,
162 start_col: Option<u32>,
163 end_row: Option<u32>,
164 end_col: Option<u32>,
165 start_row_abs: bool,
166 start_col_abs: bool,
167 end_row_abs: bool,
168 end_col_abs: bool,
169 },
170}
171
172impl ExternalRefKind {
173 pub fn cell(row: u32, col: u32) -> Self {
174 Self::Cell {
175 row,
176 col,
177 row_abs: false,
178 col_abs: false,
179 }
180 }
181
182 pub fn cell_with_abs(row: u32, col: u32, row_abs: bool, col_abs: bool) -> Self {
183 Self::Cell {
184 row,
185 col,
186 row_abs,
187 col_abs,
188 }
189 }
190
191 pub fn range(
192 start_row: Option<u32>,
193 start_col: Option<u32>,
194 end_row: Option<u32>,
195 end_col: Option<u32>,
196 ) -> Self {
197 Self::Range {
198 start_row,
199 start_col,
200 end_row,
201 end_col,
202 start_row_abs: false,
203 start_col_abs: false,
204 end_row_abs: false,
205 end_col_abs: false,
206 }
207 }
208
209 #[expect(
210 clippy::too_many_arguments,
211 reason = "constructor mirroring the enum fields; an explicit signature keeps callers readable"
212 )]
213 pub fn range_with_abs(
214 start_row: Option<u32>,
215 start_col: Option<u32>,
216 end_row: Option<u32>,
217 end_col: Option<u32>,
218 start_row_abs: bool,
219 start_col_abs: bool,
220 end_row_abs: bool,
221 end_col_abs: bool,
222 ) -> Self {
223 Self::Range {
224 start_row,
225 start_col,
226 end_row,
227 end_col,
228 start_row_abs,
229 start_col_abs,
230 end_row_abs,
231 end_col_abs,
232 }
233 }
234}
235
236#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
237#[derive(Debug, Clone, PartialEq, Hash)]
238pub struct ExternalReference {
239 pub raw: String,
240 pub book: ExternalBookRef,
241 pub sheet: String,
242 pub kind: ExternalRefKind,
243}
244
245#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
247#[derive(Debug, Clone, PartialEq, Hash)]
248pub enum ReferenceType {
249 Cell {
250 sheet: Option<String>,
251 row: u32,
252 col: u32,
253 row_abs: bool,
254 col_abs: bool,
255 },
256 Range {
257 sheet: Option<String>,
258 start_row: Option<u32>,
259 start_col: Option<u32>,
260 end_row: Option<u32>,
261 end_col: Option<u32>,
262 start_row_abs: bool,
263 start_col_abs: bool,
264 end_row_abs: bool,
265 end_col_abs: bool,
266 },
267 Cell3D {
272 sheet_first: String,
273 sheet_last: String,
274 row: u32,
275 col: u32,
276 row_abs: bool,
277 col_abs: bool,
278 },
279 Range3D {
281 sheet_first: String,
282 sheet_last: String,
283 start_row: Option<u32>,
284 start_col: Option<u32>,
285 end_row: Option<u32>,
286 end_col: Option<u32>,
287 start_row_abs: bool,
288 start_col_abs: bool,
289 end_row_abs: bool,
290 end_col_abs: bool,
291 },
292 External(ExternalReference),
293 Table(TableReference),
294 NamedRange(String),
295}
296
297impl Display for TableSpecifier {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 match self {
300 TableSpecifier::All => write!(f, "#All"),
301 TableSpecifier::Data => write!(f, "#Data"),
302 TableSpecifier::Headers => write!(f, "#Headers"),
303 TableSpecifier::Totals => write!(f, "#Totals"),
304 TableSpecifier::Row(row) => write!(f, "{row}"),
305 TableSpecifier::Column(column) => write!(f, "{column}"),
306 TableSpecifier::ColumnRange(start, end) => write!(f, "{start}:{end}"),
307 TableSpecifier::SpecialItem(item) => write!(f, "{item}"),
308 TableSpecifier::Combination(specs) => {
309 let mut first = true;
315 for spec in specs {
316 if !first {
317 write!(f, ",")?;
318 }
319 first = false;
320 match spec.as_ref() {
321 TableSpecifier::ColumnRange(start, end) => {
322 write!(f, "[{start}]:[{end}]")?;
323 }
324 other => write!(f, "[{other}]")?,
325 }
326 }
327 Ok(())
328 }
329 }
330 }
331}
332
333impl Display for TableRowSpecifier {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 match self {
336 TableRowSpecifier::Current => write!(f, "@"),
337 TableRowSpecifier::All => write!(f, "#All"),
338 TableRowSpecifier::Data => write!(f, "#Data"),
339 TableRowSpecifier::Headers => write!(f, "#Headers"),
340 TableRowSpecifier::Totals => write!(f, "#Totals"),
341 TableRowSpecifier::Index(idx) => write!(f, "{idx}"),
342 }
343 }
344}
345
346impl Display for SpecialItem {
347 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348 match self {
349 SpecialItem::Headers => write!(f, "#Headers"),
350 SpecialItem::Data => write!(f, "#Data"),
351 SpecialItem::Totals => write!(f, "#Totals"),
352 SpecialItem::All => write!(f, "#All"),
353 SpecialItem::ThisRow => write!(f, "@"),
354 }
355 }
356}
357
358fn sheet_name_needs_quoting(name: &str) -> bool {
359 formualizer_common::a1_sheet_name_needs_quoting(name)
360}
361
362#[derive(Debug, Clone)]
363struct OpenFormulaRefPart {
364 sheet: Option<String>,
365 coord: String,
366}
367
368type AxisPartWithAbs = Option<(u32, bool)>;
369type RangePartWithAbs = (AxisPartWithAbs, AxisPartWithAbs);
370
371#[derive(Debug, Clone)]
373enum SheetSpec {
374 None,
376 Single(String),
378 Range { first: String, last: String },
380}
381
382impl ReferenceType {
383 pub fn cell(sheet: Option<String>, row: u32, col: u32) -> Self {
385 Self::Cell {
386 sheet,
387 row,
388 col,
389 row_abs: false,
390 col_abs: false,
391 }
392 }
393
394 pub fn cell_with_abs(
396 sheet: Option<String>,
397 row: u32,
398 col: u32,
399 row_abs: bool,
400 col_abs: bool,
401 ) -> Self {
402 Self::Cell {
403 sheet,
404 row,
405 col,
406 row_abs,
407 col_abs,
408 }
409 }
410
411 pub fn range(
413 sheet: Option<String>,
414 start_row: Option<u32>,
415 start_col: Option<u32>,
416 end_row: Option<u32>,
417 end_col: Option<u32>,
418 ) -> Self {
419 Self::Range {
420 sheet,
421 start_row,
422 start_col,
423 end_row,
424 end_col,
425 start_row_abs: false,
426 start_col_abs: false,
427 end_row_abs: false,
428 end_col_abs: false,
429 }
430 }
431
432 #[expect(
434 clippy::too_many_arguments,
435 reason = "constructor mirroring the enum fields; an explicit signature keeps callers readable"
436 )]
437 pub fn range_with_abs(
438 sheet: Option<String>,
439 start_row: Option<u32>,
440 start_col: Option<u32>,
441 end_row: Option<u32>,
442 end_col: Option<u32>,
443 start_row_abs: bool,
444 start_col_abs: bool,
445 end_row_abs: bool,
446 end_col_abs: bool,
447 ) -> Self {
448 Self::Range {
449 sheet,
450 start_row,
451 start_col,
452 end_row,
453 end_col,
454 start_row_abs,
455 start_col_abs,
456 end_row_abs,
457 end_col_abs,
458 }
459 }
460
461 pub fn from_string(reference: &str) -> Result<Self, ParsingError> {
463 Self::parse_excel_reference(reference)
464 }
465
466 pub fn from_string_with_dialect(
468 reference: &str,
469 dialect: FormulaDialect,
470 ) -> Result<Self, ParsingError> {
471 match dialect {
472 FormulaDialect::Excel => Self::parse_excel_reference(reference),
473 FormulaDialect::OpenFormula => Self::parse_openformula_reference(reference)
474 .or_else(|_| Self::parse_excel_reference(reference)),
475 }
476 }
477
478 pub fn parse_sheet_ref(reference: &str) -> Result<SheetRef<'static>, ParsingError> {
482 Self::parse_sheet_ref_with_dialect(reference, FormulaDialect::Excel)
483 }
484
485 pub fn parse_sheet_ref_with_dialect(
487 reference: &str,
488 dialect: FormulaDialect,
489 ) -> Result<SheetRef<'static>, ParsingError> {
490 match dialect {
491 FormulaDialect::Excel => Self::parse_excel_sheet_ref(reference),
492 FormulaDialect::OpenFormula => Self::parse_openformula_sheet_ref(reference)
493 .or_else(|_| Self::parse_excel_sheet_ref(reference)),
494 }
495 }
496
497 pub fn to_sheet_ref_lossy(&self) -> Option<SheetRef<'_>> {
500 match self {
501 ReferenceType::Cell {
502 sheet,
503 row,
504 col,
505 row_abs,
506 col_abs,
507 } => {
508 let row0 = row.checked_sub(1)?;
509 let col0 = col.checked_sub(1)?;
510 let sheet_loc = match sheet.as_deref() {
511 Some(name) => SheetLocator::from_name(name),
512 None => SheetLocator::Current,
513 };
514 let coord = RelativeCoord::new(row0, col0, *row_abs, *col_abs);
515 Some(SheetRef::Cell(SheetCellRef::new(sheet_loc, coord)))
516 }
517 ReferenceType::Range {
518 sheet,
519 start_row,
520 start_col,
521 end_row,
522 end_col,
523 start_row_abs,
524 start_col_abs,
525 end_row_abs,
526 end_col_abs,
527 } => {
528 let sheet_loc = match sheet.as_deref() {
529 Some(name) => SheetLocator::from_name(name),
530 None => SheetLocator::Current,
531 };
532 let sr = start_row
533 .and_then(|v| v.checked_sub(1).map(|i| AxisBound::new(i, *start_row_abs)));
534 if start_row.is_some() && sr.is_none() {
535 return None;
536 }
537 let sc = start_col
538 .and_then(|v| v.checked_sub(1).map(|i| AxisBound::new(i, *start_col_abs)));
539 if start_col.is_some() && sc.is_none() {
540 return None;
541 }
542 let er =
543 end_row.and_then(|v| v.checked_sub(1).map(|i| AxisBound::new(i, *end_row_abs)));
544 if end_row.is_some() && er.is_none() {
545 return None;
546 }
547 let ec =
548 end_col.and_then(|v| v.checked_sub(1).map(|i| AxisBound::new(i, *end_col_abs)));
549 if end_col.is_some() && ec.is_none() {
550 return None;
551 }
552 let range = SheetRangeRef::from_parts(sheet_loc, sr, sc, er, ec).ok()?;
553 Some(SheetRef::Range(range))
554 }
555 _ => None,
556 }
557 }
558
559 fn parse_excel_sheet_ref(reference: &str) -> Result<SheetRef<'static>, ParsingError> {
560 let (spec, ref_part) = Self::extract_sheet_spec(reference);
561 let sheet = match spec {
562 SheetSpec::None => None,
563 SheetSpec::Single(name) => Some(name),
564 SheetSpec::Range { .. } => {
565 return Err(ParsingError::InvalidReference(
566 "3D references are not supported for SheetRef".to_string(),
567 ));
568 }
569 };
570
571 if ref_part.contains('[') {
572 return Err(ParsingError::InvalidReference(
573 "Table references are not supported for SheetRef".to_string(),
574 ));
575 }
576
577 let sheet_loc: SheetLocator<'static> = match sheet {
578 Some(name) => SheetLocator::from_name(name),
579 None => SheetLocator::Current,
580 };
581
582 if let Some((start, end)) = ref_part.split_once(':') {
583 let (start_col, start_row) = Self::parse_range_part_with_abs(start)?;
584 let (end_col, end_row) = Self::parse_range_part_with_abs(end)?;
585
586 let start_col = Self::axis_bound_from_1based(start_col)?;
587 let start_row = Self::axis_bound_from_1based(start_row)?;
588 let end_col = Self::axis_bound_from_1based(end_col)?;
589 let end_row = Self::axis_bound_from_1based(end_row)?;
590
591 let range =
592 SheetRangeRef::from_parts(sheet_loc, start_row, start_col, end_row, end_col)
593 .map_err(|err| ParsingError::InvalidReference(err.to_string()))?;
594 Ok(SheetRef::Range(range))
595 } else {
596 let (row, col, row_abs, col_abs) = parse_a1_1based(&ref_part)
597 .map_err(|err| ParsingError::InvalidReference(err.to_string()))?;
598 #[expect(
599 clippy::arithmetic_side_effects,
600 reason = "parse_a1_1based returns row >= 1 and col >= 1"
601 )]
602 let coord = RelativeCoord::new(row - 1, col - 1, row_abs, col_abs);
603 Ok(SheetRef::Cell(SheetCellRef::new(sheet_loc, coord)))
604 }
605 }
606
607 fn parse_openformula_sheet_ref(reference: &str) -> Result<SheetRef<'static>, ParsingError> {
608 Self::parse_excel_sheet_ref(reference)
609 }
610
611 fn axis_bound_from_1based(
612 bound: Option<(u32, bool)>,
613 ) -> Result<Option<AxisBound>, ParsingError> {
614 match bound {
615 Some((index, abs)) => AxisBound::from_excel_1based(index, abs)
616 .map(Some)
617 .map_err(|err| ParsingError::InvalidReference(err.to_string())),
618 None => Ok(None),
619 }
620 }
621
622 #[expect(
623 clippy::arithmetic_side_effects,
624 clippy::string_slice,
625 reason = "cursor bounded by slice length; the column and row runs are sliced at ASCII positions found by the scan"
626 )]
627 fn parse_range_part_with_abs(part: &str) -> Result<RangePartWithAbs, ParsingError> {
628 if let Ok((row, col, row_abs, col_abs)) = parse_a1_1based(part) {
629 return Ok((Some((col, col_abs)), Some((row, row_abs))));
630 }
631
632 let bytes = part.as_bytes();
633 let len = bytes.len();
634 let mut i = 0usize;
635
636 let mut col_abs = false;
637 let mut row_abs = false;
638
639 if bytes.get(i) == Some(&b'$') {
640 col_abs = true;
641 i += 1;
642 }
643
644 let col_start = i;
645 while bytes.get(i).is_some_and(u8::is_ascii_alphabetic) {
646 i += 1;
647 }
648
649 if i > col_start {
650 let col_str = &part[col_start..i];
651 let col1 = Self::column_to_number(col_str)?;
652
653 if i == len {
654 return Ok((Some((col1, col_abs)), None));
655 }
656
657 if bytes.get(i) == Some(&b'$') {
658 row_abs = true;
659 i += 1;
660 }
661
662 if i >= len {
663 return Err(ParsingError::InvalidReference(format!(
664 "Invalid range part: {part}"
665 )));
666 }
667
668 let row_start = i;
669 while bytes.get(i).is_some_and(u8::is_ascii_digit) {
670 i += 1;
671 }
672
673 if row_start == i || i != len {
674 return Err(ParsingError::InvalidReference(format!(
675 "Invalid range part: {part}"
676 )));
677 }
678
679 let row_str = &part[row_start..i];
680 let row1 = row_str
681 .parse::<u32>()
682 .map_err(|_| ParsingError::InvalidReference(format!("Invalid row: {row_str}")))?;
683 if row1 == 0 {
684 return Err(ParsingError::InvalidReference(format!(
685 "Invalid range part: {part}"
686 )));
687 }
688
689 return Ok((Some((col1, col_abs)), Some((row1, row_abs))));
690 }
691
692 i = 0;
693 if bytes.get(i) == Some(&b'$') {
694 row_abs = true;
695 i += 1;
696 }
697
698 let row_start = i;
699 while bytes.get(i).is_some_and(u8::is_ascii_digit) {
700 i += 1;
701 }
702
703 if row_start == i || i != len {
704 return Err(ParsingError::InvalidReference(format!(
705 "Invalid range part: {part}"
706 )));
707 }
708
709 let row_str = &part[row_start..i];
710 let row1 = row_str
711 .parse::<u32>()
712 .map_err(|_| ParsingError::InvalidReference(format!("Invalid row: {row_str}")))?;
713 if row1 == 0 {
714 return Err(ParsingError::InvalidReference(format!(
715 "Invalid range part: {part}"
716 )));
717 }
718
719 Ok((None, Some((row1, row_abs))))
720 }
721
722 fn parse_3d_reference(first: &str, last: &str, ref_part: &str) -> Result<Self, ParsingError> {
723 if first.is_empty() || last.is_empty() {
724 return Err(ParsingError::InvalidReference(format!(
725 "3D reference requires two sheet names: {first}:{last}!{ref_part}"
726 )));
727 }
728 if ref_part.is_empty() {
729 return Err(ParsingError::InvalidReference(format!(
730 "3D reference {first}:{last}! is missing a cell or range"
731 )));
732 }
733 if ref_part.contains('[') {
735 return Err(ParsingError::InvalidReference(format!(
736 "3D reference {first}:{last}!{ref_part} cannot target a table"
737 )));
738 }
739
740 if let Some((start, end)) = ref_part.split_once(':') {
741 let (start_col, start_row) = Self::parse_range_part_with_abs(start)?;
742 let (end_col, end_row) = Self::parse_range_part_with_abs(end)?;
743
744 let split = |bound: Option<(u32, bool)>| match bound {
745 Some((index, abs)) => (Some(index), abs),
746 None => (None, false),
747 };
748 let (start_col, start_col_abs) = split(start_col);
749 let (start_row, start_row_abs) = split(start_row);
750 let (end_col, end_col_abs) = split(end_col);
751 let (end_row, end_row_abs) = split(end_row);
752
753 Ok(ReferenceType::Range3D {
754 sheet_first: first.to_string(),
755 sheet_last: last.to_string(),
756 start_row,
757 start_col,
758 end_row,
759 end_col,
760 start_row_abs,
761 start_col_abs,
762 end_row_abs,
763 end_col_abs,
764 })
765 } else {
766 let (col, row, col_abs, row_abs) =
767 Self::parse_cell_reference(ref_part).map_err(|_| {
768 ParsingError::InvalidReference(format!(
769 "Invalid 3D reference target: {ref_part}"
770 ))
771 })?;
772 Ok(ReferenceType::Cell3D {
773 sheet_first: first.to_string(),
774 sheet_last: last.to_string(),
775 row,
776 col,
777 row_abs,
778 col_abs,
779 })
780 }
781 }
782
783 #[inline]
793 fn strip_workbook_zero_prefixes(reference: &str) -> Cow<'_, str> {
794 if !reference.starts_with("[0]") && !reference.starts_with("'[0]") {
797 return Cow::Borrowed(reference);
798 }
799 Self::strip_workbook_zero_prefixes_slow(reference)
800 }
801
802 #[cold]
803 #[inline(never)]
804 fn strip_workbook_zero_prefixes_slow(reference: &str) -> Cow<'_, str> {
805 let Some(after_last_bang) = reference.rsplit_once('!').map(|(_, after)| after.len()) else {
808 return Cow::Borrowed(reference);
809 };
810 let has_bang = |tail: &str| tail.len() > after_last_bang;
811
812 let mut tail = reference;
813 let mut quoted = false;
815 loop {
816 if !quoted
817 && let Some(rest) = tail.strip_prefix("[0]")
818 && has_bang(rest)
819 {
820 tail = rest.strip_prefix('!').unwrap_or(rest);
821 } else if !quoted
822 && let Some(rest) = tail.strip_prefix("'[0]")
823 && has_bang(rest)
824 {
825 quoted = true;
826 tail = rest;
827 } else if quoted
828 && let Some(rest) = tail.strip_prefix("[0]")
829 && has_bang(rest)
830 {
831 tail = rest;
832 } else {
833 break;
834 }
835 }
836 if quoted {
837 Cow::Owned(format!("'{tail}"))
838 } else {
839 Cow::Borrowed(tail)
840 }
841 }
842
843 fn parse_excel_reference(reference: &str) -> Result<Self, ParsingError> {
844 let reference = Self::strip_workbook_zero_prefixes(reference);
845 let reference = reference.as_ref();
846
847 if reference.starts_with('[') && reference.ends_with(']') && !reference.contains('!') {
854 return Self::parse_bracketed_structured_reference(reference);
855 }
856
857 let (sheet_spec, ref_part) = Self::extract_sheet_spec(reference);
859
860 let sheet = match sheet_spec {
861 SheetSpec::None => None,
862 SheetSpec::Single(name) => Some(name),
863 SheetSpec::Range { first, last } => {
866 return Self::parse_3d_reference(&first, &last, &ref_part);
867 }
868 };
869
870 if ref_part.contains('[') {
873 if Self::is_r1c1_shape(&ref_part) {
881 return Ok(ReferenceType::NamedRange(reference.to_string()));
882 }
883 return Self::parse_table_reference(&ref_part);
884 }
885
886 let external_sheet = sheet.as_deref().and_then(|s| {
887 let lb = s.rfind('[')?;
891 let (_, sheet_name) = s.get(lb..)?.split_once(']')?;
892 let token = s.strip_suffix(sheet_name)?;
895 if sheet_name.is_empty() {
896 None
897 } else {
898 Some((token, sheet_name))
899 }
900 });
901
902 if let Some((start, end)) = ref_part.split_once(':') {
903 let (start_col, start_row) = Self::parse_range_part_with_abs(start)?;
905 let (end_col, end_row) = Self::parse_range_part_with_abs(end)?;
906
907 let split = |bound: Option<(u32, bool)>| match bound {
908 Some((index, abs)) => (Some(index), abs),
909 None => (None, false),
910 };
911 let (start_col, start_col_abs) = split(start_col);
912 let (start_row, start_row_abs) = split(start_row);
913 let (end_col, end_col_abs) = split(end_col);
914 let (end_row, end_row_abs) = split(end_row);
915
916 if let Some((book_token, sheet_name)) = external_sheet {
917 Ok(ReferenceType::External(ExternalReference {
918 raw: reference.to_string(),
919 book: ExternalBookRef::Token(book_token.to_string()),
920 sheet: sheet_name.to_string(),
921 kind: ExternalRefKind::Range {
922 start_row,
923 start_col,
924 end_row,
925 end_col,
926 start_row_abs,
927 start_col_abs,
928 end_row_abs,
929 end_col_abs,
930 },
931 }))
932 } else {
933 Ok(ReferenceType::Range {
934 sheet,
935 start_row,
936 start_col,
937 end_row,
938 end_col,
939 start_row_abs,
940 start_col_abs,
941 end_row_abs,
942 end_col_abs,
943 })
944 }
945 } else {
946 match Self::parse_cell_reference(&ref_part) {
948 Ok((col, row, col_abs, row_abs)) => {
949 if let Some((book_token, sheet_name)) = external_sheet {
950 Ok(ReferenceType::External(ExternalReference {
951 raw: reference.to_string(),
952 book: ExternalBookRef::Token(book_token.to_string()),
953 sheet: sheet_name.to_string(),
954 kind: ExternalRefKind::Cell {
955 row,
956 col,
957 row_abs,
958 col_abs,
959 },
960 }))
961 } else {
962 Ok(ReferenceType::Cell {
963 sheet,
964 row,
965 col,
966 row_abs,
967 col_abs,
968 })
969 }
970 }
971 Err(_) => {
972 Ok(ReferenceType::NamedRange(reference.to_string()))
974 }
975 }
976 }
977 }
978
979 fn parse_cell_reference(reference: &str) -> Result<(u32, u32, bool, bool), ParsingError> {
981 parse_a1_1based(reference)
982 .map(|(row, col, row_abs, col_abs)| (col, row, col_abs, row_abs))
983 .map_err(|_| {
984 ParsingError::InvalidReference(format!("Invalid cell reference: {reference}"))
985 })
986 }
987
988 pub(crate) fn column_to_number(column: &str) -> Result<u32, ParsingError> {
990 col_index_from_letters_1based(column)
991 .map_err(|_| ParsingError::InvalidReference(format!("Invalid column: {column}")))
992 }
993
994 pub(crate) fn number_to_column(num: u32) -> String {
996 if num == 0 {
997 return String::new();
998 }
999 if let Some(letters) = num
1001 .checked_sub(1)
1002 .and_then(|index| COLUMN_LOOKUP.get(index as usize))
1003 {
1004 return letters.clone();
1005 }
1006
1007 col_letters_from_1based(num).unwrap_or_default()
1008 }
1009
1010 fn format_col(col: u32, abs: bool) -> String {
1011 if abs {
1012 format!("${}", Self::number_to_column(col))
1013 } else {
1014 Self::number_to_column(col)
1015 }
1016 }
1017
1018 fn format_row(row: u32, abs: bool) -> String {
1019 if abs {
1020 format!("${row}")
1021 } else {
1022 row.to_string()
1023 }
1024 }
1025}
1026
1027impl Display for ReferenceType {
1028 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1029 write!(
1030 f,
1031 "{}",
1032 match self {
1033 ReferenceType::Cell {
1034 sheet,
1035 row,
1036 col,
1037 row_abs,
1038 col_abs,
1039 } => {
1040 let col_str = Self::format_col(*col, *col_abs);
1041 let row_str = Self::format_row(*row, *row_abs);
1042
1043 if let Some(sheet_name) = sheet {
1044 format!(
1045 "{}!{col_str}{row_str}",
1046 formualizer_common::format_a1_sheet_name(sheet_name)
1047 )
1048 } else {
1049 format!("{col_str}{row_str}")
1050 }
1051 }
1052 ReferenceType::Range {
1053 sheet,
1054 start_row,
1055 start_col,
1056 end_row,
1057 end_col,
1058 start_row_abs,
1059 start_col_abs,
1060 end_row_abs,
1061 end_col_abs,
1062 } => {
1063 let start_ref = match (start_col, start_row) {
1065 (Some(col), Some(row)) => format!(
1066 "{}{}",
1067 Self::format_col(*col, *start_col_abs),
1068 Self::format_row(*row, *start_row_abs)
1069 ),
1070 (Some(col), None) => Self::format_col(*col, *start_col_abs),
1071 (None, Some(row)) => Self::format_row(*row, *start_row_abs),
1072 (None, None) => "".to_string(), };
1074
1075 let end_ref = match (end_col, end_row) {
1077 (Some(col), Some(row)) => format!(
1078 "{}{}",
1079 Self::format_col(*col, *end_col_abs),
1080 Self::format_row(*row, *end_row_abs)
1081 ),
1082 (Some(col), None) => Self::format_col(*col, *end_col_abs),
1083 (None, Some(row)) => Self::format_row(*row, *end_row_abs),
1084 (None, None) => "".to_string(), };
1086
1087 let range_part = format!("{start_ref}:{end_ref}");
1088
1089 if let Some(sheet_name) = sheet {
1090 format!(
1091 "{}!{range_part}",
1092 formualizer_common::format_a1_sheet_name(sheet_name)
1093 )
1094 } else {
1095 range_part
1096 }
1097 }
1098 ReferenceType::Cell3D {
1099 sheet_first,
1100 sheet_last,
1101 row,
1102 col,
1103 row_abs,
1104 col_abs,
1105 } => {
1106 let col_str = Self::format_col(*col, *col_abs);
1107 let row_str = Self::format_row(*row, *row_abs);
1108 let prefix = format_3d_sheet_prefix(sheet_first, sheet_last);
1109 format!("{prefix}!{col_str}{row_str}")
1110 }
1111 ReferenceType::Range3D {
1112 sheet_first,
1113 sheet_last,
1114 start_row,
1115 start_col,
1116 end_row,
1117 end_col,
1118 start_row_abs,
1119 start_col_abs,
1120 end_row_abs,
1121 end_col_abs,
1122 } => {
1123 let start_ref = match (start_col, start_row) {
1124 (Some(col), Some(row)) => format!(
1125 "{}{}",
1126 Self::format_col(*col, *start_col_abs),
1127 Self::format_row(*row, *start_row_abs)
1128 ),
1129 (Some(col), None) => Self::format_col(*col, *start_col_abs),
1130 (None, Some(row)) => Self::format_row(*row, *start_row_abs),
1131 (None, None) => "".to_string(),
1132 };
1133 let end_ref = match (end_col, end_row) {
1134 (Some(col), Some(row)) => format!(
1135 "{}{}",
1136 Self::format_col(*col, *end_col_abs),
1137 Self::format_row(*row, *end_row_abs)
1138 ),
1139 (Some(col), None) => Self::format_col(*col, *end_col_abs),
1140 (None, Some(row)) => Self::format_row(*row, *end_row_abs),
1141 (None, None) => "".to_string(),
1142 };
1143 let range_part = format!("{start_ref}:{end_ref}");
1144 let prefix = format_3d_sheet_prefix(sheet_first, sheet_last);
1145 format!("{prefix}!{range_part}")
1146 }
1147 ReferenceType::External(ext) => ext.raw.clone(),
1148 ReferenceType::Table(table_ref) => {
1149 if let Some(specifier) = &table_ref.specifier {
1150 match specifier {
1153 TableSpecifier::Column(column) => {
1154 format!("{}[{}]", table_ref.name, column.trim())
1155 }
1156 TableSpecifier::ColumnRange(start, end) => {
1157 format!("{}[{}:{}]", table_ref.name, start.trim(), end.trim())
1158 }
1159 _ => {
1160 format!("{}[{}]", table_ref.name, specifier)
1162 }
1163 }
1164 } else {
1165 table_ref.name.clone()
1166 }
1167 }
1168 ReferenceType::NamedRange(name) => name.clone(),
1169 }
1170 )
1171 }
1172}
1173
1174fn format_3d_sheet_prefix(first: &str, last: &str) -> String {
1178 if sheet_name_needs_quoting(first) || sheet_name_needs_quoting(last) {
1179 let escaped = format!("{first}:{last}").replace('\'', "''");
1180 format!("'{escaped}'")
1181 } else {
1182 format!("{first}:{last}")
1183 }
1184}
1185
1186impl TryFrom<&str> for ReferenceType {
1187 type Error = ParsingError;
1188
1189 fn try_from(value: &str) -> Result<Self, Self::Error> {
1190 ReferenceType::from_string(value)
1191 }
1192}
1193
1194impl FromStr for ReferenceType {
1195 type Err = ParsingError;
1196
1197 fn from_str(s: &str) -> Result<Self, Self::Err> {
1198 ReferenceType::from_string(s)
1199 }
1200}
1201
1202impl ReferenceType {
1203 pub fn normalise(&self) -> String {
1205 format!("{self}")
1206 }
1207
1208 #[expect(
1213 clippy::arithmetic_side_effects,
1214 clippy::string_slice,
1215 reason = "cursor bounded by slice length; the name is sliced between ASCII quotes or up to an ASCII delimiter"
1216 )]
1217 fn read_sheet_segment(reference: &str, start: usize) -> Option<(String, usize, bool)> {
1218 let bytes = reference.as_bytes();
1219 let first = *bytes.get(start)?;
1220
1221 if first == b'\'' {
1222 let mut i = start + 1;
1224 let body_start = i;
1225 while let Some(&b) = bytes.get(i) {
1226 if b == b'\'' {
1227 if bytes.get(i + 1) == Some(&b'\'') {
1228 i += 2;
1229 continue;
1230 }
1231 let raw = &reference[body_start..i];
1232 let name = raw.replace("''", "'");
1233 return Some((name, i + 1, true));
1234 }
1235 i += 1;
1236 }
1237 None
1238 } else {
1239 let mut i = start;
1242 while let Some(&b) = bytes.get(i) {
1243 match b {
1244 b':' | b'!' | b'\'' | b' ' | b'\t' | b'\n' | b'\r' => break,
1245 _ => i += 1,
1246 }
1247 }
1248 if i == start {
1249 None
1250 } else {
1251 Some((reference[start..i].to_string(), i, false))
1252 }
1253 }
1254 }
1255
1256 #[expect(
1259 clippy::arithmetic_side_effects,
1260 clippy::string_slice,
1261 reason = "byte offset from ASCII delimiter search: after_first, after_second and bang index ASCII `:` or `!` bytes inside reference"
1262 )]
1263 fn extract_sheet_spec(reference: &str) -> (SheetSpec, String) {
1264 let Some((first_name, after_first, first_quoted)) = Self::read_sheet_segment(reference, 0)
1265 else {
1266 return Self::extract_sheet_spec_fallback(reference);
1269 };
1270 let bytes = reference.as_bytes();
1271
1272 if bytes.get(after_first) == Some(&b':') {
1274 let second_start = after_first + 1;
1275 if let Some((second_name, after_second, _)) =
1276 Self::read_sheet_segment(reference, second_start)
1277 && bytes.get(after_second) == Some(&b'!')
1278 {
1279 let ref_part = reference[after_second + 1..].to_string();
1280 return (
1281 SheetSpec::Range {
1282 first: first_name,
1283 last: second_name,
1284 },
1285 ref_part,
1286 );
1287 }
1288
1289 if let Some(bang) = reference
1295 .get(second_start..)
1296 .and_then(|rest| rest.find('!'))
1297 {
1298 let ref_part = reference[second_start + bang + 1..].to_string();
1299 return (
1300 SheetSpec::Range {
1301 first: first_name,
1302 last: String::new(),
1303 },
1304 ref_part,
1305 );
1306 }
1307 }
1308
1309 if bytes.get(after_first) == Some(&b'!') {
1311 let ref_part = reference[after_first + 1..].to_string();
1312 if first_quoted
1317 && !first_name.contains('[')
1318 && let Some((first, last)) = first_name.split_once(':')
1319 && !first.is_empty()
1320 && !last.is_empty()
1321 && !last.contains(':')
1322 {
1323 return (
1324 SheetSpec::Range {
1325 first: first.to_string(),
1326 last: last.to_string(),
1327 },
1328 ref_part,
1329 );
1330 }
1331 return (SheetSpec::Single(first_name), ref_part);
1332 }
1333
1334 Self::extract_sheet_spec_fallback(reference)
1337 }
1338
1339 fn extract_sheet_spec_fallback(reference: &str) -> (SheetSpec, String) {
1340 let mut chars = reference.chars();
1345 if chars.next().is_some()
1346 && let Some((_, ref_part)) = chars.as_str().split_once('!')
1347 && let Some(sheet) = reference
1348 .strip_suffix(ref_part)
1349 .and_then(|head| head.strip_suffix('!'))
1350 {
1351 return (SheetSpec::Single(sheet.to_string()), ref_part.to_string());
1352 }
1353
1354 (SheetSpec::None, reference.to_string())
1355 }
1356
1357 #[expect(
1367 clippy::arithmetic_side_effects,
1368 reason = "cursor bounded by slice length"
1369 )]
1370 fn is_r1c1_shape(s: &str) -> bool {
1371 let bytes = s.as_bytes();
1372 let len = bytes.len();
1373 let mut i = 0usize;
1374 let mut anchored = false;
1375
1376 if bytes.get(i) != Some(&b'R') {
1377 return false;
1378 }
1379 i += 1;
1380
1381 let row_digits_start = i;
1382 while bytes.get(i).is_some_and(u8::is_ascii_digit) {
1383 i += 1;
1384 }
1385 if i > row_digits_start {
1386 anchored = true;
1387 }
1388
1389 if bytes.get(i) == Some(&b'[') {
1390 i += 1;
1391 if bytes.get(i) == Some(&b'-') {
1392 i += 1;
1393 }
1394 let n_start = i;
1395 while bytes.get(i).is_some_and(u8::is_ascii_digit) {
1396 i += 1;
1397 }
1398 if i == n_start || bytes.get(i) != Some(&b']') {
1399 return false;
1400 }
1401 i += 1;
1402 anchored = true;
1403 }
1404
1405 if bytes.get(i) != Some(&b'C') {
1406 return false;
1407 }
1408 i += 1;
1409
1410 let col_digits_start = i;
1411 while bytes.get(i).is_some_and(u8::is_ascii_digit) {
1412 i += 1;
1413 }
1414 if i > col_digits_start {
1415 anchored = true;
1416 }
1417
1418 if bytes.get(i) == Some(&b'[') {
1419 i += 1;
1420 if bytes.get(i) == Some(&b'-') {
1421 i += 1;
1422 }
1423 let n_start = i;
1424 while bytes.get(i).is_some_and(u8::is_ascii_digit) {
1425 i += 1;
1426 }
1427 if i == n_start || bytes.get(i) != Some(&b']') {
1428 return false;
1429 }
1430 i += 1;
1431 anchored = true;
1432 }
1433
1434 i == len && anchored
1435 }
1436
1437 fn parse_table_reference(reference: &str) -> Result<Self, ParsingError> {
1443 let (table_name, specifier_str) = reference
1444 .find('[')
1445 .and_then(|bracket_pos| reference.split_at_checked(bracket_pos))
1446 .ok_or_else(|| {
1447 ParsingError::InvalidReference(format!(
1448 "Missing '[' in table reference: {reference}"
1449 ))
1450 })?;
1451 let table_name = table_name.trim();
1452 if table_name.is_empty() {
1453 return Err(ParsingError::InvalidReference(reference.to_string()));
1454 }
1455
1456 let specifier = structured_ref::parse_full_specifier(specifier_str)?;
1457
1458 Ok(ReferenceType::Table(TableReference {
1459 name: table_name.to_string(),
1460 specifier,
1461 }))
1462 }
1463
1464 fn parse_bracketed_structured_reference(reference: &str) -> Result<Self, ParsingError> {
1471 debug_assert!(reference.starts_with('[') && reference.ends_with(']'));
1472 let specifier = structured_ref::parse_full_specifier(reference)?;
1473
1474 match specifier {
1475 Some(TableSpecifier::Column(name)) => Ok(ReferenceType::Table(TableReference {
1476 name,
1477 specifier: Some(TableSpecifier::SpecialItem(SpecialItem::Data)),
1478 })),
1479 other => Ok(ReferenceType::Table(TableReference {
1480 name: String::new(),
1481 specifier: other,
1482 })),
1483 }
1484 }
1485
1486 fn parse_openformula_reference(reference: &str) -> Result<Self, ParsingError> {
1487 if let Some(inner) = reference
1488 .strip_prefix('[')
1489 .and_then(|rest| rest.strip_suffix(']'))
1490 {
1491 if inner.is_empty() {
1492 return Err(ParsingError::InvalidReference(
1493 "Empty OpenFormula reference".to_string(),
1494 ));
1495 }
1496
1497 let (start_part_str, end_part_str) = match inner.split_once(':') {
1498 Some((start, end)) => (start, Some(end)),
1499 None => (inner, None),
1500 };
1501
1502 let start_part = Self::parse_openformula_part(start_part_str)?;
1503 let end_part = if let Some(part) = end_part_str {
1504 Some(Self::parse_openformula_part(part)?)
1505 } else {
1506 None
1507 };
1508
1509 let sheet = match (&start_part.sheet, &end_part) {
1510 (Some(sheet), Some(end)) => {
1511 if let Some(end_sheet) = &end.sheet
1512 && end_sheet != sheet
1513 {
1514 return Err(ParsingError::InvalidReference(format!(
1515 "Mismatched sheets in reference: {sheet} vs {end_sheet}"
1516 )));
1517 }
1518 Some(sheet.clone())
1519 }
1520 (Some(sheet), None) => Some(sheet.clone()),
1521 (None, Some(end)) => end.sheet.clone(),
1522 (None, None) => None,
1523 };
1524
1525 let mut excel_like = String::new();
1526 if let Some(sheet_name) = sheet {
1527 if sheet_name_needs_quoting(&sheet_name) {
1528 let escaped = sheet_name.replace('\'', "''");
1529 excel_like.push('\'');
1530 excel_like.push_str(&escaped);
1531 excel_like.push('\'');
1532 } else {
1533 excel_like.push_str(&sheet_name);
1534 }
1535 excel_like.push('!');
1536 }
1537
1538 excel_like.push_str(&start_part.coord);
1539 if let Some(end) = end_part {
1540 excel_like.push(':');
1541 excel_like.push_str(&end.coord);
1542 }
1543
1544 return Self::parse_excel_reference(&excel_like);
1545 }
1546
1547 Err(ParsingError::InvalidReference(format!(
1548 "Unsupported OpenFormula reference: {reference}"
1549 )))
1550 }
1551
1552 fn parse_openformula_part(part: &str) -> Result<OpenFormulaRefPart, ParsingError> {
1553 let trimmed = part.trim();
1554 if trimmed.is_empty() {
1555 return Err(ParsingError::InvalidReference(
1556 "Empty component in OpenFormula reference".to_string(),
1557 ));
1558 }
1559
1560 if trimmed == "." {
1561 return Err(ParsingError::InvalidReference(
1562 "Incomplete OpenFormula reference component".to_string(),
1563 ));
1564 }
1565
1566 if trimmed.starts_with('[') {
1567 return Err(ParsingError::InvalidReference(format!(
1569 "Unexpected '[' in OpenFormula reference component: {trimmed}"
1570 )));
1571 }
1572
1573 let (sheet, coord_slice) = if let Some(stripped) = trimmed.strip_prefix('.') {
1574 (None, stripped.trim())
1575 } else if let Some(dot_idx) = Self::find_openformula_sheet_separator(trimmed) {
1576 #[expect(
1577 clippy::string_slice,
1578 clippy::arithmetic_side_effects,
1579 reason = "byte offset from ASCII delimiter search: dot_idx is the index of a `.`"
1580 )]
1581 let (sheet_part, coord_part) = (&trimmed[..dot_idx], &trimmed[dot_idx + 1..]);
1582 let (sheet_part, coord_part) = (sheet_part.trim(), coord_part.trim());
1583 if coord_part.is_empty() {
1584 return Err(ParsingError::InvalidReference(format!(
1585 "Missing coordinate in OpenFormula reference component: {trimmed}"
1586 )));
1587 }
1588 let sheet_name = Self::normalise_openformula_sheet(sheet_part)?;
1589 (Some(sheet_name), coord_part)
1590 } else {
1591 (None, trimmed)
1592 };
1593
1594 let coord = coord_slice.trim_start_matches('.').trim().to_string();
1595
1596 if coord.is_empty() {
1597 return Err(ParsingError::InvalidReference(format!(
1598 "Missing coordinate in OpenFormula reference component: {trimmed}"
1599 )));
1600 }
1601
1602 Ok(OpenFormulaRefPart { sheet, coord })
1603 }
1604
1605 fn normalise_openformula_sheet(sheet: &str) -> Result<String, ParsingError> {
1606 let without_abs = sheet.trim().trim_start_matches('$');
1607
1608 if let Some(quoted) = without_abs.strip_prefix('\'') {
1609 let Some(inner) = quoted.strip_suffix('\'') else {
1610 return Err(ParsingError::InvalidReference(format!(
1611 "Unterminated sheet name in OpenFormula reference: {sheet}"
1612 )));
1613 };
1614 Ok(inner.replace("''", "'"))
1615 } else {
1616 Ok(without_abs.to_string())
1617 }
1618 }
1619
1620 #[expect(
1621 clippy::arithmetic_side_effects,
1622 reason = "cursor bounded by slice length"
1623 )]
1624 fn find_openformula_sheet_separator(part: &str) -> Option<usize> {
1625 let bytes = part.as_bytes();
1626 let mut i = 0;
1627 let mut in_quotes = false;
1628
1629 while let Some(&b) = bytes.get(i) {
1630 match b {
1631 b'\'' => {
1632 if bytes.get(i + 1) == Some(&b'\'') {
1633 i += 2;
1634 continue;
1635 }
1636 in_quotes = !in_quotes;
1637 i += 1;
1638 }
1639 b'.' if !in_quotes => return Some(i),
1640 _ => i += 1,
1641 }
1642 }
1643
1644 None
1645 }
1646
1647 pub fn to_excel_string(&self) -> String {
1654 match self {
1655 ReferenceType::Cell {
1656 sheet,
1657 row,
1658 col,
1659 row_abs,
1660 col_abs,
1661 } => {
1662 let col_str = Self::format_col(*col, *col_abs);
1663 let row_str = Self::format_row(*row, *row_abs);
1664 if let Some(s) = sheet {
1665 if sheet_name_needs_quoting(s) {
1666 let escaped_name = s.replace('\'', "''");
1667 format!("'{}'!{}{}", escaped_name, col_str, row_str)
1668 } else {
1669 format!("{}!{}{}", s, col_str, row_str)
1670 }
1671 } else {
1672 format!("{}{}", col_str, row_str)
1673 }
1674 }
1675 ReferenceType::Range {
1676 sheet,
1677 start_row,
1678 start_col,
1679 end_row,
1680 end_col,
1681 start_row_abs,
1682 start_col_abs,
1683 end_row_abs,
1684 end_col_abs,
1685 } => {
1686 let start_ref = match (start_col, start_row) {
1688 (Some(col), Some(row)) => format!(
1689 "{}{}",
1690 Self::format_col(*col, *start_col_abs),
1691 Self::format_row(*row, *start_row_abs)
1692 ),
1693 (Some(col), None) => Self::format_col(*col, *start_col_abs),
1694 (None, Some(row)) => Self::format_row(*row, *start_row_abs),
1695 (None, None) => "".to_string(), };
1697
1698 let end_ref = match (end_col, end_row) {
1700 (Some(col), Some(row)) => format!(
1701 "{}{}",
1702 Self::format_col(*col, *end_col_abs),
1703 Self::format_row(*row, *end_row_abs)
1704 ),
1705 (Some(col), None) => Self::format_col(*col, *end_col_abs),
1706 (None, Some(row)) => Self::format_row(*row, *end_row_abs),
1707 (None, None) => "".to_string(), };
1709
1710 let range_part = format!("{start_ref}:{end_ref}");
1711
1712 if let Some(s) = sheet {
1713 if sheet_name_needs_quoting(s) {
1714 let escaped_name = s.replace('\'', "''");
1715 format!("'{escaped_name}'!{range_part}")
1716 } else {
1717 format!("{s}!{range_part}")
1718 }
1719 } else {
1720 range_part
1721 }
1722 }
1723 ReferenceType::Cell3D { .. } | ReferenceType::Range3D { .. } => format!("{self}"),
1724 ReferenceType::External(ext) => ext.raw.clone(),
1725 ReferenceType::Table(table_ref) => {
1726 if let Some(specifier) = &table_ref.specifier {
1727 format!("{}[{}]", table_ref.name, specifier)
1728 } else {
1729 table_ref.name.clone()
1730 }
1731 }
1732 ReferenceType::NamedRange(name) => name.clone(),
1733 }
1734 }
1735}
1736
1737#[non_exhaustive]
1742#[derive(Clone, Copy, PartialEq, Eq)]
1743pub enum UnaryOperator {
1744 Plus,
1745 Minus,
1746 Percent,
1747 Spill,
1748 ImplicitIntersection,
1749}
1750#[cfg(feature = "serde")]
1753impl Serialize for UnaryOperator {
1754 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1755 serializer.serialize_str(self.as_str())
1756 }
1757}
1758#[cfg(feature = "serde")]
1759impl<'de> Deserialize<'de> for UnaryOperator {
1760 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1761 let value = String::deserialize(deserializer)?;
1762 Self::try_from(value.as_str()).map_err(serde::de::Error::custom)
1763 }
1764}
1765impl UnaryOperator {
1766 pub const fn as_str(self) -> &'static str {
1768 match self {
1769 Self::Plus => "+",
1770 Self::Minus => "-",
1771 Self::Percent => "%",
1772 Self::Spill => "#",
1773 Self::ImplicitIntersection => "@",
1774 }
1775 }
1776}
1777impl fmt::Debug for UnaryOperator {
1780 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1781 fmt::Debug::fmt(self.as_str(), f)
1782 }
1783}
1784impl Display for UnaryOperator {
1785 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1786 f.write_str(self.as_str())
1787 }
1788}
1789impl TryFrom<&str> for UnaryOperator {
1790 type Error = &'static str;
1791 fn try_from(value: &str) -> Result<Self, Self::Error> {
1792 match value {
1793 "+" => Ok(Self::Plus),
1794 "-" => Ok(Self::Minus),
1795 "%" => Ok(Self::Percent),
1796 "#" => Ok(Self::Spill),
1797 "@" => Ok(Self::ImplicitIntersection),
1798 _ => Err("unsupported formula operator"),
1799 }
1800 }
1801}
1802impl std::hash::Hash for UnaryOperator {
1803 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1804 self.as_str().hash(state);
1805 }
1806}
1807
1808#[non_exhaustive]
1813#[derive(Clone, Copy, PartialEq, Eq)]
1814pub enum BinaryOperator {
1815 Add,
1816 Subtract,
1817 Multiply,
1818 Divide,
1819 Power,
1820 Concat,
1821 Equal,
1822 NotEqual,
1823 Less,
1824 Greater,
1825 LessEqual,
1826 GreaterEqual,
1827 Range,
1828 Union,
1829 Intersection,
1830}
1831#[cfg(feature = "serde")]
1834impl Serialize for BinaryOperator {
1835 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1836 serializer.serialize_str(self.as_str())
1837 }
1838}
1839#[cfg(feature = "serde")]
1840impl<'de> Deserialize<'de> for BinaryOperator {
1841 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1842 let value = String::deserialize(deserializer)?;
1843 Self::try_from(value.as_str()).map_err(serde::de::Error::custom)
1844 }
1845}
1846impl BinaryOperator {
1847 pub const fn as_str(self) -> &'static str {
1849 match self {
1850 Self::Add => "+",
1851 Self::Subtract => "-",
1852 Self::Multiply => "*",
1853 Self::Divide => "/",
1854 Self::Power => "^",
1855 Self::Concat => "&",
1856 Self::Equal => "=",
1857 Self::NotEqual => "<>",
1858 Self::Less => "<",
1859 Self::Greater => ">",
1860 Self::LessEqual => "<=",
1861 Self::GreaterEqual => ">=",
1862 Self::Range => ":",
1863 Self::Union => ",",
1864 Self::Intersection => " ",
1865 }
1866 }
1867}
1868impl fmt::Debug for BinaryOperator {
1871 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1872 fmt::Debug::fmt(self.as_str(), f)
1873 }
1874}
1875impl Display for BinaryOperator {
1876 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1877 f.write_str(self.as_str())
1878 }
1879}
1880fn is_constant_literal(node: &ASTNode) -> bool {
1885 matches!(
1886 &node.node_type,
1887 ASTNodeType::Literal(value) if !matches!(value, LiteralValue::Error(_))
1888 )
1889}
1890
1891impl TryFrom<&str> for BinaryOperator {
1892 type Error = &'static str;
1893 fn try_from(value: &str) -> Result<Self, Self::Error> {
1894 match value {
1895 "+" => Ok(Self::Add),
1896 "-" => Ok(Self::Subtract),
1897 "*" => Ok(Self::Multiply),
1898 "/" => Ok(Self::Divide),
1899 "^" => Ok(Self::Power),
1900 "&" => Ok(Self::Concat),
1901 "=" => Ok(Self::Equal),
1902 "<>" => Ok(Self::NotEqual),
1903 "<" => Ok(Self::Less),
1904 ">" => Ok(Self::Greater),
1905 "<=" => Ok(Self::LessEqual),
1906 ">=" => Ok(Self::GreaterEqual),
1907 ":" => Ok(Self::Range),
1908 "," => Ok(Self::Union),
1909 " " => Ok(Self::Intersection),
1910 _ => Err("unsupported formula operator"),
1911 }
1912 }
1913}
1914impl std::hash::Hash for BinaryOperator {
1915 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1916 self.as_str().hash(state);
1917 }
1918}
1919
1920#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1922#[derive(Debug, Clone, PartialEq, Hash)]
1923pub enum ASTNodeType {
1924 Literal(LiteralValue),
1925 Omitted,
1930 Reference {
1931 original: String, reference: ReferenceType, },
1934 UnaryOp {
1935 op: UnaryOperator,
1936 expr: Box<ASTNode>,
1937 },
1938 BinaryOp {
1939 op: BinaryOperator,
1940 left: Box<ASTNode>,
1941 right: Box<ASTNode>,
1942 },
1943 Function {
1944 name: String,
1945 args: Vec<ASTNode>, },
1947 Call {
1950 callee: Box<ASTNode>,
1951 args: Vec<ASTNode>,
1952 },
1953 Array(Vec<Vec<ASTNode>>), }
1955
1956impl Display for ASTNodeType {
1957 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1958 match self {
1959 ASTNodeType::Literal(value) => write!(f, "Literal({value})"),
1960 ASTNodeType::Omitted => write!(f, "Omitted"),
1961 ASTNodeType::Reference { reference, .. } => write!(f, "Reference({reference:?})"),
1962 ASTNodeType::UnaryOp { op, expr } => write!(f, "UnaryOp({op}, {expr})"),
1963 ASTNodeType::BinaryOp { op, left, right } => {
1964 write!(f, "BinaryOp({op}, {left}, {right})")
1965 }
1966 ASTNodeType::Function { name, args } => write!(f, "Function({name}, {args:?})"),
1967 ASTNodeType::Call { callee, args } => write!(f, "Call({callee}, {args:?})"),
1968 ASTNodeType::Array(rows) => write!(f, "Array({rows:?})"),
1969 }
1970 }
1971}
1972
1973#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1990#[derive(Debug, Clone, PartialEq)]
1991pub struct ASTNode {
1992 pub node_type: ASTNodeType,
1993 pub source_token: Option<Token>,
1994 pub contains_volatile: bool,
1999}
2000
2001impl ASTNode {
2002 pub fn new(node_type: ASTNodeType, source_token: Option<Token>) -> Self {
2003 ASTNode {
2004 node_type,
2005 source_token,
2006 contains_volatile: false,
2007 }
2008 }
2009
2010 pub fn new_with_volatile(
2012 node_type: ASTNodeType,
2013 source_token: Option<Token>,
2014 contains_volatile: bool,
2015 ) -> Self {
2016 ASTNode {
2017 node_type,
2018 source_token,
2019 contains_volatile,
2020 }
2021 }
2022
2023 pub fn contains_volatile(&self) -> bool {
2025 self.contains_volatile
2026 }
2027
2028 pub fn fingerprint(&self) -> u64 {
2040 self.calculate_hash()
2041 }
2042
2043 pub fn calculate_hash(&self) -> u64 {
2054 let mut hasher = FormulaHasher::new();
2055 self.hash_node(&mut hasher);
2056 hasher.finish()
2057 }
2058
2059 fn hash_node(&self, hasher: &mut FormulaHasher) {
2060 match &self.node_type {
2061 ASTNodeType::Literal(value) => {
2062 hasher.write(&[1]); value.hash(hasher);
2064 }
2065 ASTNodeType::Omitted => hasher.write(&[8]),
2066 ASTNodeType::Reference { reference, .. } => {
2067 hasher.write(&[2]); reference.hash(hasher);
2069 }
2070 ASTNodeType::UnaryOp { op, expr } => {
2071 hasher.write(&[3]); hasher.write(op.as_str().as_bytes());
2073 expr.hash_node(hasher);
2074 }
2075 ASTNodeType::BinaryOp { op, left, right } => {
2076 hasher.write(&[4]); hasher.write(op.as_str().as_bytes());
2078 left.hash_node(hasher);
2079 right.hash_node(hasher);
2080 }
2081 ASTNodeType::Function { name, args } => {
2082 hasher.write(&[5]); let name_lower = name.to_lowercase();
2085 hasher.write(name_lower.as_bytes());
2086 hasher.write_usize(args.len());
2087 for arg in args {
2088 arg.hash_node(hasher);
2089 }
2090 }
2091 ASTNodeType::Call { callee, args } => {
2092 hasher.write(&[7]); callee.hash_node(hasher);
2094 hasher.write_usize(args.len());
2095 for arg in args {
2096 arg.hash_node(hasher);
2097 }
2098 }
2099 ASTNodeType::Array(rows) => {
2100 hasher.write(&[6]); hasher.write_usize(rows.len());
2102 for row in rows {
2103 hasher.write_usize(row.len());
2104 for item in row {
2105 item.hash_node(hasher);
2106 }
2107 }
2108 }
2109 }
2110 }
2111
2112 pub fn get_dependencies(&self) -> Vec<&ReferenceType> {
2113 let mut dependencies = Vec::new();
2114 self.collect_dependencies(&mut dependencies);
2115 dependencies
2116 }
2117
2118 pub fn get_dependency_strings(&self) -> Vec<String> {
2119 self.get_dependencies()
2120 .into_iter()
2121 .map(|dep| format!("{dep}"))
2122 .collect()
2123 }
2124
2125 fn collect_dependencies<'a>(&'a self, dependencies: &mut Vec<&'a ReferenceType>) {
2129 let mut stack: Vec<&'a ASTNode> = vec![self];
2130 while let Some(node) = stack.pop() {
2131 match &node.node_type {
2132 ASTNodeType::Reference { reference, .. } => dependencies.push(reference),
2133 ASTNodeType::UnaryOp { expr, .. } => stack.push(expr),
2134 ASTNodeType::BinaryOp { left, right, .. } => {
2135 stack.push(right);
2136 stack.push(left);
2137 }
2138 ASTNodeType::Function { args, .. } => stack.extend(args.iter().rev()),
2139 ASTNodeType::Call { callee, args } => {
2140 stack.extend(args.iter().rev());
2141 stack.push(callee);
2142 }
2143 ASTNodeType::Array(rows) => {
2144 for row in rows.iter().rev() {
2145 stack.extend(row.iter().rev());
2146 }
2147 }
2148 ASTNodeType::Literal(_) | ASTNodeType::Omitted => {}
2149 }
2150 }
2151 }
2152
2153 pub fn refs(&self) -> RefIter<'_> {
2156 RefIter {
2157 stack: smallvec::smallvec![self],
2158 }
2159 }
2160
2161 pub fn visit_refs<V: FnMut(RefView<'_>)>(&self, mut visitor: V) {
2163 let mut stack: Vec<&ASTNode> = Vec::with_capacity(8);
2164 stack.push(self);
2165 while let Some(node) = stack.pop() {
2166 match &node.node_type {
2167 ASTNodeType::Reference { reference, .. } => visitor(RefView::from(reference)),
2168 ASTNodeType::UnaryOp { expr, .. } => stack.push(expr),
2169 ASTNodeType::BinaryOp { left, right, .. } => {
2170 stack.push(right);
2172 stack.push(left);
2173 }
2174 ASTNodeType::Function { args, .. } => {
2175 for a in args.iter().rev() {
2176 stack.push(a);
2177 }
2178 }
2179 ASTNodeType::Call { callee, args } => {
2180 for a in args.iter().rev() {
2181 stack.push(a);
2182 }
2183 stack.push(callee);
2184 }
2185 ASTNodeType::Array(rows) => {
2186 for r in rows.iter().rev() {
2187 for item in r.iter().rev() {
2188 stack.push(item);
2189 }
2190 }
2191 }
2192 ASTNodeType::Literal(_) | ASTNodeType::Omitted => {}
2193 }
2194 }
2195 }
2196
2197 pub fn collect_references(&self, policy: &CollectPolicy) -> SmallVec<[ReferenceType; 4]> {
2199 let mut out: SmallVec<[ReferenceType; 4]> = SmallVec::new();
2200 self.visit_refs(|rv| match rv {
2201 RefView::Cell {
2202 sheet,
2203 row,
2204 col,
2205 row_abs,
2206 col_abs,
2207 } => out.push(ReferenceType::Cell {
2208 sheet: sheet.map(|s| s.to_string()),
2209 row,
2210 col,
2211 row_abs,
2212 col_abs,
2213 }),
2214 RefView::Range {
2215 sheet,
2216 start_row,
2217 start_col,
2218 end_row,
2219 end_col,
2220 start_row_abs,
2221 start_col_abs,
2222 end_row_abs,
2223 end_col_abs,
2224 } => {
2225 if policy.expand_small_ranges
2227 && let (Some(sr), Some(sc), Some(er), Some(ec)) =
2228 (start_row, start_col, end_row, end_col)
2229 {
2230 let rows = u64::from(er.saturating_sub(sr)) + 1;
2233 let cols = u64::from(ec.saturating_sub(sc)) + 1;
2234 let area = rows.saturating_mul(cols);
2235 let limit = u64::try_from(policy.range_expansion_limit).unwrap_or(u64::MAX);
2236 if area <= limit {
2237 let row_abs = start_row_abs && end_row_abs;
2238 let col_abs = start_col_abs && end_col_abs;
2239 for r in sr..=er {
2240 for c in sc..=ec {
2241 out.push(ReferenceType::Cell {
2242 sheet: sheet.map(|s| s.to_string()),
2243 row: r,
2244 col: c,
2245 row_abs,
2246 col_abs,
2247 });
2248 }
2249 }
2250 return; }
2252 }
2253 out.push(ReferenceType::Range {
2254 sheet: sheet.map(|s| s.to_string()),
2255 start_row,
2256 start_col,
2257 end_row,
2258 end_col,
2259 start_row_abs,
2260 start_col_abs,
2261 end_row_abs,
2262 end_col_abs,
2263 });
2264 }
2265 RefView::Cell3D {
2266 sheet_first,
2267 sheet_last,
2268 row,
2269 col,
2270 row_abs,
2271 col_abs,
2272 } => out.push(ReferenceType::Cell3D {
2273 sheet_first: sheet_first.to_string(),
2274 sheet_last: sheet_last.to_string(),
2275 row,
2276 col,
2277 row_abs,
2278 col_abs,
2279 }),
2280 RefView::Range3D {
2281 sheet_first,
2282 sheet_last,
2283 start_row,
2284 start_col,
2285 end_row,
2286 end_col,
2287 start_row_abs,
2288 start_col_abs,
2289 end_row_abs,
2290 end_col_abs,
2291 } => out.push(ReferenceType::Range3D {
2292 sheet_first: sheet_first.to_string(),
2293 sheet_last: sheet_last.to_string(),
2294 start_row,
2295 start_col,
2296 end_row,
2297 end_col,
2298 start_row_abs,
2299 start_col_abs,
2300 end_row_abs,
2301 end_col_abs,
2302 }),
2303 RefView::External {
2304 raw,
2305 book,
2306 sheet,
2307 kind,
2308 } => out.push(ReferenceType::External(ExternalReference {
2309 raw: raw.to_string(),
2310 book: ExternalBookRef::Token(book.to_string()),
2311 sheet: sheet.to_string(),
2312 kind,
2313 })),
2314 RefView::Table { name, specifier } => out.push(ReferenceType::Table(TableReference {
2315 name: name.to_string(),
2316 specifier: specifier.cloned(),
2317 })),
2318 RefView::NamedRange { name } => {
2319 if policy.include_names {
2320 out.push(ReferenceType::NamedRange(name.to_string()));
2321 }
2322 }
2323 });
2324 out
2325 }
2326 pub fn update_sheet_references(&mut self, target_name: Option<&str>, new_name: &str) {
2335 let mut stack: Vec<&mut ASTNode> = vec![self];
2336 while let Some(node) = stack.pop() {
2337 match &mut node.node_type {
2338 ASTNodeType::Reference {
2339 reference:
2340 ReferenceType::Cell { sheet, .. } | ReferenceType::Range { sheet, .. },
2341 ..
2342 } => {
2343 if let Some(current_sheet) = sheet
2344 && (target_name.is_none() || target_name == Some(current_sheet.as_str()))
2345 {
2346 *sheet = Some(new_name.to_string());
2347 }
2348 }
2349 ASTNodeType::Reference {
2350 reference:
2351 ReferenceType::Cell3D {
2352 sheet_first,
2353 sheet_last,
2354 ..
2355 }
2356 | ReferenceType::Range3D {
2357 sheet_first,
2358 sheet_last,
2359 ..
2360 },
2361 ..
2362 } => {
2363 if target_name.is_none() || target_name == Some(sheet_first.as_str()) {
2364 *sheet_first = new_name.to_string();
2365 }
2366 if target_name.is_none() || target_name == Some(sheet_last.as_str()) {
2367 *sheet_last = new_name.to_string();
2368 }
2369 }
2370 ASTNodeType::UnaryOp { expr, .. } => stack.push(expr),
2371 ASTNodeType::BinaryOp { left, right, .. } => {
2372 stack.push(right);
2373 stack.push(left);
2374 }
2375 ASTNodeType::Function { args, .. } => stack.extend(args.iter_mut().rev()),
2376 ASTNodeType::Call { callee, args } => {
2377 stack.extend(args.iter_mut().rev());
2378 stack.push(callee);
2379 }
2380 ASTNodeType::Array(rows) => {
2381 for row in rows.iter_mut().rev() {
2382 stack.extend(row.iter_mut().rev());
2383 }
2384 }
2385 ASTNodeType::Reference { .. } | ASTNodeType::Literal(_) | ASTNodeType::Omitted => {}
2386 }
2387 }
2388 }
2389}
2390
2391#[derive(Clone, Copy, Debug)]
2393pub enum RefView<'a> {
2394 Cell {
2395 sheet: Option<&'a str>,
2396 row: u32,
2397 col: u32,
2398 row_abs: bool,
2399 col_abs: bool,
2400 },
2401 Range {
2402 sheet: Option<&'a str>,
2403 start_row: Option<u32>,
2404 start_col: Option<u32>,
2405 end_row: Option<u32>,
2406 end_col: Option<u32>,
2407 start_row_abs: bool,
2408 start_col_abs: bool,
2409 end_row_abs: bool,
2410 end_col_abs: bool,
2411 },
2412 Cell3D {
2414 sheet_first: &'a str,
2415 sheet_last: &'a str,
2416 row: u32,
2417 col: u32,
2418 row_abs: bool,
2419 col_abs: bool,
2420 },
2421 Range3D {
2423 sheet_first: &'a str,
2424 sheet_last: &'a str,
2425 start_row: Option<u32>,
2426 start_col: Option<u32>,
2427 end_row: Option<u32>,
2428 end_col: Option<u32>,
2429 start_row_abs: bool,
2430 start_col_abs: bool,
2431 end_row_abs: bool,
2432 end_col_abs: bool,
2433 },
2434 External {
2435 raw: &'a str,
2436 book: &'a str,
2437 sheet: &'a str,
2438 kind: ExternalRefKind,
2439 },
2440 Table {
2441 name: &'a str,
2442 specifier: Option<&'a TableSpecifier>,
2443 },
2444 NamedRange {
2445 name: &'a str,
2446 },
2447}
2448
2449impl<'a> From<&'a ReferenceType> for RefView<'a> {
2450 fn from(r: &'a ReferenceType) -> Self {
2451 match r {
2452 ReferenceType::Cell {
2453 sheet,
2454 row,
2455 col,
2456 row_abs,
2457 col_abs,
2458 } => RefView::Cell {
2459 sheet: sheet.as_deref(),
2460 row: *row,
2461 col: *col,
2462 row_abs: *row_abs,
2463 col_abs: *col_abs,
2464 },
2465 ReferenceType::Range {
2466 sheet,
2467 start_row,
2468 start_col,
2469 end_row,
2470 end_col,
2471 start_row_abs,
2472 start_col_abs,
2473 end_row_abs,
2474 end_col_abs,
2475 } => RefView::Range {
2476 sheet: sheet.as_deref(),
2477 start_row: *start_row,
2478 start_col: *start_col,
2479 end_row: *end_row,
2480 end_col: *end_col,
2481 start_row_abs: *start_row_abs,
2482 start_col_abs: *start_col_abs,
2483 end_row_abs: *end_row_abs,
2484 end_col_abs: *end_col_abs,
2485 },
2486 ReferenceType::Cell3D {
2487 sheet_first,
2488 sheet_last,
2489 row,
2490 col,
2491 row_abs,
2492 col_abs,
2493 } => RefView::Cell3D {
2494 sheet_first: sheet_first.as_str(),
2495 sheet_last: sheet_last.as_str(),
2496 row: *row,
2497 col: *col,
2498 row_abs: *row_abs,
2499 col_abs: *col_abs,
2500 },
2501 ReferenceType::Range3D {
2502 sheet_first,
2503 sheet_last,
2504 start_row,
2505 start_col,
2506 end_row,
2507 end_col,
2508 start_row_abs,
2509 start_col_abs,
2510 end_row_abs,
2511 end_col_abs,
2512 } => RefView::Range3D {
2513 sheet_first: sheet_first.as_str(),
2514 sheet_last: sheet_last.as_str(),
2515 start_row: *start_row,
2516 start_col: *start_col,
2517 end_row: *end_row,
2518 end_col: *end_col,
2519 start_row_abs: *start_row_abs,
2520 start_col_abs: *start_col_abs,
2521 end_row_abs: *end_row_abs,
2522 end_col_abs: *end_col_abs,
2523 },
2524 ReferenceType::External(ext) => RefView::External {
2525 raw: ext.raw.as_str(),
2526 book: ext.book.token(),
2527 sheet: ext.sheet.as_str(),
2528 kind: ext.kind,
2529 },
2530 ReferenceType::Table(tr) => RefView::Table {
2531 name: tr.name.as_str(),
2532 specifier: tr.specifier.as_ref(),
2533 },
2534 ReferenceType::NamedRange(name) => RefView::NamedRange { name },
2535 }
2536 }
2537}
2538
2539pub struct RefIter<'a> {
2541 stack: smallvec::SmallVec<[&'a ASTNode; 8]>,
2542}
2543
2544impl<'a> Iterator for RefIter<'a> {
2545 type Item = RefView<'a>;
2546 fn next(&mut self) -> Option<Self::Item> {
2547 while let Some(node) = self.stack.pop() {
2548 match &node.node_type {
2549 ASTNodeType::Reference { reference, .. } => return Some(RefView::from(reference)),
2550 ASTNodeType::UnaryOp { expr, .. } => self.stack.push(expr),
2551 ASTNodeType::BinaryOp { left, right, .. } => {
2552 self.stack.push(right);
2553 self.stack.push(left);
2554 }
2555 ASTNodeType::Function { args, .. } => {
2556 for a in args.iter().rev() {
2557 self.stack.push(a);
2558 }
2559 }
2560 ASTNodeType::Call { callee, args } => {
2561 for a in args.iter().rev() {
2562 self.stack.push(a);
2563 }
2564 self.stack.push(callee);
2565 }
2566 ASTNodeType::Array(rows) => {
2567 for r in rows.iter().rev() {
2568 for item in r.iter().rev() {
2569 self.stack.push(item);
2570 }
2571 }
2572 }
2573 ASTNodeType::Literal(_) | ASTNodeType::Omitted => {}
2574 }
2575 }
2576 None
2577 }
2578}
2579
2580#[derive(Debug, Clone)]
2582pub struct CollectPolicy {
2583 pub expand_small_ranges: bool,
2584 pub range_expansion_limit: usize,
2585 pub include_names: bool,
2586}
2587
2588impl Default for CollectPolicy {
2589 fn default() -> Self {
2590 Self {
2591 expand_small_ranges: false,
2592 range_expansion_limit: 0,
2593 include_names: true,
2594 }
2595 }
2596}
2597
2598impl Display for ASTNode {
2599 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2600 write!(f, "{}", self.node_type)
2601 }
2602}
2603
2604impl std::hash::Hash for ASTNode {
2605 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2606 let hash = self.calculate_hash();
2607 state.write_u64(hash);
2608 }
2609}
2610
2611impl From<TokenizerError> for ParserError {
2612 fn from(err: TokenizerError) -> Self {
2613 ParserError {
2614 message: err.message,
2615 position: Some(err.pos),
2616 }
2617 }
2618}
2619
2620pub struct Parser {
2626 source: Arc<str>,
2627 tokens: Arc<[TokenSpan]>,
2628 position: usize,
2629 volatility_classifier: Option<VolatilityClassifierBox>,
2630 dialect: FormulaDialect,
2631 in_call_args_depth: usize,
2634 depth: usize,
2638 limits: ParserLimits,
2639 nodes: usize,
2640 pending_error: Option<ParserError>,
2641}
2642
2643struct BuiltNode {
2646 node: ASTNode,
2647 height: usize,
2648}
2649#[derive(Default)]
2650struct BuiltArgs {
2651 nodes: Vec<ASTNode>,
2652 height: usize,
2653 volatile: bool,
2654}
2655impl BuiltArgs {
2656 fn push(&mut self, child: BuiltNode) {
2657 self.height = self.height.max(child.height);
2658 self.volatile |= child.node.contains_volatile;
2659 self.nodes.push(child.node);
2660 }
2661}
2662impl std::ops::Deref for BuiltNode {
2663 type Target = ASTNode;
2664 fn deref(&self) -> &ASTNode {
2665 &self.node
2666 }
2667}
2668
2669impl Parser {
2670 pub fn new<T: AsRef<str>>(formula: T) -> Result<Self, TokenizerError> {
2672 Self::new_with_dialect(formula, FormulaDialect::Excel)
2673 }
2674
2675 pub fn try_from_formula(formula: &str) -> Result<Self, TokenizerError> {
2677 Self::new(formula)
2678 }
2679
2680 pub fn new_with_dialect<T: AsRef<str>>(
2682 formula: T,
2683 dialect: FormulaDialect,
2684 ) -> Result<Self, TokenizerError> {
2685 Self::new_with_limits(formula.as_ref(), dialect, ParserLimits::default())
2686 }
2687
2688 fn new_with_limits(
2689 formula: &str,
2690 dialect: FormulaDialect,
2691 limits: ParserLimits,
2692 ) -> Result<Self, TokenizerError> {
2693 limits.check_source(formula)?;
2694 let spans = crate::tokenizer::tokenize_spans_with_limits(formula, dialect, limits)?;
2695 let mut parser = Self::from_source_and_tokens(
2696 Arc::from(formula),
2697 Arc::from(spans.into_boxed_slice()),
2698 dialect,
2699 );
2700 parser.limits = limits;
2701 Ok(parser)
2702 }
2703
2704 pub fn from_token_stream(stream: &TokenStream) -> Self {
2706 let limits = ParserLimits::default();
2707 let error = stream.admission_error(limits).map(ParserError::from);
2708 if let Some(error) = error {
2709 let mut parser =
2710 Self::from_source_and_tokens(Arc::from(""), Arc::from([]), stream.dialect());
2711 parser.pending_error = Some(error);
2712 return parser;
2713 }
2714 Self::from_source_and_tokens(
2715 Arc::from(stream.source()),
2716 Arc::from(stream.spans.clone().into_boxed_slice()),
2717 stream.dialect(),
2718 )
2719 }
2720
2721 fn from_source_and_tokens(
2722 source: Arc<str>,
2723 tokens: Arc<[TokenSpan]>,
2724 dialect: FormulaDialect,
2725 ) -> Self {
2726 Parser {
2727 source,
2728 tokens,
2729 position: 0,
2730 volatility_classifier: None,
2731 dialect,
2732 in_call_args_depth: 0,
2733 depth: 0,
2734 limits: ParserLimits::default(),
2735 nodes: 0,
2736 pending_error: None,
2737 }
2738 }
2739
2740 pub fn with_volatility_classifier<F>(mut self, f: F) -> Self
2743 where
2744 F: Fn(&str) -> bool + Send + Sync + 'static,
2745 {
2746 self.volatility_classifier = Some(Box::new(f));
2747 self
2748 }
2749
2750 #[expect(
2752 clippy::arithmetic_side_effects,
2753 reason = "child heights are at most the AST height limit and the number of admitted nodes, so 1 + height cannot overflow"
2754 )]
2755 fn unary(&mut self, expr: BuiltNode, span: TokenSpan) -> Result<BuiltNode, ParserError> {
2756 let height = 1 + expr.height;
2757 self.admit(height)?;
2758 let token = self.span_to_token(&span);
2759 let volatile = expr.contains_volatile;
2760 Ok(BuiltNode {
2761 height,
2762 node: ASTNode::new_with_volatile(
2763 ASTNodeType::UnaryOp {
2764 op: UnaryOperator::try_from(token.value.as_str()).map_err(|message| {
2765 ParserError {
2766 message: message.to_string(),
2767 position: Some(span.start),
2768 }
2769 })?,
2770 expr: Box::new(expr.node),
2771 },
2772 Some(token),
2773 volatile,
2774 ),
2775 })
2776 }
2777 #[expect(
2778 clippy::arithmetic_side_effects,
2779 reason = "child heights are at most the AST height limit and the number of admitted nodes, so 1 + height cannot overflow"
2780 )]
2781 fn binary(
2782 &mut self,
2783 left: BuiltNode,
2784 right: BuiltNode,
2785 span: TokenSpan,
2786 ) -> Result<BuiltNode, ParserError> {
2787 let height = 1 + left.height.max(right.height);
2788 self.admit(height)?;
2789 let token = self.span_to_token(&span);
2790 let op = BinaryOperator::try_from(token.value.as_str()).map_err(|message| ParserError {
2791 message: if token.value == ";" {
2792 "';' is an argument separator, not an operator in this position; OpenFormula union uses '~'".to_string()
2793 } else {
2794 message.to_string()
2795 },
2796 position: Some(span.start),
2797 })?;
2798 if op == BinaryOperator::Union
2799 && (is_constant_literal(&left.node) || is_constant_literal(&right.node))
2800 {
2801 return Err(ParserError {
2802 message: "the union operator joins references; a number, text or logical constant is not a reference".to_string(),
2803 position: Some(span.start),
2804 });
2805 }
2806 let volatile = left.contains_volatile || right.contains_volatile;
2807 Ok(BuiltNode {
2808 height,
2809 node: ASTNode::new_with_volatile(
2810 ASTNodeType::BinaryOp {
2811 op,
2812 left: Box::new(left.node),
2813 right: Box::new(right.node),
2814 },
2815 Some(token),
2816 volatile,
2817 ),
2818 })
2819 }
2820 #[expect(
2821 clippy::arithmetic_side_effects,
2822 reason = "child heights are at most the AST height limit and the number of admitted nodes, so 1 + height cannot overflow"
2823 )]
2824 fn call(&mut self, left: BuiltNode, args: BuiltArgs) -> Result<BuiltNode, ParserError> {
2825 let height = 1 + left.height.max(args.height);
2826 self.admit(height)?;
2827 let volatile = left.contains_volatile || args.volatile;
2828 Ok(BuiltNode {
2829 height,
2830 node: ASTNode::new_with_volatile(
2831 ASTNodeType::Call {
2832 callee: Box::new(left.node),
2833 args: args.nodes,
2834 },
2835 None,
2836 volatile,
2837 ),
2838 })
2839 }
2840 fn byte_offset(&self, index: usize) -> usize {
2844 self.tokens
2845 .get(index)
2846 .map_or(self.source.len(), |token| token.start)
2847 }
2848
2849 #[inline]
2851 fn peek(&self) -> Option<TokenSpan> {
2852 self.tokens.get(self.position).copied()
2853 }
2854
2855 #[inline]
2857 fn peek_is(&self, token_type: TokenType, subtype: TokenSubType) -> bool {
2858 self.peek()
2859 .is_some_and(|t| t.token_type == token_type && t.subtype == subtype)
2860 }
2861
2862 #[inline]
2864 #[expect(
2865 clippy::arithmetic_side_effects,
2866 reason = "callers advance only past a token they read with peek, so position < tokens.len()"
2867 )]
2868 fn advance(&mut self) {
2869 self.position += 1;
2870 }
2871
2872 fn skip_whitespace(&mut self) {
2873 while self
2874 .peek()
2875 .is_some_and(|t| t.token_type == TokenType::Whitespace)
2876 {
2877 self.advance();
2878 }
2879 }
2880
2881 #[expect(
2882 clippy::string_slice,
2883 reason = "spans come from the span tokenizer on this source (char-boundary invariant), or from an external TokenStream whose spans admission_error checked with source.get"
2884 )]
2885 fn span_value(&self, span: &TokenSpan) -> &str {
2886 &self.source[span.start..span.end]
2887 }
2888
2889 fn semantic_span_value(&self, span: &TokenSpan) -> &str {
2890 let value = self.span_value(span);
2891 if span.token_type == TokenType::OpInfix
2892 && value.as_bytes().contains(&b' ')
2893 && value
2894 .as_bytes()
2895 .iter()
2896 .all(|byte| matches!(byte, b' ' | b'\t' | b'\r' | b'\n'))
2897 {
2898 " "
2899 } else {
2900 value
2901 }
2902 }
2903
2904 fn span_to_token(&self, span: &TokenSpan) -> Token {
2905 Token::new_with_span(
2906 self.semantic_span_value(span).to_string(),
2907 span.token_type,
2908 span.subtype,
2909 span.start,
2910 span.end,
2911 )
2912 }
2913
2914 fn span_precedence(&self, span: &TokenSpan) -> Option<(u8, Associativity)> {
2915 if !matches!(
2916 span.token_type,
2917 TokenType::OpPrefix | TokenType::OpInfix | TokenType::OpPostfix
2918 ) {
2919 return None;
2920 }
2921
2922 let op = if span.token_type == TokenType::OpPrefix {
2923 "u"
2924 } else {
2925 self.semantic_span_value(span)
2926 };
2927
2928 match op {
2929 "#" => Some((11, Associativity::Left)),
2930 ":" => Some((10, Associativity::Left)),
2931 " " => Some((9, Associativity::Left)),
2932 "," => Some((8, Associativity::Left)),
2933 "%" => Some((7, Associativity::Left)),
2934 "u" => Some((6, Associativity::Right)),
2935 "^" => Some((5, Associativity::Left)),
2936 "*" | "/" => Some((4, Associativity::Left)),
2937 "+" | "-" => Some((3, Associativity::Left)),
2938 "&" => Some((2, Associativity::Left)),
2939 "=" | "<" | ">" | "<=" | ">=" | "<>" => Some((1, Associativity::Left)),
2940 _ => None,
2941 }
2942 }
2943
2944 #[cold]
2945 fn limit_error(&self, budget: &str, limit: usize) -> ParserError {
2946 ParserError {
2947 message: format!("Formula {budget} (max {limit})"),
2948 position: Some(self.byte_offset(self.position)),
2949 }
2950 }
2951 #[expect(
2952 clippy::arithmetic_side_effects,
2953 reason = "nodes < ast_nodes is checked before the increment"
2954 )]
2955 fn admit(&mut self, height: usize) -> Result<(), ParserError> {
2956 if self.nodes >= self.limits.ast_nodes {
2957 return Err(self.limit_error("AST node limit exceeded", self.limits.ast_nodes));
2958 }
2959 if height > self.limits.ast_height {
2960 return Err(self.limit_error("AST height limit exceeded", self.limits.ast_height));
2961 }
2962 self.nodes += 1;
2963 Ok(())
2964 }
2965 fn omitted(&mut self) -> Result<BuiltNode, ParserError> {
2966 self.admit(1)?;
2967 Ok(BuiltNode {
2968 node: ASTNode::new(ASTNodeType::Omitted, None),
2969 height: 1,
2970 })
2971 }
2972 pub fn parse(&mut self) -> Result<ASTNode, ParserError> {
2973 if let Some(error) = &self.pending_error {
2974 return Err(ParserError {
2975 message: error.message.clone(),
2976 position: error.position,
2977 });
2978 }
2979 self.nodes = 0;
2980 if self.tokens.is_empty() {
2981 return Err(ParserError {
2982 message: "No tokens to parse".to_string(),
2983 position: None,
2984 });
2985 }
2986
2987 self.skip_whitespace();
2988 let Some(first) = self.peek() else {
2989 return Err(ParserError {
2990 message: "No tokens to parse".to_string(),
2991 position: None,
2992 });
2993 };
2994
2995 if first.token_type == TokenType::Literal {
2996 let span = first;
2997 self.advance();
2998 self.skip_whitespace();
2999 if let Some(extra) = self.peek() {
3000 return Err(ParserError {
3001 message: format!("Unexpected token at position {}: {extra:?}", extra.start),
3002 position: Some(extra.start),
3003 });
3004 }
3005
3006 let token = self.span_to_token(&span);
3007 self.admit(1)?;
3008 return Ok(ASTNode::new(
3009 ASTNodeType::Literal(LiteralValue::Text(token.value.clone())),
3010 Some(token),
3011 ));
3012 }
3013
3014 let ast = self.parse_expression()?;
3015 self.skip_whitespace();
3016 if let Some(extra) = self.peek() {
3017 return Err(ParserError {
3018 message: format!("Unexpected token at position {}: {extra:?}", extra.start),
3019 position: Some(extra.start),
3020 });
3021 }
3022 Ok(ast.node)
3023 }
3024
3025 fn parse_expression(&mut self) -> Result<BuiltNode, ParserError> {
3026 self.parse_bp(0)
3027 }
3028
3029 #[expect(
3030 clippy::arithmetic_side_effects,
3031 reason = "depth counts active parse_bp frames: it is incremented once per frame and decremented on every exit, so it is bounded by the call stack"
3032 )]
3033 fn parse_bp(&mut self, min_precedence: u8) -> Result<BuiltNode, ParserError> {
3034 self.depth += 1;
3037 if self.depth > self.limits.pratt_frames {
3038 self.depth -= 1;
3039 return Err(self.limit_error("nesting too deep", self.limits.pratt_frames));
3040 }
3041 let result = self.parse_bp_inner(min_precedence);
3042 self.depth -= 1;
3043 result
3044 }
3045
3046 #[expect(
3047 clippy::arithmetic_side_effects,
3048 reason = "precedence comes from the span_precedence table (at most 11), so precedence + 1 fits in u8"
3049 )]
3050 fn parse_bp_inner(&mut self, min_precedence: u8) -> Result<BuiltNode, ParserError> {
3051 let mut left = self.parse_prefix()?;
3052
3053 loop {
3054 self.skip_whitespace();
3055 let Some(token) = self.peek() else {
3056 break;
3057 };
3058
3059 if token.token_type == TokenType::Paren && token.subtype == TokenSubType::Open {
3062 self.advance();
3063 let args = self.parse_call_arguments()?;
3064 left = self.call(left, args)?;
3065 continue;
3066 }
3067
3068 if token.token_type == TokenType::OpPostfix {
3069 let (precedence, _) = self
3070 .span_precedence(&token)
3071 .unwrap_or((0, Associativity::Left));
3072 if precedence < min_precedence {
3073 break;
3074 }
3075
3076 self.advance();
3077 left = self.unary(left, token)?;
3078 continue;
3079 }
3080
3081 if token.token_type != TokenType::OpInfix {
3082 break;
3083 }
3084
3085 if self.in_call_args_depth > 0 && self.span_value(&token) == "," {
3088 break;
3089 }
3090
3091 let (precedence, associativity) = self
3092 .span_precedence(&token)
3093 .unwrap_or((0, Associativity::Left));
3094 if precedence < min_precedence {
3095 break;
3096 }
3097
3098 let op_span = token;
3099 self.advance();
3100
3101 let next_min_precedence = if associativity == Associativity::Left {
3102 precedence + 1
3103 } else {
3104 precedence
3105 };
3106
3107 let right = self.parse_bp(next_min_precedence)?;
3108 left = self.binary(left, right, op_span)?;
3109 }
3110
3111 Ok(left)
3112 }
3113
3114 fn parse_prefix(&mut self) -> Result<BuiltNode, ParserError> {
3115 self.skip_whitespace();
3116 if let Some(op_span) = self.peek()
3117 && op_span.token_type == TokenType::OpPrefix
3118 {
3119 self.advance();
3120
3121 let (precedence, _) = self
3122 .span_precedence(&op_span)
3123 .unwrap_or((0, Associativity::Right));
3124
3125 let expr = self.parse_bp(precedence)?;
3126 return self.unary(expr, op_span);
3127 }
3128
3129 self.parse_primary()
3130 }
3131
3132 fn parse_primary(&mut self) -> Result<BuiltNode, ParserError> {
3133 self.skip_whitespace();
3134 let Some(token) = self.peek() else {
3135 return Err(ParserError {
3136 message: "Unexpected end of tokens".to_string(),
3137 position: Some(self.byte_offset(self.position)),
3138 });
3139 };
3140
3141 match token.token_type {
3142 TokenType::Operand => {
3143 self.advance();
3144 self.parse_operand(token)
3145 }
3146 TokenType::Func => {
3147 self.advance();
3148 self.parse_function(token)
3149 }
3150 TokenType::Paren if token.subtype == TokenSubType::Open => {
3151 self.advance();
3152 let expr = self.parse_expression()?;
3153 self.skip_whitespace();
3154 if !self.peek_is(TokenType::Paren, TokenSubType::Close) {
3155 return Err(ParserError {
3156 message: "Expected closing parenthesis".to_string(),
3157 position: Some(self.byte_offset(self.position)),
3158 });
3159 }
3160 self.advance();
3161 Ok(expr)
3162 }
3163 TokenType::Array if token.subtype == TokenSubType::Open => {
3164 self.advance();
3165 self.parse_array()
3166 }
3167 _ => Err(ParserError {
3168 message: format!("Unexpected token: {token:?}"),
3169 position: Some(self.byte_offset(self.position)),
3170 }),
3171 }
3172 }
3173
3174 fn parse_operand(&mut self, span: TokenSpan) -> Result<BuiltNode, ParserError> {
3175 self.admit(1)?;
3176 let value = self.span_value(&span);
3177 let token = self.span_to_token(&span);
3178
3179 match span.subtype {
3180 TokenSubType::Number => {
3181 let value = value.parse::<f64>().map_err(|_| ParserError {
3182 message: format!("Invalid number: {value}"),
3183 position: Some(self.byte_offset(self.position)),
3184 })?;
3185 Ok(BuiltNode {
3186 height: 1,
3187 node: ASTNode::new(
3188 ASTNodeType::Literal(LiteralValue::Number(value)),
3189 Some(token),
3190 ),
3191 })
3192 }
3193 TokenSubType::Text => {
3194 let text = match value.strip_prefix('"').and_then(|v| v.strip_suffix('"')) {
3195 Some(inner) => inner.replace("\"\"", "\""),
3196 None => value.to_string(),
3197 };
3198 Ok(BuiltNode {
3199 height: 1,
3200 node: ASTNode::new(ASTNodeType::Literal(LiteralValue::Text(text)), Some(token)),
3201 })
3202 }
3203 TokenSubType::Logical => {
3204 let v = value.eq_ignore_ascii_case("TRUE");
3205 Ok(BuiltNode {
3206 height: 1,
3207 node: ASTNode::new(ASTNodeType::Literal(LiteralValue::Boolean(v)), Some(token)),
3208 })
3209 }
3210 TokenSubType::Error => {
3211 let error = ExcelError::from_error_string(value);
3212 Ok(BuiltNode {
3213 height: 1,
3214 node: ASTNode::new(
3215 ASTNodeType::Literal(LiteralValue::Error(error)),
3216 Some(token),
3217 ),
3218 })
3219 }
3220 TokenSubType::Range => {
3221 let reference = ReferenceType::from_string_with_dialect(value, self.dialect)
3222 .map_err(|e| ParserError {
3223 message: format!("Invalid reference '{value}': {e}"),
3224 position: Some(self.byte_offset(self.position)),
3225 })?;
3226 Ok(BuiltNode {
3227 height: 1,
3228 node: ASTNode::new(
3229 ASTNodeType::Reference {
3230 original: value.to_string(),
3231 reference,
3232 },
3233 Some(token),
3234 ),
3235 })
3236 }
3237 _ => Err(ParserError {
3238 message: format!("Unexpected operand subtype: {:?}", span.subtype),
3239 position: Some(self.byte_offset(self.position)),
3240 }),
3241 }
3242 }
3243
3244 #[expect(
3245 clippy::arithmetic_side_effects,
3246 reason = "child heights are at most the AST height limit and the number of admitted nodes, so 1 + height cannot overflow"
3247 )]
3248 fn parse_function(&mut self, func_span: TokenSpan) -> Result<BuiltNode, ParserError> {
3249 let Some(name) = self.span_value(&func_span).strip_suffix('(') else {
3250 return Err(ParserError {
3251 message: "Invalid function token".to_string(),
3252 position: Some(self.byte_offset(self.position)),
3253 });
3254 };
3255 let name = name.to_string();
3256 let args = self.parse_function_arguments()?;
3257
3258 let this_is_volatile = self
3259 .volatility_classifier
3260 .as_ref()
3261 .map(|f| f(name.as_str()))
3262 .unwrap_or(false);
3263 let args_volatile = args.volatile;
3264
3265 let height = 1 + args.height;
3266 self.admit(height)?;
3267 let func_token = self.span_to_token(&func_span);
3268 Ok(BuiltNode {
3269 height,
3270 node: ASTNode::new_with_volatile(
3271 ASTNodeType::Function {
3272 name,
3273 args: args.nodes,
3274 },
3275 Some(func_token),
3276 this_is_volatile || args_volatile,
3277 ),
3278 })
3279 }
3280
3281 #[expect(
3286 clippy::arithmetic_side_effects,
3287 reason = "in_call_args_depth is incremented and decremented once per call, so it counts active calls and is bounded by the Pratt-frame limit"
3288 )]
3289 fn parse_call_arguments(&mut self) -> Result<BuiltArgs, ParserError> {
3290 let mut args = BuiltArgs::default();
3291
3292 self.skip_whitespace();
3293 if self.peek_is(TokenType::Paren, TokenSubType::Close) {
3294 self.advance();
3295 return Ok(args);
3296 }
3297
3298 self.in_call_args_depth += 1;
3299 let result = (|| -> Result<BuiltArgs, ParserError> {
3300 let mut expecting_argument = true;
3301 let mut saw_argument = false;
3302 loop {
3303 self.skip_whitespace();
3304 let Some(token) = self.peek() else {
3305 return Err(ParserError {
3306 message: "Unterminated call argument list".to_string(),
3307 position: Some(self.byte_offset(self.position)),
3308 });
3309 };
3310
3311 let is_separator = (token.token_type == TokenType::Sep
3312 && token.subtype == TokenSubType::Arg)
3313 || (token.token_type == TokenType::OpInfix && self.span_value(&token) == ",");
3314 let is_close =
3315 token.token_type == TokenType::Paren && token.subtype == TokenSubType::Close;
3316
3317 if expecting_argument {
3318 if is_close {
3319 if saw_argument {
3320 args.push(self.omitted()?);
3321 }
3322 self.advance();
3323 return Ok(std::mem::take(&mut args));
3324 }
3325 if is_separator {
3326 args.push(self.omitted()?);
3327 saw_argument = true;
3328 self.advance();
3329 } else {
3330 args.push(self.parse_expression()?);
3331 saw_argument = true;
3332 expecting_argument = false;
3333 }
3334 } else if is_separator {
3335 self.advance();
3336 expecting_argument = true;
3337 } else if is_close {
3338 self.advance();
3339 return Ok(std::mem::take(&mut args));
3340 } else {
3341 return Err(ParserError {
3342 message: format!("Expected ',' or ')' in call arguments, got {token:?}"),
3343 position: Some(self.byte_offset(self.position)),
3344 });
3345 }
3346 }
3347 })();
3348 self.in_call_args_depth -= 1;
3349 result
3350 }
3351
3352 fn parse_function_arguments(&mut self) -> Result<BuiltArgs, ParserError> {
3353 let mut args = BuiltArgs::default();
3354
3355 self.skip_whitespace();
3356 if self.peek_is(TokenType::Func, TokenSubType::Close) {
3357 self.advance();
3358 return Ok(args);
3359 }
3360
3361 let mut expecting_argument = true;
3362 let mut saw_argument = false;
3363 loop {
3364 self.skip_whitespace();
3365 let Some(token) = self.peek() else {
3366 return Err(ParserError {
3367 message: "Unterminated function argument list".to_string(),
3368 position: Some(self.byte_offset(self.position)),
3369 });
3370 };
3371
3372 let is_separator =
3373 token.token_type == TokenType::Sep && token.subtype == TokenSubType::Arg;
3374 let is_close =
3375 token.token_type == TokenType::Func && token.subtype == TokenSubType::Close;
3376
3377 if expecting_argument {
3378 if is_close {
3379 if saw_argument {
3380 args.push(self.omitted()?);
3381 }
3382 self.advance();
3383 return Ok(args);
3384 }
3385 if is_separator {
3386 args.push(self.omitted()?);
3387 saw_argument = true;
3388 self.advance();
3389 } else {
3390 args.push(self.parse_expression()?);
3391 saw_argument = true;
3392 expecting_argument = false;
3393 }
3394 } else if is_separator {
3395 self.advance();
3396 expecting_argument = true;
3397 } else if is_close {
3398 self.advance();
3399 return Ok(args);
3400 } else {
3401 return Err(ParserError {
3402 message: format!("Expected ',' or ')' in function arguments, got {token:?}"),
3403 position: Some(self.byte_offset(self.position)),
3404 });
3405 }
3406 }
3407 }
3408
3409 #[expect(
3410 clippy::arithmetic_side_effects,
3411 reason = "child heights are at most the AST height limit and the number of admitted nodes, so 1 + height cannot overflow"
3412 )]
3413 fn append_array_item(
3414 &mut self,
3415 row: &mut Vec<ASTNode>,
3416 height: &mut usize,
3417 volatile: &mut bool,
3418 ) -> Result<(), ParserError> {
3419 let child = self.parse_expression()?;
3420 *height = (*height).max(1 + child.height);
3421 *volatile |= child.contains_volatile;
3422 row.push(child.node);
3423 Ok(())
3424 }
3425 fn finish_array(
3426 &mut self,
3427 rows: Vec<Vec<ASTNode>>,
3428 height: usize,
3429 volatile: bool,
3430 ) -> Result<BuiltNode, ParserError> {
3431 self.admit(height)?;
3432 Ok(BuiltNode {
3433 height,
3434 node: ASTNode::new_with_volatile(ASTNodeType::Array(rows), None, volatile),
3435 })
3436 }
3437 fn parse_array(&mut self) -> Result<BuiltNode, ParserError> {
3438 let mut rows = Vec::new();
3439 let mut current_row = Vec::new();
3440 let mut height = 1;
3441 let mut contains_volatile = false;
3442
3443 self.skip_whitespace();
3444 if self.peek_is(TokenType::Array, TokenSubType::Close) {
3445 self.advance();
3446 return self.finish_array(rows, 1, false);
3447 }
3448
3449 self.append_array_item(&mut current_row, &mut height, &mut contains_volatile)?;
3450
3451 while self.position < self.tokens.len() {
3452 self.skip_whitespace();
3453 let Some(token) = self.peek() else {
3454 break;
3455 };
3456
3457 match (token.token_type, token.subtype) {
3461 (TokenType::Sep, TokenSubType::Arg) => {
3462 self.advance();
3463 self.append_array_item(&mut current_row, &mut height, &mut contains_volatile)?;
3464 }
3465 (TokenType::Sep, TokenSubType::Row) => {
3466 self.advance();
3467 rows.push(current_row);
3468 current_row = Vec::new();
3469 self.append_array_item(&mut current_row, &mut height, &mut contains_volatile)?;
3470 }
3471 (TokenType::Array, TokenSubType::Close) => {
3472 self.advance();
3473 rows.push(current_row);
3474 break;
3475 }
3476 _ => {
3477 return Err(ParserError {
3478 message: format!("Unexpected token in array: {token:?}"),
3479 position: Some(self.byte_offset(self.position)),
3480 });
3481 }
3482 }
3483 }
3484
3485 if let Some(first) = rows.first()
3489 && rows.iter().any(|row| row.len() != first.len())
3490 {
3491 return Err(ParserError {
3492 message: "Array rows must have equal length".to_string(),
3493 position: Some(self.byte_offset(self.position.saturating_sub(1))),
3494 });
3495 }
3496
3497 self.finish_array(rows, height, contains_volatile)
3498 }
3499}
3500
3501impl TryFrom<&str> for Parser {
3502 type Error = TokenizerError;
3503
3504 fn try_from(formula: &str) -> Result<Self, Self::Error> {
3505 Self::new(formula)
3506 }
3507}
3508
3509impl TryFrom<String> for Parser {
3510 type Error = TokenizerError;
3511
3512 fn try_from(formula: String) -> Result<Self, Self::Error> {
3513 Self::new(formula)
3514 }
3515}
3516
3517impl From<&TokenStream> for Parser {
3518 fn from(stream: &TokenStream) -> Self {
3519 Self::from_token_stream(stream)
3520 }
3521}
3522
3523impl FromStr for ASTNode {
3524 type Err = ParserError;
3525
3526 fn from_str(formula: &str) -> Result<Self, Self::Err> {
3527 parse(formula)
3528 }
3529}
3530
3531impl TryFrom<&str> for ASTNode {
3532 type Error = ParserError;
3533
3534 fn try_from(formula: &str) -> Result<Self, Self::Error> {
3535 parse(formula)
3536 }
3537}
3538
3539impl TryFrom<String> for ASTNode {
3540 type Error = ParserError;
3541
3542 fn try_from(formula: String) -> Result<Self, Self::Error> {
3543 parse(formula)
3544 }
3545}
3546
3547impl Parser {
3548 pub fn builder() -> ParserBuilder {
3549 ParserBuilder::default()
3550 }
3551}
3552
3553#[derive(Default)]
3554pub struct ParserBuilder {
3555 dialect: FormulaDialect,
3556 volatility_classifier: Option<VolatilityClassifierArc>,
3557 limits: ParserLimits,
3558}
3559
3560impl ParserBuilder {
3561 pub fn limits(mut self, limits: ParserLimits) -> Self {
3562 self.limits = limits;
3563 self
3564 }
3565 pub fn dialect(mut self, dialect: FormulaDialect) -> Self {
3566 self.dialect = dialect;
3567 self
3568 }
3569
3570 pub fn with_volatility_classifier<F>(mut self, f: F) -> Self
3571 where
3572 F: Fn(&str) -> bool + Send + Sync + 'static,
3573 {
3574 self.volatility_classifier = Some(Arc::new(f));
3575 self
3576 }
3577
3578 pub fn build<T: AsRef<str>>(self, formula: T) -> Result<Parser, TokenizerError> {
3579 let mut parser = Parser::new_with_limits(formula.as_ref(), self.dialect, self.limits)?;
3580 if let Some(classifier) = self.volatility_classifier {
3581 parser = parser.with_volatility_classifier(move |name| classifier(name));
3582 }
3583 Ok(parser)
3584 }
3585
3586 pub fn parse<T: AsRef<str>>(self, formula: T) -> Result<ASTNode, ParserError> {
3587 let mut parser = self.build(formula)?;
3588 parser.parse()
3589 }
3590}
3591
3592pub fn normalise_reference(reference: &str) -> Result<String, ParsingError> {
3594 let ref_type = ReferenceType::from_string(reference)?;
3595 Ok(ref_type.to_string())
3596}
3597
3598pub fn parse<T: AsRef<str>>(formula: T) -> Result<ASTNode, ParserError> {
3599 parse_with_dialect(formula, FormulaDialect::Excel)
3600}
3601
3602pub fn parse_with_dialect<T: AsRef<str>>(
3603 formula: T,
3604 dialect: FormulaDialect,
3605) -> Result<ASTNode, ParserError> {
3606 let mut parser = Parser::new_with_dialect(formula, dialect)?;
3607 parser.parse()
3608}
3609
3610pub fn parse_with_volatility_classifier<T, F>(
3613 formula: T,
3614 classifier: F,
3615) -> Result<ASTNode, ParserError>
3616where
3617 T: AsRef<str>,
3618 F: Fn(&str) -> bool + Send + Sync + 'static,
3619{
3620 parse_with_dialect_and_volatility_classifier(formula, FormulaDialect::Excel, classifier)
3621}
3622
3623pub fn parse_with_dialect_and_volatility_classifier<T, F>(
3624 formula: T,
3625 dialect: FormulaDialect,
3626 classifier: F,
3627) -> Result<ASTNode, ParserError>
3628where
3629 T: AsRef<str>,
3630 F: Fn(&str) -> bool + Send + Sync + 'static,
3631{
3632 let mut parser =
3633 Parser::new_with_dialect(formula, dialect)?.with_volatility_classifier(classifier);
3634 parser.parse()
3635}
3636
3637pub struct BatchParser {
3642 include_whitespace: bool,
3643 volatility_classifier: Option<VolatilityClassifierArc>,
3644 token_cache: crate::token_cache::TokenCache,
3645 limits: ParserLimits,
3646 dialect: FormulaDialect,
3647}
3648
3649impl BatchParser {
3650 pub fn builder() -> BatchParserBuilder {
3651 BatchParserBuilder::default()
3652 }
3653
3654 pub fn parse(&mut self, formula: &str) -> Result<ASTNode, ParserError> {
3656 self.limits.check_source(formula)?;
3657 let (source, spans) = if let Some(pair) = self.token_cache.get(formula) {
3658 pair
3659 } else {
3660 let mut spans =
3661 crate::tokenizer::tokenize_spans_with_limits(formula, self.dialect, self.limits)?;
3662 let source: Arc<str> = Arc::from(formula);
3663 if !self.include_whitespace {
3664 spans.retain(|t| t.token_type != TokenType::Whitespace);
3665 }
3666
3667 let spans: Arc<[TokenSpan]> = Arc::from(spans.into_boxed_slice());
3668 self.token_cache
3669 .insert(Arc::clone(&source), Arc::clone(&spans));
3670 (source, spans)
3671 };
3672
3673 let mut parser = Parser::from_source_and_tokens(source, spans, self.dialect);
3674 parser.limits = self.limits;
3675 if let Some(classifier) = self.volatility_classifier.clone() {
3676 parser = parser.with_volatility_classifier(move |name| classifier(name));
3677 }
3678 parser.parse()
3679 }
3680}
3681
3682pub struct BatchParserBuilder {
3683 limits: ParserLimits,
3684 cache_entries: usize,
3685 cache_bytes: usize,
3686 include_whitespace: bool,
3687 volatility_classifier: Option<VolatilityClassifierArc>,
3688 dialect: FormulaDialect,
3689}
3690
3691impl Default for BatchParserBuilder {
3692 fn default() -> Self {
3693 Self {
3694 include_whitespace: false,
3695 volatility_classifier: None,
3696 dialect: FormulaDialect::default(),
3697 limits: ParserLimits::default(),
3698 cache_entries: 16_384,
3699 cache_bytes: 8 * 1024 * 1024,
3700 }
3701 }
3702}
3703impl BatchParserBuilder {
3704 pub fn limits(mut self, limits: ParserLimits) -> Self {
3705 self.limits = limits;
3706 self
3707 }
3708 pub fn cache_capacity(mut self, entries: usize, bytes: usize) -> Self {
3710 self.cache_entries = entries;
3711 self.cache_bytes = bytes;
3712 self
3713 }
3714 pub fn include_whitespace(mut self, include: bool) -> Self {
3715 self.include_whitespace = include;
3716 self
3717 }
3718
3719 pub fn with_volatility_classifier<F>(mut self, f: F) -> Self
3720 where
3721 F: Fn(&str) -> bool + Send + Sync + 'static,
3722 {
3723 self.volatility_classifier = Some(Arc::new(f));
3724 self
3725 }
3726
3727 pub fn dialect(mut self, dialect: FormulaDialect) -> Self {
3728 self.dialect = dialect;
3729 self
3730 }
3731
3732 pub fn build(self) -> BatchParser {
3733 BatchParser {
3734 include_whitespace: self.include_whitespace,
3735 volatility_classifier: self.volatility_classifier,
3736 token_cache: crate::token_cache::TokenCache::new(self.cache_entries, self.cache_bytes),
3737 limits: self.limits,
3738 dialect: self.dialect,
3739 }
3740 }
3741}