Skip to main content

ganit_core/parser/
mod.rs

1pub mod ast;
2pub mod tokens;
3
4pub use ast::Expr;
5use ast::{BinaryOp, Span, UnaryOp};
6use crate::types::ParseError;
7use nom::{IResult, character::complete::multispace0};
8use tokens::{bool_literal, identifier, number_literal, offset, string_literal};
9
10struct Parser<'a> {
11    full: &'a str,
12}
13
14impl<'a> Parser<'a> {
15    fn new(full: &'a str) -> Self {
16        Self { full }
17    }
18
19    fn span(&self, before: &str, after: &str) -> Span {
20        let start = offset(self.full, before);
21        let end = offset(self.full, after);
22        Span::new(start, end - start)
23    }
24
25    // ── primary ────────────────────────────────────────────────────────────
26
27    fn parse_primary(&self, i: &'a str) -> IResult<&'a str, Expr> {
28        let i = multispace0(i)?.0;
29
30        // Number literal (must come before identifier to catch e.g. "1e3")
31        if let Ok((rest, n)) = number_literal(i) {
32            return Ok((rest, Expr::Number(n, self.span(i, rest))));
33        }
34
35        // String literal
36        if let Ok((rest, text)) = string_literal(i) {
37            return Ok((rest, Expr::Text(text, self.span(i, rest))));
38        }
39
40        // Parenthesised expression
41        if let Some(inner) = i.strip_prefix('(') {
42            let (rest, expr) = self.parse_comparison(inner)?;
43            let rest = multispace0(rest)?.0;
44            if let Some(after) = rest.strip_prefix(')') {
45                return Ok((after, expr));
46            }
47            return Err(nom::Err::Error(nom::error::Error::new(
48                rest,
49                nom::error::ErrorKind::Char,
50            )));
51        }
52
53        // Boolean (before identifier — uses word-boundary check in bool_literal)
54        if let Ok((rest, b)) = bool_literal(i) {
55            return Ok((rest, Expr::Bool(b, self.span(i, rest))));
56        }
57
58        // Identifier: variable or function call
59        if let Ok((rest, name)) = identifier(i) {
60            let rest_ws = multispace0(rest)?.0;
61            if let Some(args_input) = rest_ws.strip_prefix('(') {
62                // Function call
63                let (rest2, args) = self.parse_arg_list(args_input)?;
64                let rest2 = multispace0(rest2)?.0;
65                if let Some(after) = rest2.strip_prefix(')') {
66                    return Ok((after, Expr::FunctionCall {
67                        name: name.to_uppercase(),
68                        args,
69                        span: self.span(i, after),
70                    }));
71                }
72                return Err(nom::Err::Error(nom::error::Error::new(
73                    rest2,
74                    nom::error::ErrorKind::Char,
75                )));
76            }
77            return Ok((rest, Expr::Variable(name.to_string(), self.span(i, rest))));
78        }
79
80        Err(nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Alt)))
81    }
82
83    fn parse_arg_list(&self, i: &'a str) -> IResult<&'a str, Vec<Expr>> {
84        let mut args = Vec::new();
85        let mut rest = multispace0(i)?.0;
86
87        if rest.starts_with(')') {
88            return Ok((rest, args));
89        }
90
91        let (r, first) = self.parse_comparison(rest)?;
92        args.push(first);
93        rest = r;
94
95        loop {
96            rest = multispace0(rest)?.0;
97            if let Some(after_comma) = rest.strip_prefix(',') {
98                let (r, arg) = self.parse_comparison(after_comma)?;
99                args.push(arg);
100                rest = r;
101            } else {
102                break;
103            }
104        }
105
106        Ok((rest, args))
107    }
108
109    // ── postfix % ─────────────────────────────────────────────────────────
110
111    fn parse_postfix(&self, i: &'a str) -> IResult<&'a str, Expr> {
112        let (rest, expr) = self.parse_primary(i)?;
113        let rest_ws = multispace0(rest)?.0;
114        if let Some(after) = rest_ws.strip_prefix('%') {
115            return Ok((after, Expr::UnaryOp {
116                op: UnaryOp::Percent,
117                operand: Box::new(expr),
118                span: self.span(i, after),
119            }));
120        }
121        Ok((rest, expr))
122    }
123
124    // ── unary minus ───────────────────────────────────────────────────────
125
126    fn parse_unary(&self, i: &'a str) -> IResult<&'a str, Expr> {
127        let i_ws = multispace0(i)?.0;
128        if let Some(after_minus) = i_ws.strip_prefix('-') {
129            let (rest, operand) = self.parse_unary(after_minus)?;
130            return Ok((rest, Expr::UnaryOp {
131                op: UnaryOp::Neg,
132                operand: Box::new(operand),
133                span: self.span(i_ws, rest),
134            }));
135        }
136        self.parse_postfix(i)
137    }
138
139    // ── power ^ (right-associative) ───────────────────────────────────────
140
141    fn parse_power(&self, i: &'a str) -> IResult<&'a str, Expr> {
142        let (rest, left) = self.parse_unary(i)?;
143        let rest_ws = multispace0(rest)?.0;
144        if let Some(after_op) = rest_ws.strip_prefix('^') {
145            let (rest2, right) = self.parse_power(after_op)?;
146            return Ok((rest2, Expr::BinaryOp {
147                op: BinaryOp::Pow,
148                left: Box::new(left),
149                right: Box::new(right),
150                span: self.span(i, rest2),
151            }));
152        }
153        Ok((rest, left))
154    }
155
156    // ── multiplicative * / ────────────────────────────────────────────────
157
158    fn parse_multiplicative(&self, i: &'a str) -> IResult<&'a str, Expr> {
159        let (mut rest, mut left) = self.parse_power(i)?;
160        loop {
161            let ws = multispace0(rest)?.0;
162            let op = ws.strip_prefix('*').map(|after| (BinaryOp::Mul, after))
163                .or_else(|| ws.strip_prefix('/').map(|after| (BinaryOp::Div, after)));
164            match op {
165                None => break,
166                Some((op, after)) => {
167                    let (r, right) = self.parse_power(after)?;
168                    left = Expr::BinaryOp {
169                        op,
170                        span: self.span(i, r),
171                        left: Box::new(left),
172                        right: Box::new(right),
173                    };
174                    rest = r;
175                }
176            }
177        }
178        Ok((rest, left))
179    }
180
181    // ── additive + - ──────────────────────────────────────────────────────
182
183    fn parse_additive(&self, i: &'a str) -> IResult<&'a str, Expr> {
184        let (mut rest, mut left) = self.parse_multiplicative(i)?;
185        loop {
186            let ws = multispace0(rest)?.0;
187            let op = ws.strip_prefix('+').map(|after| (BinaryOp::Add, after))
188                .or_else(|| ws.strip_prefix('-').map(|after| (BinaryOp::Sub, after)));
189            match op {
190                None => break,
191                Some((op, after)) => {
192                    let (r, right) = self.parse_multiplicative(after)?;
193                    left = Expr::BinaryOp {
194                        op,
195                        span: self.span(i, r),
196                        left: Box::new(left),
197                        right: Box::new(right),
198                    };
199                    rest = r;
200                }
201            }
202        }
203        Ok((rest, left))
204    }
205
206    // ── concat & ─────────────────────────────────────────────────────────
207
208    fn parse_concat(&self, i: &'a str) -> IResult<&'a str, Expr> {
209        let (mut rest, mut left) = self.parse_additive(i)?;
210        loop {
211            let ws = multispace0(rest)?.0;
212            if let Some(after) = ws.strip_prefix('&') {
213                let (r, right) = self.parse_additive(after)?;
214                left = Expr::BinaryOp {
215                    op: BinaryOp::Concat,
216                    span: self.span(i, r),
217                    left: Box::new(left),
218                    right: Box::new(right),
219                };
220                rest = r;
221            } else {
222                break;
223            }
224        }
225        Ok((rest, left))
226    }
227
228    // ── comparison = <> < > <= >= ─────────────────────────────────────────
229
230    fn parse_comparison(&self, i: &'a str) -> IResult<&'a str, Expr> {
231        let (rest, left) = self.parse_concat(i)?;
232        let ws = multispace0(rest)?.0;
233
234        // Longest match first
235        let op_result: Option<(BinaryOp, &'a str)> = if let Some(after) = ws.strip_prefix("<>") {
236            Some((BinaryOp::Ne, after))
237        } else if let Some(after) = ws.strip_prefix("<=") {
238            Some((BinaryOp::Le, after))
239        } else if let Some(after) = ws.strip_prefix(">=") {
240            Some((BinaryOp::Ge, after))
241        } else if let Some(after) = ws.strip_prefix('<') {
242            Some((BinaryOp::Lt, after))
243        } else if let Some(after) = ws.strip_prefix('>') {
244            Some((BinaryOp::Gt, after))
245        } else if let Some(after) = ws.strip_prefix('=') {
246            Some((BinaryOp::Eq, after))
247        } else {
248            None
249        };
250
251        if let Some((op, after)) = op_result {
252            let (r, right) = self.parse_concat(after)?;
253            return Ok((r, Expr::BinaryOp {
254                op,
255                span: self.span(i, r),
256                left: Box::new(left),
257                right: Box::new(right),
258            }));
259        }
260
261        Ok((rest, left))
262    }
263}
264
265// ── public API ──────────────────────────────────────────────────────────────
266
267/// Parse a formula string into an expression tree.
268///
269/// The formula must start with `=`. Returns a [`ParseError`] if the input
270/// is not a valid formula.
271pub fn parse(formula: &str) -> Result<Expr, ParseError> {
272    let input = formula.strip_prefix('=').unwrap_or(formula).trim();
273    let p = Parser::new(formula);
274    match p.parse_comparison(input) {
275        Ok((rest, expr)) => {
276            let rest = rest.trim();
277            if rest.is_empty() {
278                Ok(expr)
279            } else {
280                Err(ParseError {
281                    message: format!("Unexpected input '{}'", rest),
282                    position: offset(formula, rest),
283                })
284            }
285        }
286        Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(ParseError {
287            message: "Parse error".into(),
288            position: offset(formula, e.input),
289        }),
290        Err(nom::Err::Incomplete(_)) => Err(ParseError {
291            message: "Incomplete input".into(),
292            position: formula.len(),
293        }),
294    }
295}
296
297/// Validate that a formula string is syntactically correct without returning the AST.
298pub fn validate(formula: &str) -> Result<(), ParseError> {
299    parse(formula).map(|_| ())
300}
301
302#[cfg(test)]
303mod tests {
304    use super::*;
305    use crate::parser::ast::{BinaryOp, Expr, UnaryOp};
306
307    #[test]
308    fn parse_number_literal() {
309        let expr = parse("=42").unwrap();
310        assert!(matches!(expr, Expr::Number(n, _) if n == 42.0));
311    }
312
313    #[test]
314    fn parse_binary_add() {
315        let expr = parse("=1+2").unwrap();
316        assert!(matches!(expr, Expr::BinaryOp { op: BinaryOp::Add, .. }));
317    }
318
319    #[test]
320    fn parse_precedence() {
321        // 2+3*4 should parse as 2+(3*4)
322        let expr = parse("=2+3*4").unwrap();
323        match expr {
324            Expr::BinaryOp { op: BinaryOp::Add, right, .. } => {
325                assert!(matches!(*right, Expr::BinaryOp { op: BinaryOp::Mul, .. }));
326            }
327            _ => panic!("Expected Add at top"),
328        }
329    }
330
331    #[test]
332    fn parse_function_call() {
333        let expr = parse("=SUM(1,2,3)").unwrap();
334        match expr {
335            Expr::FunctionCall { name, args, .. } => {
336                assert_eq!(name, "SUM");
337                assert_eq!(args.len(), 3);
338            }
339            _ => panic!("Expected FunctionCall"),
340        }
341    }
342
343    #[test]
344    fn parse_percent() {
345        let expr = parse("=50%").unwrap();
346        assert!(matches!(expr, Expr::UnaryOp { op: UnaryOp::Percent, .. }));
347    }
348
349    #[test]
350    fn parse_string_literal() {
351        let expr = parse("=\"hello\"").unwrap();
352        assert!(matches!(expr, Expr::Text(ref s, _) if s == "hello"));
353    }
354
355    #[test]
356    fn parse_concat_op() {
357        let expr = parse("=\"a\"&\"b\"").unwrap();
358        assert!(matches!(expr, Expr::BinaryOp { op: BinaryOp::Concat, .. }));
359    }
360
361    #[test]
362    fn validate_incomplete_fails() {
363        let err = validate("=SUM(1,").unwrap_err();
364        assert!(!err.message.is_empty());
365    }
366
367    #[test]
368    fn parse_nested() {
369        assert!(parse("=ROUND(SUM(1,2)*1.1, 1)").is_ok());
370    }
371
372    #[test]
373    fn parse_boolean() {
374        let expr = parse("=TRUE").unwrap();
375        assert!(matches!(expr, Expr::Bool(true, _)));
376    }
377
378    #[test]
379    fn parse_variable() {
380        let expr = parse("=myVar").unwrap();
381        assert!(matches!(expr, Expr::Variable(ref n, _) if n == "myVar"));
382    }
383
384    #[test]
385    fn parse_power_right_assoc() {
386        // 2^3^2 = 2^(3^2) = 2^9 = 512 (right-associative)
387        let expr = parse("=2^3^2").unwrap();
388        match expr {
389            Expr::BinaryOp { op: BinaryOp::Pow, right, .. } => {
390                assert!(matches!(*right, Expr::BinaryOp { op: BinaryOp::Pow, .. }));
391            }
392            _ => panic!("Expected Pow at top"),
393        }
394    }
395}