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