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 fn parse_primary(&self, i: &'a str) -> IResult<&'a str, Expr> {
28 let i = multispace0(i)?.0;
29
30 if let Ok((rest, n)) = number_literal(i) {
32 return Ok((rest, Expr::Number(n, self.span(i, rest))));
33 }
34
35 if let Ok((rest, text)) = string_literal(i) {
37 return Ok((rest, Expr::Text(text, self.span(i, rest))));
38 }
39
40 if let Some(inner) = i.strip_prefix('{') {
42 let (rest, elems) = self.parse_array_elements(inner)?;
43 let rest = multispace0(rest)?.0;
44 if let Some(after) = rest.strip_prefix('}') {
45 return Ok((after, Expr::Array(elems, self.span(i, after))));
46 }
47 return Err(nom::Err::Error(nom::error::Error::new(
48 rest,
49 nom::error::ErrorKind::Char,
50 )));
51 }
52
53 if let Some(inner) = i.strip_prefix('(') {
55 let (rest, expr) = self.parse_comparison(inner)?;
56 let rest = multispace0(rest)?.0;
57 if let Some(after) = rest.strip_prefix(')') {
58 return Ok((after, expr));
59 }
60 return Err(nom::Err::Error(nom::error::Error::new(
61 rest,
62 nom::error::ErrorKind::Char,
63 )));
64 }
65
66 if let Ok((rest, b)) = bool_literal(i) {
68 return Ok((rest, Expr::Bool(b, self.span(i, rest))));
69 }
70
71 if let Ok((rest, name)) = identifier(i) {
73 let rest_ws = multispace0(rest)?.0;
74 if let Some(args_input) = rest_ws.strip_prefix('(') {
75 let (rest2, args) = self.parse_arg_list(args_input)?;
77 let rest2 = multispace0(rest2)?.0;
78 if let Some(after_close) = rest2.strip_prefix(')') {
79 let func_expr = Expr::FunctionCall {
80 name: name.to_uppercase(),
81 args,
82 span: self.span(i, after_close),
83 };
84 let after_ws = multispace0(after_close)?.0;
86 if let Some(call_input) = after_ws.strip_prefix('(') {
87 let (rest3, call_args) = self.parse_arg_list(call_input)?;
88 let rest3 = multispace0(rest3)?.0;
89 if let Some(after) = rest3.strip_prefix(')') {
90 return Ok((after, Expr::Apply {
91 func: Box::new(func_expr),
92 call_args,
93 span: self.span(i, after),
94 }));
95 }
96 return Err(nom::Err::Error(nom::error::Error::new(
97 rest3,
98 nom::error::ErrorKind::Char,
99 )));
100 }
101 return Ok((after_close, func_expr));
102 }
103 return Err(nom::Err::Error(nom::error::Error::new(
104 rest2,
105 nom::error::ErrorKind::Char,
106 )));
107 }
108 if is_cell_ref(name) {
110 if let Some(after_colon) = rest_ws.strip_prefix(':') {
111 if let Ok((rest2, name2)) = identifier(after_colon) {
112 if is_cell_ref(name2) {
113 let range_name = format!("{}:{}", name, name2);
114 return Ok((rest2, Expr::Variable(range_name, self.span(i, rest2))));
115 }
116 }
117 }
118 }
119 return Ok((rest, Expr::Variable(name.to_string(), self.span(i, rest))));
120 }
121
122 Err(nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Alt)))
123 }
124
125 fn parse_arg_list(&self, i: &'a str) -> IResult<&'a str, Vec<Expr>> {
126 let mut args = Vec::new();
127 let mut rest = multispace0(i)?.0;
128
129 if rest.starts_with(')') {
130 return Ok((rest, args));
131 }
132
133 let ws = multispace0(rest)?.0;
135 if ws.starts_with(',') || ws.starts_with(')') {
136 args.push(Expr::Variable(String::new(), Span::new(0, 0)));
138 } else {
139 let (r, first) = self.parse_comparison(rest)?;
140 args.push(first);
141 rest = r;
142 }
143
144 loop {
145 rest = multispace0(rest)?.0;
146 if let Some(after_comma) = rest.strip_prefix(',') {
147 let after_ws = multispace0(after_comma)?.0;
148 if after_ws.starts_with(',') || after_ws.starts_with(')') {
149 args.push(Expr::Variable(String::new(), Span::new(0, 0)));
151 rest = after_comma;
152 } else {
153 let (r, arg) = self.parse_comparison(after_comma)?;
154 args.push(arg);
155 rest = r;
156 }
157 } else {
158 break;
159 }
160 }
161
162 Ok((rest, args))
163 }
164
165 fn parse_array_elements(&self, i: &'a str) -> IResult<&'a str, Vec<Expr>> {
166 let mut rows: Vec<Vec<Expr>> = Vec::new();
167 let mut current_row: Vec<Expr> = Vec::new();
168 let mut rest = multispace0(i)?.0;
169 if rest.starts_with('}') {
170 return Ok((rest, Vec::new())); }
172 let (r, first) = self.parse_comparison(rest)?;
173 current_row.push(first);
174 rest = r;
175 loop {
176 rest = multispace0(rest)?.0;
177 if let Some(after_comma) = rest.strip_prefix(',') {
178 let (r, elem) = self.parse_comparison(after_comma)?;
179 current_row.push(elem);
180 rest = r;
181 } else if let Some(after_semi) = rest.strip_prefix(';') {
182 rows.push(std::mem::take(&mut current_row));
183 let (r, elem) = self.parse_comparison(after_semi)?;
184 current_row.push(elem);
185 rest = r;
186 } else {
187 break;
188 }
189 }
190 rows.push(current_row);
191 if rows.len() == 1 {
193 return Ok((rest, rows.into_iter().next().unwrap()));
194 }
195 let span_start = i;
197 let row_exprs: Vec<Expr> = rows
198 .into_iter()
199 .map(|row_elems| {
200 let s = self.span(span_start, rest);
201 Expr::Array(row_elems, s)
202 })
203 .collect();
204 Ok((rest, row_exprs))
205 }
206
207 fn parse_postfix(&self, i: &'a str) -> IResult<&'a str, Expr> {
210 let (rest, expr) = self.parse_primary(i)?;
211 let rest_ws = multispace0(rest)?.0;
212 if let Some(after) = rest_ws.strip_prefix('%') {
213 return Ok((after, Expr::UnaryOp {
214 op: UnaryOp::Percent,
215 operand: Box::new(expr),
216 span: self.span(i, after),
217 }));
218 }
219 Ok((rest, expr))
220 }
221
222 fn parse_unary(&self, i: &'a str) -> IResult<&'a str, Expr> {
225 let i_ws = multispace0(i)?.0;
226 if let Some(after_minus) = i_ws.strip_prefix('-') {
227 let (rest, operand) = self.parse_unary(after_minus)?;
228 return Ok((rest, Expr::UnaryOp {
229 op: UnaryOp::Neg,
230 operand: Box::new(operand),
231 span: self.span(i_ws, rest),
232 }));
233 }
234 self.parse_postfix(i)
235 }
236
237 fn parse_power(&self, i: &'a str) -> IResult<&'a str, Expr> {
240 let (rest, left) = self.parse_unary(i)?;
241 let rest_ws = multispace0(rest)?.0;
242 if let Some(after_op) = rest_ws.strip_prefix('^') {
243 let (rest2, right) = self.parse_power(after_op)?;
244 return Ok((rest2, Expr::BinaryOp {
245 op: BinaryOp::Pow,
246 left: Box::new(left),
247 right: Box::new(right),
248 span: self.span(i, rest2),
249 }));
250 }
251 Ok((rest, left))
252 }
253
254 fn parse_multiplicative(&self, i: &'a str) -> IResult<&'a str, Expr> {
257 let (mut rest, mut left) = self.parse_power(i)?;
258 loop {
259 let ws = multispace0(rest)?.0;
260 let op = ws.strip_prefix('*').map(|after| (BinaryOp::Mul, after))
261 .or_else(|| ws.strip_prefix('/').map(|after| (BinaryOp::Div, after)));
262 match op {
263 None => break,
264 Some((op, after)) => {
265 let (r, right) = self.parse_power(after)?;
266 left = Expr::BinaryOp {
267 op,
268 span: self.span(i, r),
269 left: Box::new(left),
270 right: Box::new(right),
271 };
272 rest = r;
273 }
274 }
275 }
276 Ok((rest, left))
277 }
278
279 fn parse_additive(&self, i: &'a str) -> IResult<&'a str, Expr> {
282 let (mut rest, mut left) = self.parse_multiplicative(i)?;
283 loop {
284 let ws = multispace0(rest)?.0;
285 let op = ws.strip_prefix('+').map(|after| (BinaryOp::Add, after))
286 .or_else(|| ws.strip_prefix('-').map(|after| (BinaryOp::Sub, after)));
287 match op {
288 None => break,
289 Some((op, after)) => {
290 let (r, right) = self.parse_multiplicative(after)?;
291 left = Expr::BinaryOp {
292 op,
293 span: self.span(i, r),
294 left: Box::new(left),
295 right: Box::new(right),
296 };
297 rest = r;
298 }
299 }
300 }
301 Ok((rest, left))
302 }
303
304 fn parse_concat(&self, i: &'a str) -> IResult<&'a str, Expr> {
307 let (mut rest, mut left) = self.parse_additive(i)?;
308 loop {
309 let ws = multispace0(rest)?.0;
310 if let Some(after) = ws.strip_prefix('&') {
311 let (r, right) = self.parse_additive(after)?;
312 left = Expr::BinaryOp {
313 op: BinaryOp::Concat,
314 span: self.span(i, r),
315 left: Box::new(left),
316 right: Box::new(right),
317 };
318 rest = r;
319 } else {
320 break;
321 }
322 }
323 Ok((rest, left))
324 }
325
326 fn parse_comparison(&self, i: &'a str) -> IResult<&'a str, Expr> {
329 let (rest, left) = self.parse_concat(i)?;
330 let ws = multispace0(rest)?.0;
331
332 let op_result: Option<(BinaryOp, &'a str)> = if let Some(after) = ws.strip_prefix("<>") {
334 Some((BinaryOp::Ne, after))
335 } else if let Some(after) = ws.strip_prefix("<=") {
336 Some((BinaryOp::Le, after))
337 } else if let Some(after) = ws.strip_prefix(">=") {
338 Some((BinaryOp::Ge, after))
339 } else if let Some(after) = ws.strip_prefix('<') {
340 Some((BinaryOp::Lt, after))
341 } else if let Some(after) = ws.strip_prefix('>') {
342 Some((BinaryOp::Gt, after))
343 } else if let Some(after) = ws.strip_prefix('=') {
344 Some((BinaryOp::Eq, after))
345 } else {
346 None
347 };
348
349 if let Some((op, after)) = op_result {
350 let (r, right) = self.parse_concat(after)?;
351 return Ok((r, Expr::BinaryOp {
352 op,
353 span: self.span(i, r),
354 left: Box::new(left),
355 right: Box::new(right),
356 }));
357 }
358
359 Ok((rest, left))
360 }
361}
362
363fn is_cell_ref(name: &str) -> bool {
365 let bytes = name.as_bytes();
366 let col_end = bytes.iter().take_while(|b| b.is_ascii_alphabetic()).count();
367 col_end > 0 && col_end < bytes.len() && bytes[col_end..].iter().all(|b| b.is_ascii_digit())
368}
369
370#[deprecated(note = "use Engine::sheets()/Engine::excel() and engine.parse() — engine flavor is required; see ADR 2026-04-27")]
377pub fn parse(formula: &str) -> Result<Expr, ParseError> {
378 parse_formula(formula)
379}
380
381pub(crate) fn parse_formula(formula: &str) -> Result<Expr, ParseError> {
383 let input = formula.strip_prefix('=').unwrap_or(formula).trim();
384 let p = Parser::new(formula);
385 match p.parse_comparison(input) {
386 Ok((rest, expr)) => {
387 let rest = rest.trim();
388 if rest.is_empty() {
389 Ok(expr)
390 } else {
391 Err(ParseError {
392 message: format!("Unexpected input '{}'", rest),
393 position: offset(formula, rest),
394 })
395 }
396 }
397 Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(ParseError {
398 message: "Parse error".into(),
399 position: offset(formula, e.input),
400 }),
401 Err(nom::Err::Incomplete(_)) => Err(ParseError {
402 message: "Incomplete input".into(),
403 position: formula.len(),
404 }),
405 }
406}
407
408#[deprecated(note = "use Engine::sheets()/Engine::excel() and engine.validate() — engine flavor is required; see ADR 2026-04-27")]
410pub fn validate(formula: &str) -> Result<(), ParseError> {
411 parse_formula(formula).map(|_| ())
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use super::parse_formula as parse;
418 use crate::parser::ast::{BinaryOp, Expr, UnaryOp};
419
420 #[test]
421 fn parse_number_literal() {
422 let expr = parse("=42").unwrap();
423 assert!(matches!(expr, Expr::Number(n, _) if n == 42.0));
424 }
425
426 #[test]
427 fn parse_binary_add() {
428 let expr = parse("=1+2").unwrap();
429 assert!(matches!(expr, Expr::BinaryOp { op: BinaryOp::Add, .. }));
430 }
431
432 #[test]
433 fn parse_precedence() {
434 let expr = parse("=2+3*4").unwrap();
436 match expr {
437 Expr::BinaryOp { op: BinaryOp::Add, right, .. } => {
438 assert!(matches!(*right, Expr::BinaryOp { op: BinaryOp::Mul, .. }));
439 }
440 _ => panic!("Expected Add at top"),
441 }
442 }
443
444 #[test]
445 fn parse_function_call() {
446 let expr = parse("=SUM(1,2,3)").unwrap();
447 match expr {
448 Expr::FunctionCall { name, args, .. } => {
449 assert_eq!(name, "SUM");
450 assert_eq!(args.len(), 3);
451 }
452 _ => panic!("Expected FunctionCall"),
453 }
454 }
455
456 #[test]
457 fn parse_percent() {
458 let expr = parse("=50%").unwrap();
459 assert!(matches!(expr, Expr::UnaryOp { op: UnaryOp::Percent, .. }));
460 }
461
462 #[test]
463 fn parse_string_literal() {
464 let expr = parse("=\"hello\"").unwrap();
465 assert!(matches!(expr, Expr::Text(ref s, _) if s == "hello"));
466 }
467
468 #[test]
469 fn parse_concat_op() {
470 let expr = parse("=\"a\"&\"b\"").unwrap();
471 assert!(matches!(expr, Expr::BinaryOp { op: BinaryOp::Concat, .. }));
472 }
473
474 #[test]
475 #[allow(deprecated)]
476 fn validate_incomplete_fails() {
477 let err = validate("=SUM(1,").unwrap_err();
478 assert!(!err.message.is_empty());
479 }
480
481 #[test]
482 fn parse_nested() {
483 assert!(parse("=ROUND(SUM(1,2)*1.1, 1)").is_ok());
484 }
485
486 #[test]
487 fn parse_boolean() {
488 let expr = parse("=TRUE").unwrap();
489 assert!(matches!(expr, Expr::Bool(true, _)));
490 }
491
492 #[test]
493 fn parse_variable() {
494 let expr = parse("=myVar").unwrap();
495 assert!(matches!(expr, Expr::Variable(ref n, _) if n == "myVar"));
496 }
497
498 #[test]
499 fn parse_array_literal_numbers() {
500 let expr = parse("={1,2,3}").unwrap();
501 match expr {
502 Expr::Array(elems, _) => assert_eq!(elems.len(), 3),
503 _ => panic!("Expected Array"),
504 }
505 }
506
507 #[test]
508 fn parse_array_literal_mixed() {
509 let expr = parse("={1,\"hello\",TRUE}").unwrap();
510 assert!(matches!(expr, Expr::Array(_, _)));
511 }
512
513 #[test]
514 fn parse_array_literal_empty() {
515 let expr = parse("={}").unwrap();
516 assert!(matches!(expr, Expr::Array(ref e, _) if e.is_empty()));
517 }
518
519 #[test]
520 fn parse_array_in_function_call() {
521 let expr = parse("=SUM({1,2,3})").unwrap();
522 match expr {
523 Expr::FunctionCall { name, args, .. } => {
524 assert_eq!(name, "SUM");
525 assert_eq!(args.len(), 1);
526 assert!(matches!(args[0], Expr::Array(_, _)));
527 }
528 _ => panic!("Expected FunctionCall"),
529 }
530 }
531
532 #[test]
533 fn parse_power_right_assoc() {
534 let expr = parse("=2^3^2").unwrap();
536 match expr {
537 Expr::BinaryOp { op: BinaryOp::Pow, right, .. } => {
538 assert!(matches!(*right, Expr::BinaryOp { op: BinaryOp::Pow, .. }));
539 }
540 _ => panic!("Expected Pow at top"),
541 }
542 }
543}