Skip to main content

truecalc_core/parser/
ast.rs

1use super::refs::Ref;
2
3/// Byte range of a node within the original formula string.
4#[derive(Debug, Clone, PartialEq)]
5pub struct Span {
6    pub offset: usize, // byte offset from start of formula
7    pub length: usize,
8}
9
10impl Span {
11    pub fn new(offset: usize, length: usize) -> Self {
12        Self { offset, length }
13    }
14}
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum UnaryOp {
18    Neg,     // -x
19    Percent, // x% → x/100
20}
21
22#[derive(Debug, Clone, PartialEq)]
23pub enum BinaryOp {
24    Add, Sub, Mul, Div, Pow,
25    Concat,         // &
26    Eq, Ne, Lt, Gt, Le, Ge,
27}
28
29#[derive(Debug, Clone, PartialEq)]
30pub enum Expr {
31    Number(f64, Span),
32    Text(String, Span),
33    Bool(bool, Span),
34    Variable(String, Span),
35    /// Sheet-qualified reference: `Sheet1!A1`, `'Q2 Data'!A1:B2`.
36    /// Bare cell/range references (`A1`, `A1:D4`) and bare names remain
37    /// [`Expr::Variable`]; sheet-qualified forms always carry `sheet: Some(_)`.
38    Reference(Ref, Span),
39    UnaryOp {
40        op: UnaryOp,
41        operand: Box<Expr>,
42        span: Span,
43    },
44    BinaryOp {
45        op: BinaryOp,
46        left: Box<Expr>,
47        right: Box<Expr>,
48        span: Span,
49    },
50    FunctionCall {
51        name: String,   // always uppercased
52        args: Vec<Expr>,
53        span: Span,
54    },
55    Array(Vec<Expr>, Span),
56    /// Immediately-invoked function application: `expr(call_args)`.
57    /// Used for LAMBDA: `LAMBDA(x, x*2)(5)` → `Apply { func: LAMBDA(...), call_args: [5] }`.
58    Apply {
59        func: Box<Expr>,
60        call_args: Vec<Expr>,
61        span: Span,
62    },
63}
64
65impl Expr {
66    pub fn span(&self) -> &Span {
67        match self {
68            Expr::Number(_, s) | Expr::Text(_, s) | Expr::Bool(_, s) | Expr::Variable(_, s) | Expr::Reference(_, s) => s,
69            Expr::UnaryOp { span, .. }
70            | Expr::BinaryOp { span, .. }
71            | Expr::FunctionCall { span, .. }
72            | Expr::Apply { span, .. } => span,
73            Expr::Array(_, span) => span,
74        }
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn span_stores_offset_and_length() {
84        let s = Span::new(5, 10);
85        assert_eq!(s.offset, 5);
86        assert_eq!(s.length, 10);
87    }
88
89    #[test]
90    fn expr_number_span() {
91        let e = Expr::Number(1.0, Span::new(0, 3));
92        assert_eq!(e.span().offset, 0);
93        assert_eq!(e.span().length, 3);
94    }
95
96    #[test]
97    fn expr_text_span() {
98        let e = Expr::Text("hello".into(), Span::new(2, 7));
99        assert_eq!(e.span().offset, 2);
100    }
101
102    #[test]
103    fn expr_bool_span() {
104        let e = Expr::Bool(true, Span::new(1, 4));
105        assert_eq!(e.span().offset, 1);
106    }
107
108    #[test]
109    fn expr_function_call_span() {
110        let e = Expr::FunctionCall {
111            name: "SUM".into(),
112            args: vec![],
113            span: Span::new(0, 5),
114        };
115        assert_eq!(e.span().offset, 0);
116        assert_eq!(e.span().length, 5);
117    }
118
119    #[test]
120    fn unary_op_debug() {
121        assert_eq!(format!("{:?}", UnaryOp::Neg), "Neg");
122        assert_eq!(format!("{:?}", UnaryOp::Percent), "Percent");
123    }
124
125    #[test]
126    fn binary_op_debug() {
127        assert_eq!(format!("{:?}", BinaryOp::Add), "Add");
128        assert_eq!(format!("{:?}", BinaryOp::Eq), "Eq");
129    }
130
131    #[test]
132    fn expr_variable_span() {
133        let e = Expr::Variable("x".into(), Span::new(0, 1));
134        assert_eq!(e.span().offset, 0);
135        assert_eq!(e.span().length, 1);
136    }
137
138    #[test]
139    fn expr_unary_op_span() {
140        let e = Expr::UnaryOp {
141            op: UnaryOp::Neg,
142            operand: Box::new(Expr::Number(1.0, Span::new(1, 1))),
143            span: Span::new(0, 2),
144        };
145        assert_eq!(e.span().offset, 0);
146        assert_eq!(e.span().length, 2);
147    }
148
149    #[test]
150    fn expr_binary_op_span() {
151        let e = Expr::BinaryOp {
152            op: BinaryOp::Add,
153            left: Box::new(Expr::Number(1.0, Span::new(0, 1))),
154            right: Box::new(Expr::Number(2.0, Span::new(2, 1))),
155            span: Span::new(0, 3),
156        };
157        assert_eq!(e.span().offset, 0);
158        assert_eq!(e.span().length, 3);
159    }
160
161    #[test]
162    fn expr_apply_span() {
163        let e = Expr::Apply {
164            func: Box::new(Expr::Variable("f".into(), Span::new(0, 1))),
165            call_args: vec![],
166            span: Span::new(0, 4),
167        };
168        assert_eq!(e.span().offset, 0);
169        assert_eq!(e.span().length, 4);
170    }
171}