Skip to main content

truecalc_core/parser/
ast.rs

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