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 i.starts_with('\'') {
100 return self.parse_quoted_sheet_ref(i);
101 }
102
103 if let Ok((rest, span)) = dollar_cell_ref(i) {
109 let rest_ws = multispace0(rest)?.0;
110 if let Some(after_colon) = rest_ws.strip_prefix(':') {
111 if let Ok((rest2, end_span)) = cell_ref_text(after_colon) {
112 if CellAddr::parse(end_span).is_some() {
113 let range_name = format!("{}:{}", span, end_span);
114 return Ok((rest2, Expr::Variable(range_name, self.span(i, rest2))));
115 }
116 }
117 }
118 return Ok((rest, Expr::Variable(span.to_string(), self.span(i, rest))));
119 }
120
121 if let Ok((rest, name)) = identifier(i) {
123 if let Some(after_bang) = rest.strip_prefix('!') {
126 return self.parse_ref_body(i, name.to_string(), after_bang);
127 }
128 let rest_ws = multispace0(rest)?.0;
129 if let Some(args_input) = rest_ws.strip_prefix('(') {
130 let (rest2, args) = self.parse_arg_list(args_input)?;
132 let rest2 = multispace0(rest2)?.0;
133 if let Some(after_close) = rest2.strip_prefix(')') {
134 let func_expr = Expr::FunctionCall {
135 name: name.to_uppercase(),
136 args,
137 span: self.span(i, after_close),
138 };
139 let after_ws = multispace0(after_close)?.0;
141 if let Some(call_input) = after_ws.strip_prefix('(') {
142 let (rest3, call_args) = self.parse_arg_list(call_input)?;
143 let rest3 = multispace0(rest3)?.0;
144 if let Some(after) = rest3.strip_prefix(')') {
145 return Ok((after, Expr::Apply {
146 func: Box::new(func_expr),
147 call_args,
148 span: self.span(i, after),
149 }));
150 }
151 return Err(nom::Err::Error(nom::error::Error::new(
152 rest3,
153 nom::error::ErrorKind::Char,
154 )));
155 }
156 return Ok((after_close, func_expr));
157 }
158 return Err(nom::Err::Error(nom::error::Error::new(
159 rest2,
160 nom::error::ErrorKind::Char,
161 )));
162 }
163 if CellAddr::parse(name).is_some() {
167 if let Some(after_colon) = rest_ws.strip_prefix(':') {
168 if let Ok((rest2, name2)) = cell_ref_text(after_colon) {
169 if CellAddr::parse(name2).is_some() {
170 let range_name = format!("{}:{}", name, name2);
171 return Ok((rest2, Expr::Variable(range_name, self.span(i, rest2))));
172 }
173 }
174 }
175 }
176 return Ok((rest, Expr::Variable(name.to_string(), self.span(i, rest))));
177 }
178
179 Err(nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Alt)))
180 }
181
182 fn parse_ref_body(&self, start: &'a str, sheet: String, i: &'a str) -> IResult<&'a str, Expr> {
188 let err = || nom::Err::Error(nom::error::Error::new(i, nom::error::ErrorKind::Tag));
189 let (rest, cell_text) = cell_ref_text(i).map_err(|_| err())?;
190 let addr = CellAddr::parse(cell_text).ok_or_else(err)?;
191 let rest_ws = multispace0(rest)?.0;
193 if let Some(after_colon) = rest_ws.strip_prefix(':') {
194 if let Ok((rest2, end_text)) = cell_ref_text(after_colon) {
195 if let Some(end) = CellAddr::parse(end_text) {
196 let r = Ref::Range { sheet: Some(sheet), start: addr, end };
197 return Ok((rest2, Expr::Reference(r, self.span(start, rest2))));
198 }
199 }
200 }
201 let r = Ref::Cell { sheet: Some(sheet), addr };
202 Ok((rest, Expr::Reference(r, self.span(start, rest))))
203 }
204
205 fn parse_quoted_sheet_ref(&self, i: &'a str) -> IResult<&'a str, Expr> {
208 let inner = &i[1..];
209 let mut sheet = String::new();
210 let mut idx = 0;
211 loop {
212 match inner[idx..].find('\'') {
213 None => {
215 return Err(nom::Err::Error(nom::error::Error::new(
216 i,
217 nom::error::ErrorKind::Char,
218 )));
219 }
220 Some(q) => {
221 sheet.push_str(&inner[idx..idx + q]);
222 let after = idx + q + 1;
223 if inner[after..].starts_with('\'') {
224 sheet.push('\'');
225 idx = after + 1;
226 } else {
227 idx = after;
228 break;
229 }
230 }
231 }
232 }
233 let rest = &inner[idx..];
234 if sheet.is_empty() {
235 return Err(nom::Err::Error(nom::error::Error::new(
236 i,
237 nom::error::ErrorKind::Char,
238 )));
239 }
240 match rest.strip_prefix('!') {
241 Some(after_bang) => self.parse_ref_body(i, sheet, after_bang),
242 None => Err(nom::Err::Error(nom::error::Error::new(
243 rest,
244 nom::error::ErrorKind::Char,
245 ))),
246 }
247 }
248
249 fn parse_arg_list(&self, i: &'a str) -> IResult<&'a str, Vec<Expr>> {
250 let mut args = Vec::new();
251 let mut rest = multispace0(i)?.0;
252
253 if rest.starts_with(')') {
254 return Ok((rest, args));
255 }
256
257 let ws = multispace0(rest)?.0;
259 if ws.starts_with(',') || ws.starts_with(')') {
260 args.push(Expr::Variable(String::new(), Span::new(0, 0)));
262 } else {
263 let (r, first) = self.parse_comparison(rest)?;
264 args.push(first);
265 rest = r;
266 }
267
268 loop {
269 rest = multispace0(rest)?.0;
270 if let Some(after_comma) = rest.strip_prefix(',') {
271 let after_ws = multispace0(after_comma)?.0;
272 if after_ws.starts_with(',') || after_ws.starts_with(')') {
273 args.push(Expr::Variable(String::new(), Span::new(0, 0)));
275 rest = after_comma;
276 } else {
277 let (r, arg) = self.parse_comparison(after_ws)?;
284 args.push(arg);
285 rest = r;
286 }
287 } else {
288 break;
289 }
290 }
291
292 Ok((rest, args))
293 }
294
295 fn parse_array_elements(&self, i: &'a str) -> IResult<&'a str, Vec<Expr>> {
296 let mut rows: Vec<Vec<Expr>> = Vec::new();
297 let mut current_row: Vec<Expr> = Vec::new();
298 let mut rest = multispace0(i)?.0;
299 if rest.starts_with('}') {
300 return Ok((rest, Vec::new())); }
302 let (r, first) = self.parse_comparison(rest)?;
303 current_row.push(first);
304 rest = r;
305 loop {
306 rest = multispace0(rest)?.0;
307 if let Some(after_comma) = rest.strip_prefix(',') {
308 let after_ws = multispace0(after_comma)?.0;
315 let (r, elem) = self.parse_comparison(after_ws)?;
316 current_row.push(elem);
317 rest = r;
318 } else if let Some(after_semi) = rest.strip_prefix(';') {
319 rows.push(std::mem::take(&mut current_row));
320 let after_ws = multispace0(after_semi)?.0;
323 let (r, elem) = self.parse_comparison(after_ws)?;
324 current_row.push(elem);
325 rest = r;
326 } else {
327 break;
328 }
329 }
330 rows.push(current_row);
331 if rows.len() == 1 {
333 return Ok((rest, rows.into_iter().next().unwrap()));
334 }
335 let row_exprs: Vec<Expr> = rows
341 .into_iter()
342 .map(|row_elems| {
343 let s = match (row_elems.first(), row_elems.last()) {
344 (Some(first), Some(last)) => {
345 let start = first.span().offset;
346 let end = last.span().offset + last.span().length;
347 Span::new(start, end - start)
348 }
349 _ => self.span(i, rest),
354 };
355 Expr::Array(row_elems, s)
356 })
357 .collect();
358 Ok((rest, row_exprs))
359 }
360
361 fn parse_postfix(&self, i: &'a str) -> IResult<&'a str, Expr> {
364 let (rest, expr) = self.parse_primary(i)?;
365 let rest_ws = multispace0(rest)?.0;
366 if let Some(after) = rest_ws.strip_prefix('%') {
367 return Ok((after, Expr::UnaryOp {
368 op: UnaryOp::Percent,
369 operand: Box::new(expr),
370 span: self.span(i, after),
371 }));
372 }
373 Ok((rest, expr))
374 }
375
376 fn parse_unary(&self, i: &'a str) -> IResult<&'a str, Expr> {
379 let i_ws = multispace0(i)?.0;
380 if let Some(after_minus) = i_ws.strip_prefix('-') {
381 let (rest, operand) = self.parse_unary(after_minus)?;
382 return Ok((rest, Expr::UnaryOp {
383 op: UnaryOp::Neg,
384 operand: Box::new(operand),
385 span: self.span(i_ws, rest),
386 }));
387 }
388 self.parse_postfix(i)
389 }
390
391 fn parse_power(&self, i: &'a str) -> IResult<&'a str, Expr> {
394 let (rest, left) = self.parse_unary(i)?;
395 let rest_ws = multispace0(rest)?.0;
396 if let Some(after_op) = rest_ws.strip_prefix('^') {
397 let (rest2, right) = self.parse_power(after_op)?;
398 return Ok((rest2, Expr::BinaryOp {
399 op: BinaryOp::Pow,
400 left: Box::new(left),
401 right: Box::new(right),
402 span: self.span(i, rest2),
403 }));
404 }
405 Ok((rest, left))
406 }
407
408 fn parse_multiplicative(&self, i: &'a str) -> IResult<&'a str, Expr> {
411 let (mut rest, mut left) = self.parse_power(i)?;
412 loop {
413 let ws = multispace0(rest)?.0;
414 let op = ws.strip_prefix('*').map(|after| (BinaryOp::Mul, after))
415 .or_else(|| ws.strip_prefix('/').map(|after| (BinaryOp::Div, after)));
416 match op {
417 None => break,
418 Some((op, after)) => {
419 let (r, right) = self.parse_power(after)?;
420 left = Expr::BinaryOp {
421 op,
422 span: self.span(i, r),
423 left: Box::new(left),
424 right: Box::new(right),
425 };
426 rest = r;
427 }
428 }
429 }
430 Ok((rest, left))
431 }
432
433 fn parse_additive(&self, i: &'a str) -> IResult<&'a str, Expr> {
436 let (mut rest, mut left) = self.parse_multiplicative(i)?;
437 loop {
438 let ws = multispace0(rest)?.0;
439 let op = ws.strip_prefix('+').map(|after| (BinaryOp::Add, after))
440 .or_else(|| ws.strip_prefix('-').map(|after| (BinaryOp::Sub, after)));
441 match op {
442 None => break,
443 Some((op, after)) => {
444 let (r, right) = self.parse_multiplicative(after)?;
445 left = Expr::BinaryOp {
446 op,
447 span: self.span(i, r),
448 left: Box::new(left),
449 right: Box::new(right),
450 };
451 rest = r;
452 }
453 }
454 }
455 Ok((rest, left))
456 }
457
458 fn parse_concat(&self, i: &'a str) -> IResult<&'a str, Expr> {
461 let (mut rest, mut left) = self.parse_additive(i)?;
462 loop {
463 let ws = multispace0(rest)?.0;
464 if let Some(after) = ws.strip_prefix('&') {
465 let (r, right) = self.parse_additive(after)?;
466 left = Expr::BinaryOp {
467 op: BinaryOp::Concat,
468 span: self.span(i, r),
469 left: Box::new(left),
470 right: Box::new(right),
471 };
472 rest = r;
473 } else {
474 break;
475 }
476 }
477 Ok((rest, left))
478 }
479
480 fn parse_comparison(&self, i: &'a str) -> IResult<&'a str, Expr> {
483 let (rest, left) = self.parse_concat(i)?;
484 let ws = multispace0(rest)?.0;
485
486 let op_result: Option<(BinaryOp, &'a str)> = if let Some(after) = ws.strip_prefix("<>") {
488 Some((BinaryOp::Ne, after))
489 } else if let Some(after) = ws.strip_prefix("<=") {
490 Some((BinaryOp::Le, after))
491 } else if let Some(after) = ws.strip_prefix(">=") {
492 Some((BinaryOp::Ge, after))
493 } else if let Some(after) = ws.strip_prefix('<') {
494 Some((BinaryOp::Lt, after))
495 } else if let Some(after) = ws.strip_prefix('>') {
496 Some((BinaryOp::Gt, after))
497 } else if let Some(after) = ws.strip_prefix('=') {
498 Some((BinaryOp::Eq, after))
499 } else {
500 None
501 };
502
503 if let Some((op, after)) = op_result {
504 let (r, right) = self.parse_concat(after)?;
505 return Ok((r, Expr::BinaryOp {
506 op,
507 span: self.span(i, r),
508 left: Box::new(left),
509 right: Box::new(right),
510 }));
511 }
512
513 Ok((rest, left))
514 }
515}
516
517#[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")]
524pub fn parse(formula: &str) -> Result<Expr, ParseError> {
525 parse_formula(formula)
526}
527
528pub(crate) fn parse_formula(formula: &str) -> Result<Expr, ParseError> {
530 let input = formula.strip_prefix('=').unwrap_or(formula).trim();
531 let p = Parser::new(formula);
532 match p.parse_comparison(input) {
533 Ok((rest, expr)) => {
534 let rest = rest.trim();
535 if rest.is_empty() {
536 Ok(expr)
537 } else {
538 Err(ParseError {
539 message: format!("Unexpected input '{}'", rest),
540 position: offset(formula, rest),
541 })
542 }
543 }
544 Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(ParseError {
545 message: "Parse error".into(),
546 position: offset(formula, e.input),
547 }),
548 Err(nom::Err::Incomplete(_)) => Err(ParseError {
549 message: "Incomplete input".into(),
550 position: formula.len(),
551 }),
552 }
553}
554
555#[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")]
557pub fn validate(formula: &str) -> Result<(), ParseError> {
558 parse_formula(formula).map(|_| ())
559}
560
561#[cfg(test)]
562mod tests;