1pub mod ast;
2pub mod refs;
3pub mod tokens;
4
5pub use ast::{Expr, Span};
6pub use refs::{CellAddr, Ref};
7use ast::{BinaryOp, UnaryOp};
8use crate::types::ParseError;
9use nom::{IResult, character::complete::multispace0};
10use tokens::{bool_literal, dollar_cell_ref, error_literal, 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 inner = multispace0(inner)?.0;
74 let (rest, expr) = self.parse_comparison(inner)?;
75 let rest = multispace0(rest)?.0;
76 if let Some(after) = rest.strip_prefix(')') {
77 return Ok((after, expr));
78 }
79 return Err(nom::Err::Error(nom::error::Error::new(
80 rest,
81 nom::error::ErrorKind::Char,
82 )));
83 }
84
85 if let Ok((rest, b)) = bool_literal(i) {
87 return Ok((rest, Expr::Bool(b, self.span(i, rest))));
88 }
89
90 if let Ok((rest, kind)) = error_literal(i) {
95 return Ok((rest, Expr::Error(kind, self.span(i, rest))));
96 }
97
98 if let Some(after_bracket) = i.strip_prefix('[') {
102 if after_bracket.starts_with('@') {
103 return self.parse_table_ref(i, None, after_bracket);
104 }
105 }
106
107 if i.starts_with('\'') {
109 return self.parse_quoted_sheet_ref(i);
110 }
111
112 if let Ok((rest, span)) = dollar_cell_ref(i) {
118 let rest_ws = multispace0(rest)?.0;
119 if let Some(after_colon) = rest_ws.strip_prefix(':') {
120 if let Ok((rest2, end_span)) = cell_ref_text(after_colon) {
121 if CellAddr::parse(end_span).is_some() {
122 let range_name = format!("{}:{}", span, end_span);
123 return Ok((rest2, Expr::Variable(range_name, self.span(i, rest2))));
124 }
125 }
126 }
127 return Ok((rest, Expr::Variable(span.to_string(), self.span(i, rest))));
128 }
129
130 if let Ok((rest, name)) = identifier(i) {
132 if let Some(after_bang) = rest.strip_prefix('!') {
135 return self.parse_ref_body(i, name.to_string(), after_bang);
136 }
137 if let Some(after_bracket) = rest.strip_prefix('[') {
140 return self.parse_table_ref(i, Some(name.to_string()), after_bracket);
141 }
142 let rest_ws = multispace0(rest)?.0;
143 if let Some(args_input) = rest_ws.strip_prefix('(') {
144 let (rest2, args) = self.parse_arg_list(args_input)?;
146 let rest2 = multispace0(rest2)?.0;
147 if let Some(after_close) = rest2.strip_prefix(')') {
148 let func_expr = Expr::FunctionCall {
149 name: name.to_uppercase(),
150 args,
151 span: self.span(i, after_close),
152 };
153 let after_ws = multispace0(after_close)?.0;
155 if let Some(call_input) = after_ws.strip_prefix('(') {
156 let (rest3, call_args) = self.parse_arg_list(call_input)?;
157 let rest3 = multispace0(rest3)?.0;
158 if let Some(after) = rest3.strip_prefix(')') {
159 return Ok((after, Expr::Apply {
160 func: Box::new(func_expr),
161 call_args,
162 span: self.span(i, after),
163 }));
164 }
165 return Err(nom::Err::Error(nom::error::Error::new(
166 rest3,
167 nom::error::ErrorKind::Char,
168 )));
169 }
170 return Ok((after_close, func_expr));
171 }
172 return Err(nom::Err::Error(nom::error::Error::new(
173 rest2,
174 nom::error::ErrorKind::Char,
175 )));
176 }
177 if CellAddr::parse(name).is_some() {
181 if let Some(after_colon) = rest_ws.strip_prefix(':') {
182 if let Ok((rest2, name2)) = cell_ref_text(after_colon) {
183 if CellAddr::parse(name2).is_some() {
184 let range_name = format!("{}:{}", name, name2);
185 return Ok((rest2, Expr::Variable(range_name, self.span(i, rest2))));
186 }
187 }
188 }
189 }
190 return Ok((rest, Expr::Variable(name.to_string(), self.span(i, rest))));
191 }
192
193 Err(nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Alt)))
194 }
195
196 fn parse_ref_body(&self, start: &'a str, sheet: String, i: &'a str) -> IResult<&'a str, Expr> {
202 let err = || nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Tag));
203 let (rest, cell_text) = cell_ref_text(i).map_err(|_| err())?;
204 let addr = CellAddr::parse(cell_text).ok_or_else(err)?;
205 let rest_ws = multispace0(rest)?.0;
207 if let Some(after_colon) = rest_ws.strip_prefix(':') {
208 if let Ok((rest2, end_text)) = cell_ref_text(after_colon) {
209 if let Some(end) = CellAddr::parse(end_text) {
210 let r = Ref::Range { sheet: Some(sheet), start: addr, end };
211 return Ok((rest2, Expr::Reference(r, self.span(start, rest2))));
212 }
213 }
214 }
215 let r = Ref::Cell { sheet: Some(sheet), addr };
216 Ok((rest, Expr::Reference(r, self.span(start, rest))))
217 }
218
219 fn parse_table_ref(
224 &self,
225 start: &'a str,
226 table: Option<String>,
227 i: &'a str,
228 ) -> IResult<&'a str, Expr> {
229 let err = || nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Tag));
230 let this_row = i.starts_with('@');
231 let i = if this_row { &i[1..] } else { i };
232 let (rest, column) = identifier(i).map_err(|_| err())?;
233 let after = rest.strip_prefix(']').ok_or_else(err)?;
234 let r = Ref::Table { table, column: column.to_string(), this_row };
235 Ok((after, Expr::Reference(r, self.span(start, after))))
236 }
237
238 fn parse_quoted_sheet_ref(&self, i: &'a str) -> IResult<&'a str, Expr> {
241 let inner = &i[1..];
242 let mut sheet = String::new();
243 let mut idx = 0;
244 loop {
245 match inner[idx..].find('\'') {
246 None => {
248 return Err(nom::Err::Error(nom::error::Error::new(
249 i,
250 nom::error::ErrorKind::Char,
251 )));
252 }
253 Some(q) => {
254 sheet.push_str(&inner[idx..idx + q]);
255 let after = idx + q + 1;
256 if inner[after..].starts_with('\'') {
257 sheet.push('\'');
258 idx = after + 1;
259 } else {
260 idx = after;
261 break;
262 }
263 }
264 }
265 }
266 let rest = &inner[idx..];
267 if sheet.is_empty() {
268 return Err(nom::Err::Error(nom::error::Error::new(
269 i,
270 nom::error::ErrorKind::Char,
271 )));
272 }
273 match rest.strip_prefix('!') {
274 Some(after_bang) => self.parse_ref_body(i, sheet, after_bang),
275 None => Err(nom::Err::Error(nom::error::Error::new(
276 rest,
277 nom::error::ErrorKind::Char,
278 ))),
279 }
280 }
281
282 fn parse_arg_list(&self, i: &'a str) -> IResult<&'a str, Vec<Expr>> {
283 let mut args = Vec::new();
284 let mut rest = multispace0(i)?.0;
285
286 if rest.starts_with(')') {
287 return Ok((rest, args));
288 }
289
290 let ws = multispace0(rest)?.0;
292 if ws.starts_with(',') || ws.starts_with(')') {
293 args.push(Expr::Variable(String::new(), Span::new(0, 0)));
295 } else {
296 let (r, first) = self.parse_comparison(rest)?;
297 args.push(first);
298 rest = r;
299 }
300
301 loop {
302 rest = multispace0(rest)?.0;
303 if let Some(after_comma) = rest.strip_prefix(',') {
304 let after_ws = multispace0(after_comma)?.0;
305 if after_ws.starts_with(',') || after_ws.starts_with(')') {
306 args.push(Expr::Variable(String::new(), Span::new(0, 0)));
308 rest = after_comma;
309 } else {
310 let (r, arg) = self.parse_comparison(after_ws)?;
317 args.push(arg);
318 rest = r;
319 }
320 } else {
321 break;
322 }
323 }
324
325 Ok((rest, args))
326 }
327
328 fn parse_array_elements(&self, i: &'a str) -> IResult<&'a str, Vec<Expr>> {
329 let mut rows: Vec<Vec<Expr>> = Vec::new();
330 let mut current_row: Vec<Expr> = Vec::new();
331 let mut rest = multispace0(i)?.0;
332 if rest.starts_with('}') {
333 return Ok((rest, Vec::new())); }
335 let (r, first) = self.parse_comparison(rest)?;
336 current_row.push(first);
337 rest = r;
338 loop {
339 rest = multispace0(rest)?.0;
340 if let Some(after_comma) = rest.strip_prefix(',') {
341 let after_ws = multispace0(after_comma)?.0;
348 let (r, elem) = self.parse_comparison(after_ws)?;
349 current_row.push(elem);
350 rest = r;
351 } else if let Some(after_semi) = rest.strip_prefix(';') {
352 rows.push(std::mem::take(&mut current_row));
353 let after_ws = multispace0(after_semi)?.0;
356 let (r, elem) = self.parse_comparison(after_ws)?;
357 current_row.push(elem);
358 rest = r;
359 } else {
360 break;
361 }
362 }
363 rows.push(current_row);
364 if rows.len() == 1 {
366 return Ok((rest, rows.into_iter().next().unwrap()));
367 }
368 let row_exprs: Vec<Expr> = rows
374 .into_iter()
375 .map(|row_elems| {
376 let s = match (row_elems.first(), row_elems.last()) {
377 (Some(first), Some(last)) => {
378 let start = first.span().offset;
379 let end = last.span().offset + last.span().length;
380 Span::new(start, end - start)
381 }
382 _ => self.span(i, rest),
387 };
388 Expr::Array(row_elems, s)
389 })
390 .collect();
391 Ok((rest, row_exprs))
392 }
393
394 fn parse_postfix(&self, i: &'a str) -> IResult<&'a str, Expr> {
397 let (rest, expr) = self.parse_primary(i)?;
398 let rest_ws = multispace0(rest)?.0;
399 if let Some(after) = rest_ws.strip_prefix('%') {
400 return Ok((after, Expr::UnaryOp {
401 op: UnaryOp::Percent,
402 operand: Box::new(expr),
403 span: self.span(i, after),
404 }));
405 }
406 Ok((rest, expr))
407 }
408
409 fn parse_unary(&self, i: &'a str) -> IResult<&'a str, Expr> {
412 let i_ws = multispace0(i)?.0;
413 if let Some(after_minus) = i_ws.strip_prefix('-') {
414 let (rest, operand) = self.parse_unary(after_minus)?;
415 return Ok((rest, Expr::UnaryOp {
416 op: UnaryOp::Neg,
417 operand: Box::new(operand),
418 span: self.span(i_ws, rest),
419 }));
420 }
421 self.parse_postfix(i)
422 }
423
424 fn parse_power(&self, i: &'a str) -> IResult<&'a str, Expr> {
427 let (rest, left) = self.parse_unary(i)?;
428 let rest_ws = multispace0(rest)?.0;
429 if let Some(after_op) = rest_ws.strip_prefix('^') {
430 let (rest2, right) = self.parse_power(after_op)?;
431 return Ok((rest2, Expr::BinaryOp {
432 op: BinaryOp::Pow,
433 left: Box::new(left),
434 right: Box::new(right),
435 span: self.span(i, rest2),
436 }));
437 }
438 Ok((rest, left))
439 }
440
441 fn parse_multiplicative(&self, i: &'a str) -> IResult<&'a str, Expr> {
444 let (mut rest, mut left) = self.parse_power(i)?;
445 loop {
446 let ws = multispace0(rest)?.0;
447 let op = ws.strip_prefix('*').map(|after| (BinaryOp::Mul, after))
448 .or_else(|| ws.strip_prefix('/').map(|after| (BinaryOp::Div, after)));
449 match op {
450 None => break,
451 Some((op, after)) => {
452 let (r, right) = self.parse_power(after)?;
453 left = Expr::BinaryOp {
454 op,
455 span: self.span(i, r),
456 left: Box::new(left),
457 right: Box::new(right),
458 };
459 rest = r;
460 }
461 }
462 }
463 Ok((rest, left))
464 }
465
466 fn parse_additive(&self, i: &'a str) -> IResult<&'a str, Expr> {
469 let (mut rest, mut left) = self.parse_multiplicative(i)?;
470 loop {
471 let ws = multispace0(rest)?.0;
472 let op = ws.strip_prefix('+').map(|after| (BinaryOp::Add, after))
473 .or_else(|| ws.strip_prefix('-').map(|after| (BinaryOp::Sub, after)));
474 match op {
475 None => break,
476 Some((op, after)) => {
477 let (r, right) = self.parse_multiplicative(after)?;
478 left = Expr::BinaryOp {
479 op,
480 span: self.span(i, r),
481 left: Box::new(left),
482 right: Box::new(right),
483 };
484 rest = r;
485 }
486 }
487 }
488 Ok((rest, left))
489 }
490
491 fn parse_concat(&self, i: &'a str) -> IResult<&'a str, Expr> {
494 let (mut rest, mut left) = self.parse_additive(i)?;
495 loop {
496 let ws = multispace0(rest)?.0;
497 if let Some(after) = ws.strip_prefix('&') {
498 let (r, right) = self.parse_additive(after)?;
499 left = Expr::BinaryOp {
500 op: BinaryOp::Concat,
501 span: self.span(i, r),
502 left: Box::new(left),
503 right: Box::new(right),
504 };
505 rest = r;
506 } else {
507 break;
508 }
509 }
510 Ok((rest, left))
511 }
512
513 fn parse_comparison(&self, i: &'a str) -> IResult<&'a str, Expr> {
516 let (rest, left) = self.parse_concat(i)?;
517 let ws = multispace0(rest)?.0;
518
519 let op_result: Option<(BinaryOp, &'a str)> = if let Some(after) = ws.strip_prefix("<>") {
521 Some((BinaryOp::Ne, after))
522 } else if let Some(after) = ws.strip_prefix("<=") {
523 Some((BinaryOp::Le, after))
524 } else if let Some(after) = ws.strip_prefix(">=") {
525 Some((BinaryOp::Ge, after))
526 } else if let Some(after) = ws.strip_prefix('<') {
527 Some((BinaryOp::Lt, after))
528 } else if let Some(after) = ws.strip_prefix('>') {
529 Some((BinaryOp::Gt, after))
530 } else if let Some(after) = ws.strip_prefix('=') {
531 Some((BinaryOp::Eq, after))
532 } else {
533 None
534 };
535
536 if let Some((op, after)) = op_result {
537 let (r, right) = self.parse_concat(after)?;
538 return Ok((r, Expr::BinaryOp {
539 op,
540 span: self.span(i, r),
541 left: Box::new(left),
542 right: Box::new(right),
543 }));
544 }
545
546 Ok((rest, left))
547 }
548}
549
550#[deprecated(since = "0.7.0", note = "use parse_formula() instead — parsing is flavor-independent, so no Engine is required; see ADR 2026-04-27; removal target: 0.7.0 coordinated release")]
557pub fn parse(formula: &str) -> Result<Expr, ParseError> {
558 parse_formula(formula)
559}
560
561pub fn parse_formula(formula: &str) -> Result<Expr, ParseError> {
586 let input = formula.strip_prefix('=').unwrap_or(formula).trim();
587 let p = Parser::new(formula);
588 match p.parse_comparison(input) {
589 Ok((rest, expr)) => {
590 let rest = rest.trim();
591 if rest.is_empty() {
592 Ok(expr)
593 } else {
594 Err(ParseError {
595 message: format!("Unexpected input '{}'", rest),
596 position: offset(formula, rest),
597 })
598 }
599 }
600 Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(ParseError {
601 message: "Parse error".into(),
602 position: offset(formula, e.input),
603 }),
604 Err(nom::Err::Incomplete(_)) => Err(ParseError {
605 message: "Incomplete input".into(),
606 position: formula.len(),
607 }),
608 }
609}
610
611#[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")]
613pub fn validate(formula: &str) -> Result<(), ParseError> {
614 parse_formula(formula).map(|_| ())
615}
616
617#[cfg(test)]
618mod tests;