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