Skip to main content

sheetkit_core/formula/
parser.rs

1//! nom-based parser for Excel formula strings.
2//!
3//! The formula string does NOT include the leading `=` sign.
4//!
5//! Operator precedence (lowest to highest):
6//! 1. Comparison (=, <>, <, <=, >, >=)
7//! 2. Concatenation (&)
8//! 3. Addition / Subtraction (+, -)
9//! 4. Multiplication / Division (*, /)
10//! 5. Exponentiation (^)
11//! 6. Unary (+, -, %)
12//! 7. Primary (literals, references, functions, parens)
13
14use nom::{
15    branch::alt,
16    bytes::complete::{tag, tag_no_case, take_while1},
17    character::complete::{alpha1, char, multispace0},
18    combinator::{map, opt, recognize, value},
19    multi::{many0, separated_list0},
20    sequence::{delimited, pair, preceded, terminated},
21    IResult,
22};
23
24use super::ast::{BinaryOperator, CellReference, Expr, UnaryOperator};
25use crate::error::{Error, Result};
26
27/// Parse an Excel formula string into an AST expression.
28///
29/// The input should NOT include the leading `=` sign.
30///
31/// # Errors
32///
33/// Returns an error if the formula string cannot be parsed.
34pub fn parse_formula(input: &str) -> Result<Expr> {
35    let trimmed = input.trim();
36    if trimmed.is_empty() {
37        return Err(Error::Internal("empty formula".to_string()));
38    }
39    match parse_expr(trimmed) {
40        Ok(("", expr)) => Ok(expr),
41        Ok((remaining, _)) => Err(Error::Internal(format!(
42            "unexpected trailing input: {remaining}"
43        ))),
44        Err(e) => Err(Error::Internal(format!("formula parse error: {e}"))),
45    }
46}
47
48/// Wraps a parser to consume optional surrounding whitespace.
49fn ws<'a, F, O>(inner: F) -> impl FnMut(&'a str) -> IResult<&'a str, O>
50where
51    F: FnMut(&'a str) -> IResult<&'a str, O>,
52{
53    delimited(multispace0, inner, multispace0)
54}
55
56/// Top-level expression: comparison operators (lowest precedence).
57fn parse_expr(input: &str) -> IResult<&str, Expr> {
58    let (input, left) = parse_concat_expr(input)?;
59    let (input, rest) = many0(pair(ws(parse_comparison_op), parse_concat_expr))(input)?;
60    Ok((input, fold_binary(left, rest)))
61}
62
63fn parse_comparison_op(input: &str) -> IResult<&str, BinaryOperator> {
64    alt((
65        value(BinaryOperator::Le, tag("<=")),
66        value(BinaryOperator::Ge, tag(">=")),
67        value(BinaryOperator::Ne, tag("<>")),
68        value(BinaryOperator::Lt, tag("<")),
69        value(BinaryOperator::Gt, tag(">")),
70        value(BinaryOperator::Eq, tag("=")),
71    ))(input)
72}
73
74/// Concatenation (&).
75fn parse_concat_expr(input: &str) -> IResult<&str, Expr> {
76    let (input, left) = parse_additive(input)?;
77    let (input, rest) = many0(pair(
78        ws(value(BinaryOperator::Concat, tag("&"))),
79        parse_additive,
80    ))(input)?;
81    Ok((input, fold_binary(left, rest)))
82}
83
84/// Addition and subtraction.
85fn parse_additive(input: &str) -> IResult<&str, Expr> {
86    let (input, left) = parse_multiplicative(input)?;
87    let (input, rest) = many0(pair(ws(parse_add_sub_op), parse_multiplicative))(input)?;
88    Ok((input, fold_binary(left, rest)))
89}
90
91fn parse_add_sub_op(input: &str) -> IResult<&str, BinaryOperator> {
92    alt((
93        value(BinaryOperator::Add, tag("+")),
94        value(BinaryOperator::Sub, tag("-")),
95    ))(input)
96}
97
98/// Multiplication and division.
99fn parse_multiplicative(input: &str) -> IResult<&str, Expr> {
100    let (input, left) = parse_power(input)?;
101    let (input, rest) = many0(pair(ws(parse_mul_div_op), parse_power))(input)?;
102    Ok((input, fold_binary(left, rest)))
103}
104
105fn parse_mul_div_op(input: &str) -> IResult<&str, BinaryOperator> {
106    alt((
107        value(BinaryOperator::Mul, tag("*")),
108        value(BinaryOperator::Div, tag("/")),
109    ))(input)
110}
111
112/// Exponentiation (^).
113fn parse_power(input: &str) -> IResult<&str, Expr> {
114    let (input, left) = parse_unary(input)?;
115    let (input, rest) = many0(pair(ws(value(BinaryOperator::Pow, tag("^"))), parse_unary))(input)?;
116    Ok((input, fold_binary(left, rest)))
117}
118
119/// Unary +/- prefix and % postfix.
120fn parse_unary(input: &str) -> IResult<&str, Expr> {
121    let input = input.trim_start();
122    // Try unary minus
123    if let Ok((rest, _)) = tag::<&str, &str, nom::error::Error<&str>>("-")(input) {
124        let (rest, operand) = parse_unary(rest)?;
125        return Ok((
126            rest,
127            Expr::UnaryOp {
128                op: UnaryOperator::Neg,
129                operand: Box::new(operand),
130            },
131        ));
132    }
133    // Try unary plus
134    if let Ok((rest, _)) = tag::<&str, &str, nom::error::Error<&str>>("+")(input) {
135        let (rest, operand) = parse_unary(rest)?;
136        return Ok((
137            rest,
138            Expr::UnaryOp {
139                op: UnaryOperator::Pos,
140                operand: Box::new(operand),
141            },
142        ));
143    }
144    // Otherwise parse primary and check for postfix %
145    let (input, expr) = parse_primary(input)?;
146    let (input, pcts) = many0(ws(value(UnaryOperator::Percent, tag("%"))))(input)?;
147    let result = pcts.into_iter().fold(expr, |acc, op| Expr::UnaryOp {
148        op,
149        operand: Box::new(acc),
150    });
151    Ok((input, result))
152}
153
154/// Primary expressions: literals, references, function calls, parenthesized expressions.
155fn parse_primary(input: &str) -> IResult<&str, Expr> {
156    let input = input.trim_start();
157    alt((
158        parse_paren_expr,
159        parse_string_literal,
160        parse_error_literal,
161        parse_bool_literal,
162        parse_function_call,
163        parse_cell_ref_or_range,
164        parse_number_literal,
165    ))(input)
166}
167
168/// Parse a numeric literal (integer or decimal).
169fn parse_number_literal(input: &str) -> IResult<&str, Expr> {
170    let (input, num_str) = recognize(pair(
171        take_while1(|c: char| c.is_ascii_digit()),
172        opt(pair(tag("."), take_while1(|c: char| c.is_ascii_digit()))),
173    ))(input)?;
174    let n: f64 = num_str.parse().map_err(|_| {
175        nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Float))
176    })?;
177    Ok((input, Expr::Number(n)))
178}
179
180/// Parse a string literal with `"` delimiters. `""` is an escaped quote (Excel convention).
181fn parse_string_literal(input: &str) -> IResult<&str, Expr> {
182    let (input, _) = tag("\"")(input)?;
183    let mut result = String::new();
184    let mut remaining = input;
185    loop {
186        if remaining.is_empty() {
187            return Err(nom::Err::Error(nom::error::Error::new(
188                remaining,
189                nom::error::ErrorKind::Tag,
190            )));
191        }
192        if remaining.starts_with("\"\"") {
193            result.push('"');
194            remaining = &remaining[2..];
195        } else if remaining.starts_with('"') {
196            remaining = &remaining[1..];
197            break;
198        } else {
199            let c = remaining.chars().next().unwrap();
200            result.push(c);
201            remaining = &remaining[c.len_utf8()..];
202        }
203    }
204    Ok((remaining, Expr::String(result)))
205}
206
207/// Parse a boolean literal: TRUE or FALSE (case-insensitive).
208fn parse_bool_literal(input: &str) -> IResult<&str, Expr> {
209    let (input, val) = alt((
210        value(
211            true,
212            terminated(tag_no_case("TRUE"), not_alnum_or_underscore),
213        ),
214        value(
215            false,
216            terminated(tag_no_case("FALSE"), not_alnum_or_underscore),
217        ),
218    ))(input)?;
219    Ok((input, Expr::Bool(val)))
220}
221
222/// Succeeds if the next character is NOT alphanumeric or underscore, or if at end of input.
223/// This prevents "TRUE1" from being parsed as Bool(true) with leftover "1".
224fn not_alnum_or_underscore(input: &str) -> IResult<&str, ()> {
225    if input.is_empty() {
226        return Ok((input, ()));
227    }
228    let c = input.chars().next().unwrap();
229    if c.is_alphanumeric() || c == '_' {
230        Err(nom::Err::Error(nom::error::Error::new(
231            input,
232            nom::error::ErrorKind::Alpha,
233        )))
234    } else {
235        Ok((input, ()))
236    }
237}
238
239/// Parse an Excel error literal (#DIV/0!, #VALUE!, etc.).
240fn parse_error_literal(input: &str) -> IResult<&str, Expr> {
241    let (input, err) = alt((
242        tag("#DIV/0!"),
243        tag("#VALUE!"),
244        tag("#REF!"),
245        tag("#NAME?"),
246        tag("#NUM!"),
247        tag("#NULL!"),
248        tag("#N/A"),
249    ))(input)?;
250    Ok((input, Expr::Error(err.to_string())))
251}
252
253/// Parse a cell reference (possibly with a sheet prefix) or a range (A1:B10).
254fn parse_cell_ref_or_range(input: &str) -> IResult<&str, Expr> {
255    let (input, first) = parse_single_cell_ref(input)?;
256    // Check for range operator ':'
257    if let Ok((input, _)) = tag::<&str, &str, nom::error::Error<&str>>(":")(input) {
258        let (input, second) = parse_single_cell_ref(input)?;
259        Ok((
260            input,
261            Expr::Range {
262                start: first,
263                end: second,
264            },
265        ))
266    } else {
267        Ok((input, Expr::CellRef(first)))
268    }
269}
270
271/// Parse a single cell reference, optionally prefixed with a sheet name.
272fn parse_single_cell_ref(input: &str) -> IResult<&str, CellReference> {
273    // Try to parse sheet prefix first
274    let (input, sheet) = opt(parse_sheet_prefix)(input)?;
275    // Parse optional $ for abs col
276    let (input, abs_col) = map(opt(tag("$")), |o| o.is_some())(input)?;
277    // Parse column letters
278    let (input, col) = alpha1(input)?;
279    // Make sure column letters are A-Z only
280    let col_upper = col.to_uppercase();
281    if !col_upper.chars().all(|c| c.is_ascii_uppercase()) {
282        return Err(nom::Err::Error(nom::error::Error::new(
283            input,
284            nom::error::ErrorKind::Alpha,
285        )));
286    }
287    // Parse optional $ for abs row
288    let (input, abs_row) = map(opt(tag("$")), |o| o.is_some())(input)?;
289    // Parse row digits
290    let (input, row_str) = take_while1(|c: char| c.is_ascii_digit())(input)?;
291    let row: u32 = row_str.parse().map_err(|_| {
292        nom::Err::Error(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
293    })?;
294    Ok((
295        input,
296        CellReference {
297            col: col_upper,
298            row,
299            abs_col,
300            abs_row,
301            sheet,
302        },
303    ))
304}
305
306/// Parse a sheet prefix like `Sheet1!` or `'Sheet Name'!`.
307fn parse_sheet_prefix(input: &str) -> IResult<&str, String> {
308    alt((parse_quoted_sheet_prefix, parse_unquoted_sheet_prefix))(input)
309}
310
311/// Parse an unquoted sheet prefix: `SheetName!`
312fn parse_unquoted_sheet_prefix(input: &str) -> IResult<&str, String> {
313    let (input, name) = terminated(
314        recognize(pair(
315            take_while1(|c: char| c.is_alphanumeric() || c == '_'),
316            many0(take_while1(|c: char| c.is_alphanumeric() || c == '_')),
317        )),
318        tag("!"),
319    )(input)?;
320    Ok((input, name.to_string()))
321}
322
323/// Parse a quoted sheet prefix: `'Sheet Name'!`
324fn parse_quoted_sheet_prefix(input: &str) -> IResult<&str, String> {
325    let (input, _) = tag("'")(input)?;
326    let mut result = String::new();
327    let mut remaining = input;
328    loop {
329        if remaining.is_empty() {
330            return Err(nom::Err::Error(nom::error::Error::new(
331                remaining,
332                nom::error::ErrorKind::Tag,
333            )));
334        }
335        if remaining.starts_with("''") {
336            result.push('\'');
337            remaining = &remaining[2..];
338        } else if remaining.starts_with('\'') {
339            remaining = &remaining[1..];
340            break;
341        } else {
342            let c = remaining.chars().next().unwrap();
343            result.push(c);
344            remaining = &remaining[c.len_utf8()..];
345        }
346    }
347    let (remaining, _) = tag("!")(remaining)?;
348    Ok((remaining, result))
349}
350
351/// Parse a function call: `FUNCNAME(arg1, arg2, ...)`.
352fn parse_function_call(input: &str) -> IResult<&str, Expr> {
353    // Function name: letters, digits, underscores, dots (e.g., _xlfn.CONCAT)
354    let (input, name) = recognize(pair(
355        alt((alpha1, tag("_"))),
356        many0(alt((
357            take_while1(|c: char| c.is_alphanumeric() || c == '_'),
358            tag("."),
359        ))),
360    ))(input)?;
361    // Must be followed by '('
362    let (input, _) = preceded(multispace0, char('('))(input)?;
363    let (input, _) = multispace0(input)?;
364    // Parse arguments separated by commas
365    let (input, args) = separated_list0(ws(char(',')), parse_expr)(input)?;
366    let (input, _) = preceded(multispace0, char(')'))(input)?;
367    Ok((
368        input,
369        Expr::Function {
370            name: name.to_uppercase(),
371            args,
372        },
373    ))
374}
375
376/// Parse a parenthesized expression.
377fn parse_paren_expr(input: &str) -> IResult<&str, Expr> {
378    let (input, _) = char('(')(input)?;
379    let (input, _) = multispace0(input)?;
380    let (input, expr) = parse_expr(input)?;
381    let (input, _) = preceded(multispace0, char(')'))(input)?;
382    Ok((input, Expr::Paren(Box::new(expr))))
383}
384
385/// Fold a left-associative chain of binary operations into a single AST node.
386fn fold_binary(first: Expr, rest: Vec<(BinaryOperator, Expr)>) -> Expr {
387    rest.into_iter()
388        .fold(first, |left, (op, right)| Expr::BinaryOp {
389            op,
390            left: Box::new(left),
391            right: Box::new(right),
392        })
393}
394
395#[cfg(test)]
396mod tests {
397    use super::*;
398    use crate::formula::ast::{BinaryOperator, CellReference, Expr, UnaryOperator};
399
400    #[test]
401    fn test_parse_integer() {
402        let result = parse_formula("42").unwrap();
403        assert_eq!(result, Expr::Number(42.0));
404    }
405
406    #[test]
407    fn test_parse_decimal() {
408        let result = parse_formula("3.14").unwrap();
409        assert_eq!(result, Expr::Number(3.14));
410    }
411
412    #[test]
413    fn test_parse_negative_number() {
414        let result = parse_formula("-5").unwrap();
415        assert_eq!(
416            result,
417            Expr::UnaryOp {
418                op: UnaryOperator::Neg,
419                operand: Box::new(Expr::Number(5.0)),
420            }
421        );
422    }
423
424    #[test]
425    fn test_parse_string() {
426        let result = parse_formula("\"hello\"").unwrap();
427        assert_eq!(result, Expr::String("hello".to_string()));
428    }
429
430    #[test]
431    fn test_parse_empty_string() {
432        let result = parse_formula("\"\"").unwrap();
433        assert_eq!(result, Expr::String(String::new()));
434    }
435
436    #[test]
437    fn test_parse_true() {
438        let result = parse_formula("TRUE").unwrap();
439        assert_eq!(result, Expr::Bool(true));
440    }
441
442    #[test]
443    fn test_parse_false() {
444        let result = parse_formula("FALSE").unwrap();
445        assert_eq!(result, Expr::Bool(false));
446    }
447
448    #[test]
449    fn test_parse_error_div0() {
450        let result = parse_formula("#DIV/0!").unwrap();
451        assert_eq!(result, Expr::Error("#DIV/0!".to_string()));
452    }
453
454    #[test]
455    fn test_parse_error_na() {
456        let result = parse_formula("#N/A").unwrap();
457        assert_eq!(result, Expr::Error("#N/A".to_string()));
458    }
459
460    #[test]
461    fn test_parse_cell_ref() {
462        let result = parse_formula("A1").unwrap();
463        assert_eq!(
464            result,
465            Expr::CellRef(CellReference {
466                col: "A".to_string(),
467                row: 1,
468                abs_col: false,
469                abs_row: false,
470                sheet: None,
471            })
472        );
473    }
474
475    #[test]
476    fn test_parse_abs_cell_ref() {
477        let result = parse_formula("$A$1").unwrap();
478        assert_eq!(
479            result,
480            Expr::CellRef(CellReference {
481                col: "A".to_string(),
482                row: 1,
483                abs_col: true,
484                abs_row: true,
485                sheet: None,
486            })
487        );
488    }
489
490    #[test]
491    fn test_parse_mixed_cell_ref() {
492        let result = parse_formula("$A1").unwrap();
493        assert_eq!(
494            result,
495            Expr::CellRef(CellReference {
496                col: "A".to_string(),
497                row: 1,
498                abs_col: true,
499                abs_row: false,
500                sheet: None,
501            })
502        );
503    }
504
505    #[test]
506    fn test_parse_range() {
507        let result = parse_formula("A1:B10").unwrap();
508        assert_eq!(
509            result,
510            Expr::Range {
511                start: CellReference {
512                    col: "A".to_string(),
513                    row: 1,
514                    abs_col: false,
515                    abs_row: false,
516                    sheet: None,
517                },
518                end: CellReference {
519                    col: "B".to_string(),
520                    row: 10,
521                    abs_col: false,
522                    abs_row: false,
523                    sheet: None,
524                },
525            }
526        );
527    }
528
529    #[test]
530    fn test_parse_addition() {
531        let result = parse_formula("1+2").unwrap();
532        assert_eq!(
533            result,
534            Expr::BinaryOp {
535                op: BinaryOperator::Add,
536                left: Box::new(Expr::Number(1.0)),
537                right: Box::new(Expr::Number(2.0)),
538            }
539        );
540    }
541
542    #[test]
543    fn test_parse_subtraction() {
544        let result = parse_formula("5-3").unwrap();
545        assert_eq!(
546            result,
547            Expr::BinaryOp {
548                op: BinaryOperator::Sub,
549                left: Box::new(Expr::Number(5.0)),
550                right: Box::new(Expr::Number(3.0)),
551            }
552        );
553    }
554
555    #[test]
556    fn test_parse_multiplication() {
557        let result = parse_formula("2*3").unwrap();
558        assert_eq!(
559            result,
560            Expr::BinaryOp {
561                op: BinaryOperator::Mul,
562                left: Box::new(Expr::Number(2.0)),
563                right: Box::new(Expr::Number(3.0)),
564            }
565        );
566    }
567
568    #[test]
569    fn test_parse_division() {
570        let result = parse_formula("10/2").unwrap();
571        assert_eq!(
572            result,
573            Expr::BinaryOp {
574                op: BinaryOperator::Div,
575                left: Box::new(Expr::Number(10.0)),
576                right: Box::new(Expr::Number(2.0)),
577            }
578        );
579    }
580
581    #[test]
582    fn test_precedence_mul_over_add() {
583        // "1+2*3" should parse as Add(1, Mul(2, 3))
584        let result = parse_formula("1+2*3").unwrap();
585        assert_eq!(
586            result,
587            Expr::BinaryOp {
588                op: BinaryOperator::Add,
589                left: Box::new(Expr::Number(1.0)),
590                right: Box::new(Expr::BinaryOp {
591                    op: BinaryOperator::Mul,
592                    left: Box::new(Expr::Number(2.0)),
593                    right: Box::new(Expr::Number(3.0)),
594                }),
595            }
596        );
597    }
598
599    #[test]
600    fn test_precedence_parens() {
601        // "(1+2)*3" should parse as Mul(Paren(Add(1, 2)), 3)
602        let result = parse_formula("(1+2)*3").unwrap();
603        assert_eq!(
604            result,
605            Expr::BinaryOp {
606                op: BinaryOperator::Mul,
607                left: Box::new(Expr::Paren(Box::new(Expr::BinaryOp {
608                    op: BinaryOperator::Add,
609                    left: Box::new(Expr::Number(1.0)),
610                    right: Box::new(Expr::Number(2.0)),
611                }))),
612                right: Box::new(Expr::Number(3.0)),
613            }
614        );
615    }
616
617    #[test]
618    fn test_parse_function_no_args() {
619        let result = parse_formula("NOW()").unwrap();
620        assert_eq!(
621            result,
622            Expr::Function {
623                name: "NOW".to_string(),
624                args: vec![],
625            }
626        );
627    }
628
629    #[test]
630    fn test_parse_function_one_arg() {
631        let result = parse_formula("ABS(-5)").unwrap();
632        assert_eq!(
633            result,
634            Expr::Function {
635                name: "ABS".to_string(),
636                args: vec![Expr::UnaryOp {
637                    op: UnaryOperator::Neg,
638                    operand: Box::new(Expr::Number(5.0)),
639                }],
640            }
641        );
642    }
643
644    #[test]
645    fn test_parse_function_multi_args() {
646        let result = parse_formula("SUM(1,2,3)").unwrap();
647        assert_eq!(
648            result,
649            Expr::Function {
650                name: "SUM".to_string(),
651                args: vec![Expr::Number(1.0), Expr::Number(2.0), Expr::Number(3.0),],
652            }
653        );
654    }
655
656    #[test]
657    fn test_parse_nested_function() {
658        let result = parse_formula("SUM(A1:A10,MAX(B1:B10))").unwrap();
659        assert_eq!(
660            result,
661            Expr::Function {
662                name: "SUM".to_string(),
663                args: vec![
664                    Expr::Range {
665                        start: CellReference {
666                            col: "A".to_string(),
667                            row: 1,
668                            abs_col: false,
669                            abs_row: false,
670                            sheet: None,
671                        },
672                        end: CellReference {
673                            col: "A".to_string(),
674                            row: 10,
675                            abs_col: false,
676                            abs_row: false,
677                            sheet: None,
678                        },
679                    },
680                    Expr::Function {
681                        name: "MAX".to_string(),
682                        args: vec![Expr::Range {
683                            start: CellReference {
684                                col: "B".to_string(),
685                                row: 1,
686                                abs_col: false,
687                                abs_row: false,
688                                sheet: None,
689                            },
690                            end: CellReference {
691                                col: "B".to_string(),
692                                row: 10,
693                                abs_col: false,
694                                abs_row: false,
695                                sheet: None,
696                            },
697                        }],
698                    },
699                ],
700            }
701        );
702    }
703
704    #[test]
705    fn test_parse_equal() {
706        let result = parse_formula("A1=B1").unwrap();
707        assert_eq!(
708            result,
709            Expr::BinaryOp {
710                op: BinaryOperator::Eq,
711                left: Box::new(Expr::CellRef(CellReference {
712                    col: "A".to_string(),
713                    row: 1,
714                    abs_col: false,
715                    abs_row: false,
716                    sheet: None,
717                })),
718                right: Box::new(Expr::CellRef(CellReference {
719                    col: "B".to_string(),
720                    row: 1,
721                    abs_col: false,
722                    abs_row: false,
723                    sheet: None,
724                })),
725            }
726        );
727    }
728
729    #[test]
730    fn test_parse_not_equal() {
731        let result = parse_formula("A1<>B1").unwrap();
732        assert_eq!(
733            result,
734            Expr::BinaryOp {
735                op: BinaryOperator::Ne,
736                left: Box::new(Expr::CellRef(CellReference {
737                    col: "A".to_string(),
738                    row: 1,
739                    abs_col: false,
740                    abs_row: false,
741                    sheet: None,
742                })),
743                right: Box::new(Expr::CellRef(CellReference {
744                    col: "B".to_string(),
745                    row: 1,
746                    abs_col: false,
747                    abs_row: false,
748                    sheet: None,
749                })),
750            }
751        );
752    }
753
754    #[test]
755    fn test_parse_less_than() {
756        let result = parse_formula("A1<B1").unwrap();
757        assert_eq!(
758            result,
759            Expr::BinaryOp {
760                op: BinaryOperator::Lt,
761                left: Box::new(Expr::CellRef(CellReference {
762                    col: "A".to_string(),
763                    row: 1,
764                    abs_col: false,
765                    abs_row: false,
766                    sheet: None,
767                })),
768                right: Box::new(Expr::CellRef(CellReference {
769                    col: "B".to_string(),
770                    row: 1,
771                    abs_col: false,
772                    abs_row: false,
773                    sheet: None,
774                })),
775            }
776        );
777    }
778
779    #[test]
780    fn test_parse_concat() {
781        let result = parse_formula("A1&B1").unwrap();
782        assert_eq!(
783            result,
784            Expr::BinaryOp {
785                op: BinaryOperator::Concat,
786                left: Box::new(Expr::CellRef(CellReference {
787                    col: "A".to_string(),
788                    row: 1,
789                    abs_col: false,
790                    abs_row: false,
791                    sheet: None,
792                })),
793                right: Box::new(Expr::CellRef(CellReference {
794                    col: "B".to_string(),
795                    row: 1,
796                    abs_col: false,
797                    abs_row: false,
798                    sheet: None,
799                })),
800            }
801        );
802    }
803
804    #[test]
805    fn test_parse_complex_formula() {
806        // "SUM(A1:A10)+AVERAGE(B1:B10)*2"
807        let result = parse_formula("SUM(A1:A10)+AVERAGE(B1:B10)*2").unwrap();
808        assert_eq!(
809            result,
810            Expr::BinaryOp {
811                op: BinaryOperator::Add,
812                left: Box::new(Expr::Function {
813                    name: "SUM".to_string(),
814                    args: vec![Expr::Range {
815                        start: CellReference {
816                            col: "A".to_string(),
817                            row: 1,
818                            abs_col: false,
819                            abs_row: false,
820                            sheet: None,
821                        },
822                        end: CellReference {
823                            col: "A".to_string(),
824                            row: 10,
825                            abs_col: false,
826                            abs_row: false,
827                            sheet: None,
828                        },
829                    }],
830                }),
831                right: Box::new(Expr::BinaryOp {
832                    op: BinaryOperator::Mul,
833                    left: Box::new(Expr::Function {
834                        name: "AVERAGE".to_string(),
835                        args: vec![Expr::Range {
836                            start: CellReference {
837                                col: "B".to_string(),
838                                row: 1,
839                                abs_col: false,
840                                abs_row: false,
841                                sheet: None,
842                            },
843                            end: CellReference {
844                                col: "B".to_string(),
845                                row: 10,
846                                abs_col: false,
847                                abs_row: false,
848                                sheet: None,
849                            },
850                        }],
851                    }),
852                    right: Box::new(Expr::Number(2.0)),
853                }),
854            }
855        );
856    }
857
858    #[test]
859    fn test_parse_sheet_ref() {
860        let result = parse_formula("Sheet1!A1").unwrap();
861        assert_eq!(
862            result,
863            Expr::CellRef(CellReference {
864                col: "A".to_string(),
865                row: 1,
866                abs_col: false,
867                abs_row: false,
868                sheet: Some("Sheet1".to_string()),
869            })
870        );
871    }
872}