1pub mod ast;
2pub mod refs;
3pub mod tokens;
4
5pub use ast::Expr;
6pub use refs::{CellAddr, Ref};
7use ast::{BinaryOp, Span, UnaryOp};
8use crate::types::ParseError;
9use nom::{IResult, character::complete::multispace0};
10use tokens::{bool_literal, dollar_cell_ref, identifier, number_literal, offset, string_literal};
11
12fn cell_ref_text(i: &str) -> IResult<&str, &str> {
21 dollar_cell_ref(i).or_else(|_| identifier(i))
22}
23
24struct Parser<'a> {
25 full: &'a str,
26}
27
28impl<'a> Parser<'a> {
29 fn new(full: &'a str) -> Self {
30 Self { full }
31 }
32
33 fn span(&self, before: &str, after: &str) -> Span {
34 let start = offset(self.full, before);
35 let end = offset(self.full, after);
36 Span::new(start, end - start)
37 }
38
39 fn parse_primary(&self, i: &'a str) -> IResult<&'a str, Expr> {
42 let i = multispace0(i)?.0;
43
44 if let Ok((rest, n)) = number_literal(i) {
46 return Ok((rest, Expr::Number(n, self.span(i, rest))));
47 }
48
49 if let Ok((rest, text)) = string_literal(i) {
51 return Ok((rest, Expr::Text(text, self.span(i, rest))));
52 }
53
54 if let Some(inner) = i.strip_prefix('{') {
56 let (rest, elems) = self.parse_array_elements(inner)?;
57 let rest = multispace0(rest)?.0;
58 if let Some(after) = rest.strip_prefix('}') {
59 return Ok((after, Expr::Array(elems, self.span(i, after))));
60 }
61 return Err(nom::Err::Error(nom::error::Error::new(
62 rest,
63 nom::error::ErrorKind::Char,
64 )));
65 }
66
67 if let Some(inner) = i.strip_prefix('(') {
69 let (rest, expr) = self.parse_comparison(inner)?;
70 let rest = multispace0(rest)?.0;
71 if let Some(after) = rest.strip_prefix(')') {
72 return Ok((after, expr));
73 }
74 return Err(nom::Err::Error(nom::error::Error::new(
75 rest,
76 nom::error::ErrorKind::Char,
77 )));
78 }
79
80 if let Ok((rest, b)) = bool_literal(i) {
82 return Ok((rest, Expr::Bool(b, self.span(i, rest))));
83 }
84
85 if i.starts_with('\'') {
87 return self.parse_quoted_sheet_ref(i);
88 }
89
90 if let Ok((rest, span)) = dollar_cell_ref(i) {
96 let rest_ws = multispace0(rest)?.0;
97 if let Some(after_colon) = rest_ws.strip_prefix(':') {
98 if let Ok((rest2, end_span)) = cell_ref_text(after_colon) {
99 if CellAddr::parse(end_span).is_some() {
100 let range_name = format!("{}:{}", span, end_span);
101 return Ok((rest2, Expr::Variable(range_name, self.span(i, rest2))));
102 }
103 }
104 }
105 return Ok((rest, Expr::Variable(span.to_string(), self.span(i, rest))));
106 }
107
108 if let Ok((rest, name)) = identifier(i) {
110 if let Some(after_bang) = rest.strip_prefix('!') {
113 return self.parse_ref_body(i, name.to_string(), after_bang);
114 }
115 let rest_ws = multispace0(rest)?.0;
116 if let Some(args_input) = rest_ws.strip_prefix('(') {
117 let (rest2, args) = self.parse_arg_list(args_input)?;
119 let rest2 = multispace0(rest2)?.0;
120 if let Some(after_close) = rest2.strip_prefix(')') {
121 let func_expr = Expr::FunctionCall {
122 name: name.to_uppercase(),
123 args,
124 span: self.span(i, after_close),
125 };
126 let after_ws = multispace0(after_close)?.0;
128 if let Some(call_input) = after_ws.strip_prefix('(') {
129 let (rest3, call_args) = self.parse_arg_list(call_input)?;
130 let rest3 = multispace0(rest3)?.0;
131 if let Some(after) = rest3.strip_prefix(')') {
132 return Ok((after, Expr::Apply {
133 func: Box::new(func_expr),
134 call_args,
135 span: self.span(i, after),
136 }));
137 }
138 return Err(nom::Err::Error(nom::error::Error::new(
139 rest3,
140 nom::error::ErrorKind::Char,
141 )));
142 }
143 return Ok((after_close, func_expr));
144 }
145 return Err(nom::Err::Error(nom::error::Error::new(
146 rest2,
147 nom::error::ErrorKind::Char,
148 )));
149 }
150 if CellAddr::parse(name).is_some() {
154 if let Some(after_colon) = rest_ws.strip_prefix(':') {
155 if let Ok((rest2, name2)) = cell_ref_text(after_colon) {
156 if CellAddr::parse(name2).is_some() {
157 let range_name = format!("{}:{}", name, name2);
158 return Ok((rest2, Expr::Variable(range_name, self.span(i, rest2))));
159 }
160 }
161 }
162 }
163 return Ok((rest, Expr::Variable(name.to_string(), self.span(i, rest))));
164 }
165
166 Err(nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Alt)))
167 }
168
169 fn parse_ref_body(&self, start: &'a str, sheet: String, i: &'a str) -> IResult<&'a str, Expr> {
175 let err = || nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Tag));
176 let (rest, cell_text) = cell_ref_text(i).map_err(|_| err())?;
177 let addr = CellAddr::parse(cell_text).ok_or_else(err)?;
178 let rest_ws = multispace0(rest)?.0;
180 if let Some(after_colon) = rest_ws.strip_prefix(':') {
181 if let Ok((rest2, end_text)) = cell_ref_text(after_colon) {
182 if let Some(end) = CellAddr::parse(end_text) {
183 let r = Ref::Range { sheet: Some(sheet), start: addr, end };
184 return Ok((rest2, Expr::Reference(r, self.span(start, rest2))));
185 }
186 }
187 }
188 let r = Ref::Cell { sheet: Some(sheet), addr };
189 Ok((rest, Expr::Reference(r, self.span(start, rest))))
190 }
191
192 fn parse_quoted_sheet_ref(&self, i: &'a str) -> IResult<&'a str, Expr> {
195 let inner = &i[1..];
196 let mut sheet = String::new();
197 let mut idx = 0;
198 loop {
199 match inner[idx..].find('\'') {
200 None => {
202 return Err(nom::Err::Error(nom::error::Error::new(
203 i,
204 nom::error::ErrorKind::Char,
205 )));
206 }
207 Some(q) => {
208 sheet.push_str(&inner[idx..idx + q]);
209 let after = idx + q + 1;
210 if inner[after..].starts_with('\'') {
211 sheet.push('\'');
212 idx = after + 1;
213 } else {
214 idx = after;
215 break;
216 }
217 }
218 }
219 }
220 let rest = &inner[idx..];
221 if sheet.is_empty() {
222 return Err(nom::Err::Error(nom::error::Error::new(
223 i,
224 nom::error::ErrorKind::Char,
225 )));
226 }
227 match rest.strip_prefix('!') {
228 Some(after_bang) => self.parse_ref_body(i, sheet, after_bang),
229 None => Err(nom::Err::Error(nom::error::Error::new(
230 rest,
231 nom::error::ErrorKind::Char,
232 ))),
233 }
234 }
235
236 fn parse_arg_list(&self, i: &'a str) -> IResult<&'a str, Vec<Expr>> {
237 let mut args = Vec::new();
238 let mut rest = multispace0(i)?.0;
239
240 if rest.starts_with(')') {
241 return Ok((rest, args));
242 }
243
244 let ws = multispace0(rest)?.0;
246 if ws.starts_with(',') || ws.starts_with(')') {
247 args.push(Expr::Variable(String::new(), Span::new(0, 0)));
249 } else {
250 let (r, first) = self.parse_comparison(rest)?;
251 args.push(first);
252 rest = r;
253 }
254
255 loop {
256 rest = multispace0(rest)?.0;
257 if let Some(after_comma) = rest.strip_prefix(',') {
258 let after_ws = multispace0(after_comma)?.0;
259 if after_ws.starts_with(',') || after_ws.starts_with(')') {
260 args.push(Expr::Variable(String::new(), Span::new(0, 0)));
262 rest = after_comma;
263 } else {
264 let (r, arg) = self.parse_comparison(after_comma)?;
265 args.push(arg);
266 rest = r;
267 }
268 } else {
269 break;
270 }
271 }
272
273 Ok((rest, args))
274 }
275
276 fn parse_array_elements(&self, i: &'a str) -> IResult<&'a str, Vec<Expr>> {
277 let mut rows: Vec<Vec<Expr>> = Vec::new();
278 let mut current_row: Vec<Expr> = Vec::new();
279 let mut rest = multispace0(i)?.0;
280 if rest.starts_with('}') {
281 return Ok((rest, Vec::new())); }
283 let (r, first) = self.parse_comparison(rest)?;
284 current_row.push(first);
285 rest = r;
286 loop {
287 rest = multispace0(rest)?.0;
288 if let Some(after_comma) = rest.strip_prefix(',') {
289 let (r, elem) = self.parse_comparison(after_comma)?;
290 current_row.push(elem);
291 rest = r;
292 } else if let Some(after_semi) = rest.strip_prefix(';') {
293 rows.push(std::mem::take(&mut current_row));
294 let (r, elem) = self.parse_comparison(after_semi)?;
295 current_row.push(elem);
296 rest = r;
297 } else {
298 break;
299 }
300 }
301 rows.push(current_row);
302 if rows.len() == 1 {
304 return Ok((rest, rows.into_iter().next().unwrap()));
305 }
306 let span_start = i;
308 let row_exprs: Vec<Expr> = rows
309 .into_iter()
310 .map(|row_elems| {
311 let s = self.span(span_start, rest);
312 Expr::Array(row_elems, s)
313 })
314 .collect();
315 Ok((rest, row_exprs))
316 }
317
318 fn parse_postfix(&self, i: &'a str) -> IResult<&'a str, Expr> {
321 let (rest, expr) = self.parse_primary(i)?;
322 let rest_ws = multispace0(rest)?.0;
323 if let Some(after) = rest_ws.strip_prefix('%') {
324 return Ok((after, Expr::UnaryOp {
325 op: UnaryOp::Percent,
326 operand: Box::new(expr),
327 span: self.span(i, after),
328 }));
329 }
330 Ok((rest, expr))
331 }
332
333 fn parse_unary(&self, i: &'a str) -> IResult<&'a str, Expr> {
336 let i_ws = multispace0(i)?.0;
337 if let Some(after_minus) = i_ws.strip_prefix('-') {
338 let (rest, operand) = self.parse_unary(after_minus)?;
339 return Ok((rest, Expr::UnaryOp {
340 op: UnaryOp::Neg,
341 operand: Box::new(operand),
342 span: self.span(i_ws, rest),
343 }));
344 }
345 self.parse_postfix(i)
346 }
347
348 fn parse_power(&self, i: &'a str) -> IResult<&'a str, Expr> {
351 let (rest, left) = self.parse_unary(i)?;
352 let rest_ws = multispace0(rest)?.0;
353 if let Some(after_op) = rest_ws.strip_prefix('^') {
354 let (rest2, right) = self.parse_power(after_op)?;
355 return Ok((rest2, Expr::BinaryOp {
356 op: BinaryOp::Pow,
357 left: Box::new(left),
358 right: Box::new(right),
359 span: self.span(i, rest2),
360 }));
361 }
362 Ok((rest, left))
363 }
364
365 fn parse_multiplicative(&self, i: &'a str) -> IResult<&'a str, Expr> {
368 let (mut rest, mut left) = self.parse_power(i)?;
369 loop {
370 let ws = multispace0(rest)?.0;
371 let op = ws.strip_prefix('*').map(|after| (BinaryOp::Mul, after))
372 .or_else(|| ws.strip_prefix('/').map(|after| (BinaryOp::Div, after)));
373 match op {
374 None => break,
375 Some((op, after)) => {
376 let (r, right) = self.parse_power(after)?;
377 left = Expr::BinaryOp {
378 op,
379 span: self.span(i, r),
380 left: Box::new(left),
381 right: Box::new(right),
382 };
383 rest = r;
384 }
385 }
386 }
387 Ok((rest, left))
388 }
389
390 fn parse_additive(&self, i: &'a str) -> IResult<&'a str, Expr> {
393 let (mut rest, mut left) = self.parse_multiplicative(i)?;
394 loop {
395 let ws = multispace0(rest)?.0;
396 let op = ws.strip_prefix('+').map(|after| (BinaryOp::Add, after))
397 .or_else(|| ws.strip_prefix('-').map(|after| (BinaryOp::Sub, after)));
398 match op {
399 None => break,
400 Some((op, after)) => {
401 let (r, right) = self.parse_multiplicative(after)?;
402 left = Expr::BinaryOp {
403 op,
404 span: self.span(i, r),
405 left: Box::new(left),
406 right: Box::new(right),
407 };
408 rest = r;
409 }
410 }
411 }
412 Ok((rest, left))
413 }
414
415 fn parse_concat(&self, i: &'a str) -> IResult<&'a str, Expr> {
418 let (mut rest, mut left) = self.parse_additive(i)?;
419 loop {
420 let ws = multispace0(rest)?.0;
421 if let Some(after) = ws.strip_prefix('&') {
422 let (r, right) = self.parse_additive(after)?;
423 left = Expr::BinaryOp {
424 op: BinaryOp::Concat,
425 span: self.span(i, r),
426 left: Box::new(left),
427 right: Box::new(right),
428 };
429 rest = r;
430 } else {
431 break;
432 }
433 }
434 Ok((rest, left))
435 }
436
437 fn parse_comparison(&self, i: &'a str) -> IResult<&'a str, Expr> {
440 let (rest, left) = self.parse_concat(i)?;
441 let ws = multispace0(rest)?.0;
442
443 let op_result: Option<(BinaryOp, &'a str)> = if let Some(after) = ws.strip_prefix("<>") {
445 Some((BinaryOp::Ne, after))
446 } else if let Some(after) = ws.strip_prefix("<=") {
447 Some((BinaryOp::Le, after))
448 } else if let Some(after) = ws.strip_prefix(">=") {
449 Some((BinaryOp::Ge, after))
450 } else if let Some(after) = ws.strip_prefix('<') {
451 Some((BinaryOp::Lt, after))
452 } else if let Some(after) = ws.strip_prefix('>') {
453 Some((BinaryOp::Gt, after))
454 } else if let Some(after) = ws.strip_prefix('=') {
455 Some((BinaryOp::Eq, after))
456 } else {
457 None
458 };
459
460 if let Some((op, after)) = op_result {
461 let (r, right) = self.parse_concat(after)?;
462 return Ok((r, Expr::BinaryOp {
463 op,
464 span: self.span(i, r),
465 left: Box::new(left),
466 right: Box::new(right),
467 }));
468 }
469
470 Ok((rest, left))
471 }
472}
473
474#[deprecated(since = "0.7.0", note = "use Engine::sheets()/Engine::excel() and engine.parse() — engine flavor is required; see ADR 2026-04-27; removal target: 0.7.0 coordinated release")]
481pub fn parse(formula: &str) -> Result<Expr, ParseError> {
482 parse_formula(formula)
483}
484
485pub(crate) fn parse_formula(formula: &str) -> Result<Expr, ParseError> {
487 let input = formula.strip_prefix('=').unwrap_or(formula).trim();
488 let p = Parser::new(formula);
489 match p.parse_comparison(input) {
490 Ok((rest, expr)) => {
491 let rest = rest.trim();
492 if rest.is_empty() {
493 Ok(expr)
494 } else {
495 Err(ParseError {
496 message: format!("Unexpected input '{}'", rest),
497 position: offset(formula, rest),
498 })
499 }
500 }
501 Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(ParseError {
502 message: "Parse error".into(),
503 position: offset(formula, e.input),
504 }),
505 Err(nom::Err::Incomplete(_)) => Err(ParseError {
506 message: "Incomplete input".into(),
507 position: formula.len(),
508 }),
509 }
510}
511
512#[deprecated(since = "0.7.0", note = "use Engine::sheets()/Engine::excel() and engine.validate() — engine flavor is required; see ADR 2026-04-27; removal target: 0.7.0 coordinated release")]
514pub fn validate(formula: &str) -> Result<(), ParseError> {
515 parse_formula(formula).map(|_| ())
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521 use super::parse_formula as parse;
522 use crate::parser::ast::{BinaryOp, Expr, UnaryOp};
523
524 #[test]
525 fn parse_number_literal() {
526 let expr = parse("=42").unwrap();
527 assert!(matches!(expr, Expr::Number(n, _) if n == 42.0));
528 }
529
530 #[test]
531 fn parse_binary_add() {
532 let expr = parse("=1+2").unwrap();
533 assert!(matches!(expr, Expr::BinaryOp { op: BinaryOp::Add, .. }));
534 }
535
536 #[test]
537 fn parse_precedence() {
538 let expr = parse("=2+3*4").unwrap();
540 match expr {
541 Expr::BinaryOp { op: BinaryOp::Add, right, .. } => {
542 assert!(matches!(*right, Expr::BinaryOp { op: BinaryOp::Mul, .. }));
543 }
544 _ => panic!("Expected Add at top"),
545 }
546 }
547
548 #[test]
549 fn parse_function_call() {
550 let expr = parse("=SUM(1,2,3)").unwrap();
551 match expr {
552 Expr::FunctionCall { name, args, .. } => {
553 assert_eq!(name, "SUM");
554 assert_eq!(args.len(), 3);
555 }
556 _ => panic!("Expected FunctionCall"),
557 }
558 }
559
560 #[test]
561 fn parse_percent() {
562 let expr = parse("=50%").unwrap();
563 assert!(matches!(expr, Expr::UnaryOp { op: UnaryOp::Percent, .. }));
564 }
565
566 #[test]
567 fn parse_string_literal() {
568 let expr = parse("=\"hello\"").unwrap();
569 assert!(matches!(expr, Expr::Text(ref s, _) if s == "hello"));
570 }
571
572 #[test]
573 fn parse_concat_op() {
574 let expr = parse("=\"a\"&\"b\"").unwrap();
575 assert!(matches!(expr, Expr::BinaryOp { op: BinaryOp::Concat, .. }));
576 }
577
578 #[test]
579 #[allow(deprecated)]
580 fn validate_incomplete_fails() {
581 let err = validate("=SUM(1,").unwrap_err();
582 assert!(!err.message.is_empty());
583 }
584
585 #[test]
586 fn parse_nested() {
587 assert!(parse("=ROUND(SUM(1,2)*1.1, 1)").is_ok());
588 }
589
590 #[test]
591 fn parse_boolean() {
592 let expr = parse("=TRUE").unwrap();
593 assert!(matches!(expr, Expr::Bool(true, _)));
594 }
595
596 #[test]
597 fn parse_variable() {
598 let expr = parse("=myVar").unwrap();
599 assert!(matches!(expr, Expr::Variable(ref n, _) if n == "myVar"));
600 }
601
602 #[test]
603 fn parse_array_literal_numbers() {
604 let expr = parse("={1,2,3}").unwrap();
605 match expr {
606 Expr::Array(elems, _) => assert_eq!(elems.len(), 3),
607 _ => panic!("Expected Array"),
608 }
609 }
610
611 #[test]
612 fn parse_array_literal_mixed() {
613 let expr = parse("={1,\"hello\",TRUE}").unwrap();
614 assert!(matches!(expr, Expr::Array(_, _)));
615 }
616
617 #[test]
618 fn parse_array_literal_empty() {
619 let expr = parse("={}").unwrap();
620 assert!(matches!(expr, Expr::Array(ref e, _) if e.is_empty()));
621 }
622
623 #[test]
624 fn parse_array_in_function_call() {
625 let expr = parse("=SUM({1,2,3})").unwrap();
626 match expr {
627 Expr::FunctionCall { name, args, .. } => {
628 assert_eq!(name, "SUM");
629 assert_eq!(args.len(), 1);
630 assert!(matches!(args[0], Expr::Array(_, _)));
631 }
632 _ => panic!("Expected FunctionCall"),
633 }
634 }
635
636 #[test]
637 fn parse_power_right_assoc() {
638 let expr = parse("=2^3^2").unwrap();
640 match expr {
641 Expr::BinaryOp { op: BinaryOp::Pow, right, .. } => {
642 assert!(matches!(*right, Expr::BinaryOp { op: BinaryOp::Pow, .. }));
643 }
644 _ => panic!("Expected Pow at top"),
645 }
646 }
647}