1use std::collections::BTreeMap;
36
37use serde_json::json;
38
39use crate::error::{EngineError, ErrorCode};
40use crate::limits::Limits;
41use crate::number::Number;
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum UnaryOp {
45 Neg,
46 Pos,
47 Not,
48}
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum BinaryOp {
52 Add,
53 Sub,
54 Mul,
55 Div,
56 Rem,
57 Pow,
58 And,
59 Or,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum CompareOp {
64 Eq,
65 Ne,
66 Lt,
67 Le,
68 Gt,
69 Ge,
70}
71
72#[derive(Clone, Debug, PartialEq)]
73pub struct CallArg {
74 pub name: Option<String>,
75 pub value: Expr,
76}
77
78#[derive(Clone, Debug, PartialEq)]
79pub enum Expr {
80 Number(Number),
81 Text(String),
82 Bool(bool),
83 Ident(String),
84 Array(Vec<Expr>),
85 Record(Vec<(String, Expr)>),
86 Unary {
87 op: UnaryOp,
88 expr: Box<Expr>,
89 },
90 Binary {
91 op: BinaryOp,
92 left: Box<Expr>,
93 right: Box<Expr>,
94 },
95 Compare {
96 operands: Vec<Expr>,
97 ops: Vec<CompareOp>,
98 },
99 Call {
100 name: String,
101 args: Vec<CallArg>,
102 },
103}
104
105impl Expr {
106 pub fn canonical_json(&self) -> serde_json::Value {
108 match self {
109 Expr::Number(number) => json!({
110 "type": "number",
111 "value": serde_json::to_value(number).unwrap_or(serde_json::Value::Null),
112 }),
113 Expr::Text(text) => json!({"type": "text", "value": text}),
114 Expr::Bool(value) => json!({"type": "bool", "value": value}),
115 Expr::Ident(name) => json!({"type": "binding", "name": name}),
116 Expr::Array(items) => json!({
117 "type": "array",
118 "items": items.iter().map(Expr::canonical_json).collect::<Vec<_>>(),
119 }),
120 Expr::Record(fields) => json!({
121 "type": "record",
122 "fields": fields
123 .iter()
124 .map(|(k, v)| json!({"name": k, "value": v.canonical_json()}))
125 .collect::<Vec<_>>(),
126 }),
127 Expr::Unary { op, expr } => json!({
128 "type": "unary",
129 "op": format!("{op:?}").to_lowercase(),
130 "expr": expr.canonical_json(),
131 }),
132 Expr::Binary { op, left, right } => json!({
133 "type": "binary",
134 "op": format!("{op:?}").to_lowercase(),
135 "left": left.canonical_json(),
136 "right": right.canonical_json(),
137 }),
138 Expr::Compare { operands, ops } => json!({
139 "type": "compare",
140 "ops": ops.iter().map(|op| format!("{op:?}").to_lowercase()).collect::<Vec<_>>(),
141 "operands": operands.iter().map(Expr::canonical_json).collect::<Vec<_>>(),
142 }),
143 Expr::Call { name, args } => json!({
144 "type": "call",
145 "name": name,
146 "args": args
147 .iter()
148 .map(|arg| json!({
149 "name": arg.name,
150 "value": arg.value.canonical_json(),
151 }))
152 .collect::<Vec<_>>(),
153 }),
154 }
155 }
156
157 pub fn collect_bindings(&self, out: &mut BTreeMap<String, ()>) {
159 match self {
160 Expr::Ident(name) => {
161 out.insert(name.clone(), ());
162 }
163 Expr::Array(items) => {
164 for item in items {
165 item.collect_bindings(out);
166 }
167 }
168 Expr::Record(fields) => {
169 for (_, value) in fields {
170 value.collect_bindings(out);
171 }
172 }
173 Expr::Unary { expr, .. } => expr.collect_bindings(out),
174 Expr::Binary { left, right, .. } => {
175 left.collect_bindings(out);
176 right.collect_bindings(out);
177 }
178 Expr::Compare { operands, .. } => {
179 for operand in operands {
180 operand.collect_bindings(out);
181 }
182 }
183 Expr::Call { args, .. } => {
184 for arg in args {
185 arg.value.collect_bindings(out);
186 }
187 }
188 Expr::Number(_) | Expr::Text(_) | Expr::Bool(_) => {}
189 }
190 }
191
192 pub fn calls(&self, out: &mut Vec<String>) {
193 match self {
194 Expr::Call { name, args } => {
195 out.push(name.clone());
196 for arg in args {
197 arg.value.calls(out);
198 }
199 }
200 Expr::Array(items) => {
201 for item in items {
202 item.calls(out);
203 }
204 }
205 Expr::Record(fields) => {
206 for (_, value) in fields {
207 value.calls(out);
208 }
209 }
210 Expr::Unary { expr, .. } => expr.calls(out),
211 Expr::Binary { left, right, .. } => {
212 left.calls(out);
213 right.calls(out);
214 }
215 Expr::Compare { operands, .. } => {
216 for operand in operands {
217 operand.calls(out);
218 }
219 }
220 Expr::Number(_) | Expr::Text(_) | Expr::Bool(_) | Expr::Ident(_) => {}
221 }
222 }
223}
224
225#[derive(Clone, Debug, PartialEq)]
226enum Token {
227 Number(String),
228 Text(String),
229 Ident(String),
230 True,
231 False,
232 And,
233 Or,
234 Not,
235 LParen,
236 RParen,
237 LBracket,
238 RBracket,
239 LBrace,
240 RBrace,
241 Comma,
242 Colon,
243 Assign,
244 Dot,
245 Plus,
246 Minus,
247 Star,
248 Slash,
249 Percent,
250 Caret,
251 Eq,
252 Ne,
253 Lt,
254 Le,
255 Gt,
256 Ge,
257 Bang,
258}
259
260struct Lexer<'a> {
261 source: &'a str,
262 bytes: &'a [u8],
263 pos: usize,
264 tokens: Vec<Token>,
265 limits: &'a Limits,
266}
267
268impl<'a> Lexer<'a> {
269 fn new(source: &'a str, limits: &'a Limits) -> Result<Lexer<'a>, EngineError> {
270 if source.len() > limits.max_expression_length {
271 return Err(EngineError::new(
272 ErrorCode::ResourceLimit,
273 format!(
274 "expression length {} exceeds the limit of {}",
275 source.len(),
276 limits.max_expression_length
277 ),
278 ));
279 }
280 Ok(Lexer {
281 source,
282 bytes: source.as_bytes(),
283 pos: 0,
284 tokens: Vec::new(),
285 limits,
286 })
287 }
288
289 fn peek(&self) -> Option<u8> {
290 self.bytes.get(self.pos).copied()
291 }
292
293 fn peek2(&self) -> Option<u8> {
294 self.bytes.get(self.pos + 1).copied()
295 }
296
297 fn skip_trivia(&mut self) -> Result<(), EngineError> {
298 loop {
299 match self.peek() {
300 Some(b' ' | b'\t' | b'\n' | b'\r') => self.pos += 1,
301 Some(b'/') if self.peek2() == Some(b'/') => {
302 while let Some(c) = self.peek() {
303 if c == b'\n' {
304 break;
305 }
306 self.pos += 1;
307 }
308 }
309 Some(b'/') if self.peek2() == Some(b'*') => {
310 self.pos += 2;
311 loop {
312 match (self.peek(), self.peek2()) {
313 (Some(b'*'), Some(b'/')) => {
314 self.pos += 2;
315 break;
316 }
317 (Some(_), _) => self.pos += 1,
318 (None, _) => {
319 return Err(EngineError::malformed(
320 "unterminated block comment in expression",
321 ));
322 }
323 }
324 }
325 }
326 _ => return Ok(()),
327 }
328 }
329 }
330
331 fn push(&mut self, token: Token) -> Result<(), EngineError> {
332 self.tokens.push(token);
333 if self.tokens.len() > self.limits.max_expression_tokens {
334 return Err(EngineError::new(
335 ErrorCode::ResourceLimit,
336 format!(
337 "expression token count exceeds the limit of {}",
338 self.limits.max_expression_tokens
339 ),
340 ));
341 }
342 Ok(())
343 }
344
345 fn lex_number(&mut self) -> Result<(), EngineError> {
346 let start = self.pos;
347 let mut seen_digit = false;
348 while let Some(c) = self.peek() {
349 match c {
350 b'0'..=b'9' => {
351 seen_digit = true;
352 self.pos += 1;
353 }
354 b'.' => {
355 self.pos += 1;
356 }
357 b'e' | b'E' => {
358 self.pos += 1;
359 if matches!(self.peek(), Some(b'+' | b'-')) {
360 self.pos += 1;
361 }
362 }
363 _ => break,
364 }
365 }
366 if !seen_digit {
367 return Err(EngineError::malformed(format!(
368 "invalid number literal at byte {start}"
369 )));
370 }
371 let text = &self.source[start..self.pos];
372 self.push(Token::Number(text.to_string()))
373 }
374
375 fn lex_ident(&mut self) -> Result<(), EngineError> {
376 let start = self.pos;
377 while let Some(c) = self.peek() {
378 if c.is_ascii_alphanumeric() || c == b'_' {
379 self.pos += 1;
380 } else {
381 break;
382 }
383 }
384 let text = &self.source[start..self.pos];
385 let token = match text {
386 "true" => Token::True,
387 "false" => Token::False,
388 "and" => Token::And,
389 "or" => Token::Or,
390 "not" => Token::Not,
391 _ => Token::Ident(text.to_string()),
392 };
393 self.push(token)
394 }
395
396 fn lex_string(&mut self) -> Result<(), EngineError> {
397 self.pos += 1; let mut out = String::new();
399 loop {
400 match self.peek() {
401 None => {
402 return Err(EngineError::malformed(
403 "unterminated string literal in expression",
404 ));
405 }
406 Some(b'"') => {
407 self.pos += 1;
408 break;
409 }
410 Some(b'\\') => {
411 self.pos += 1;
412 let escaped = self.peek().ok_or_else(|| {
413 EngineError::malformed("unterminated escape in string literal")
414 })?;
415 self.pos += 1;
416 match escaped {
417 b'"' => out.push('"'),
418 b'\\' => out.push('\\'),
419 b'/' => out.push('/'),
420 b'n' => out.push('\n'),
421 b't' => out.push('\t'),
422 b'r' => out.push('\r'),
423 b'b' => out.push('\u{0008}'),
424 b'f' => out.push('\u{000C}'),
425 b'u' => {
426 let hex_start = self.pos;
427 if hex_start + 4 > self.bytes.len() {
428 return Err(EngineError::malformed(
429 "invalid unicode escape in string literal",
430 ));
431 }
432 let hex = &self.source[hex_start..hex_start + 4];
433 self.pos += 4;
434 let code = u32::from_str_radix(hex, 16).map_err(|_| {
435 EngineError::malformed("invalid unicode escape in string literal")
436 })?;
437 let ch = char::from_u32(code).ok_or_else(|| {
438 EngineError::malformed("invalid unicode scalar in string literal")
439 })?;
440 out.push(ch);
441 }
442 _ => {
443 return Err(EngineError::malformed(format!(
444 "invalid escape sequence \\{} in string literal",
445 escaped as char
446 )));
447 }
448 }
449 }
450 Some(_) => {
451 let ch = self.source[self.pos..].chars().next().unwrap();
452 self.pos += ch.len_utf8();
453 out.push(ch);
454 }
455 }
456 if out.len() > self.limits.max_string_len {
457 return Err(EngineError::new(
458 ErrorCode::ResourceLimit,
459 "string literal exceeds the string length limit",
460 ));
461 }
462 }
463 self.push(Token::Text(out))
464 }
465
466 fn run(mut self) -> Result<Vec<Token>, EngineError> {
467 loop {
468 self.skip_trivia()?;
469 let Some(c) = self.peek() else { break };
470 match c {
471 b'0'..=b'9' | b'.' => {
472 if c == b'.' && !matches!(self.peek2(), Some(b'0'..=b'9')) {
473 self.pos += 1;
474 self.push(Token::Dot)?;
475 } else {
476 self.lex_number()?;
477 }
478 }
479 b'a'..=b'z' | b'A'..=b'Z' | b'_' => self.lex_ident()?,
480 b'"' => self.lex_string()?,
481 b'(' => {
482 self.pos += 1;
483 self.push(Token::LParen)?;
484 }
485 b')' => {
486 self.pos += 1;
487 self.push(Token::RParen)?;
488 }
489 b'[' => {
490 self.pos += 1;
491 self.push(Token::LBracket)?;
492 }
493 b']' => {
494 self.pos += 1;
495 self.push(Token::RBracket)?;
496 }
497 b'{' => {
498 self.pos += 1;
499 self.push(Token::LBrace)?;
500 }
501 b'}' => {
502 self.pos += 1;
503 self.push(Token::RBrace)?;
504 }
505 b',' => {
506 self.pos += 1;
507 self.push(Token::Comma)?;
508 }
509 b':' => {
510 self.pos += 1;
511 self.push(Token::Colon)?;
512 }
513 b'+' => {
514 self.pos += 1;
515 self.push(Token::Plus)?;
516 }
517 b'-' => {
518 self.pos += 1;
519 self.push(Token::Minus)?;
520 }
521 b'*' => {
522 self.pos += 1;
523 self.push(Token::Star)?;
524 }
525 b'/' => {
526 self.pos += 1;
527 self.push(Token::Slash)?;
528 }
529 b'%' => {
530 self.pos += 1;
531 self.push(Token::Percent)?;
532 }
533 b'^' => {
534 self.pos += 1;
535 self.push(Token::Caret)?;
536 }
537 b'=' => {
538 if self.peek2() == Some(b'=') {
539 self.pos += 2;
540 self.push(Token::Eq)?;
541 } else {
542 self.pos += 1;
543 self.push(Token::Assign)?;
544 }
545 }
546 b'!' => {
547 if self.peek2() == Some(b'=') {
548 self.pos += 2;
549 self.push(Token::Ne)?;
550 } else {
551 self.pos += 1;
552 self.push(Token::Bang)?;
553 }
554 }
555 b'<' => {
556 if self.peek2() == Some(b'=') {
557 self.pos += 2;
558 self.push(Token::Le)?;
559 } else {
560 self.pos += 1;
561 self.push(Token::Lt)?;
562 }
563 }
564 b'>' => {
565 if self.peek2() == Some(b'=') {
566 self.pos += 2;
567 self.push(Token::Ge)?;
568 } else {
569 self.pos += 1;
570 self.push(Token::Gt)?;
571 }
572 }
573 b'&' if self.peek2() == Some(b'&') => {
574 self.pos += 2;
575 self.push(Token::And)?;
576 }
577 b'|' if self.peek2() == Some(b'|') => {
578 self.pos += 2;
579 self.push(Token::Or)?;
580 }
581 other => {
582 return Err(EngineError::malformed(format!(
583 "unexpected character {:?} in expression at byte {}",
584 other as char, self.pos
585 )));
586 }
587 }
588 }
589 Ok(self.tokens)
590 }
591}
592
593struct Parser<'a> {
594 tokens: &'a [Token],
595 pos: usize,
596 limits: &'a Limits,
597 depth: usize,
598}
599
600impl<'a> Parser<'a> {
601 fn new(tokens: &'a [Token], limits: &'a Limits) -> Parser<'a> {
602 Parser {
603 tokens,
604 pos: 0,
605 limits,
606 depth: 0,
607 }
608 }
609
610 fn enter(&mut self) -> Result<(), EngineError> {
611 self.depth += 1;
612 if self.depth > self.limits.max_ast_depth {
613 return Err(EngineError::new(
614 ErrorCode::ResourceLimit,
615 format!(
616 "expression nesting depth exceeds the limit of {}",
617 self.limits.max_ast_depth
618 ),
619 ));
620 }
621 Ok(())
622 }
623
624 fn exit(&mut self) {
625 self.depth -= 1;
626 }
627
628 fn peek(&self) -> Option<&Token> {
629 self.tokens.get(self.pos)
630 }
631
632 fn next(&mut self) -> Option<Token> {
633 let token = self.tokens.get(self.pos).cloned();
634 if token.is_some() {
635 self.pos += 1;
636 }
637 token
638 }
639
640 fn expect(&mut self, token: &Token, what: &str) -> Result<(), EngineError> {
641 match self.next() {
642 Some(found) if &found == token => Ok(()),
643 Some(found) => Err(EngineError::malformed(format!(
644 "expected {what}, found {found:?}"
645 ))),
646 None => Err(EngineError::malformed(format!(
647 "expected {what}, found end of expression"
648 ))),
649 }
650 }
651
652 fn parse(&mut self) -> Result<Expr, EngineError> {
653 let expr = self.parse_or()?;
654 if self.pos != self.tokens.len() {
655 return Err(EngineError::malformed(format!(
656 "unexpected token {:?} after expression",
657 self.tokens[self.pos]
658 )));
659 }
660 Ok(expr)
661 }
662
663 fn parse_or(&mut self) -> Result<Expr, EngineError> {
664 let mut left = self.parse_and()?;
665 while matches!(self.peek(), Some(Token::Or)) {
666 self.next();
667 let right = self.parse_and()?;
668 left = Expr::Binary {
669 op: BinaryOp::Or,
670 left: Box::new(left),
671 right: Box::new(right),
672 };
673 }
674 Ok(left)
675 }
676
677 fn parse_and(&mut self) -> Result<Expr, EngineError> {
678 let mut left = self.parse_not()?;
679 while matches!(self.peek(), Some(Token::And)) {
680 self.next();
681 let right = self.parse_not()?;
682 left = Expr::Binary {
683 op: BinaryOp::And,
684 left: Box::new(left),
685 right: Box::new(right),
686 };
687 }
688 Ok(left)
689 }
690
691 fn parse_not(&mut self) -> Result<Expr, EngineError> {
692 if matches!(self.peek(), Some(Token::Not | Token::Bang)) {
693 self.next();
694 self.enter()?;
695 let expr = self.parse_not();
696 self.exit();
697 return Ok(Expr::Unary {
698 op: UnaryOp::Not,
699 expr: Box::new(expr?),
700 });
701 }
702 self.parse_comparison()
703 }
704
705 fn parse_comparison(&mut self) -> Result<Expr, EngineError> {
706 let first = self.parse_additive()?;
707 let mut operands = vec![first];
708 let mut ops = Vec::new();
709 loop {
710 let op = match self.peek() {
711 Some(Token::Eq) => CompareOp::Eq,
712 Some(Token::Ne) => CompareOp::Ne,
713 Some(Token::Lt) => CompareOp::Lt,
714 Some(Token::Le) => CompareOp::Le,
715 Some(Token::Gt) => CompareOp::Gt,
716 Some(Token::Ge) => CompareOp::Ge,
717 _ => break,
718 };
719 self.next();
720 let right = self.parse_additive()?;
721 operands.push(right);
722 ops.push(op);
723 }
724 if ops.is_empty() {
725 Ok(operands.pop().unwrap())
726 } else {
727 Ok(Expr::Compare { operands, ops })
728 }
729 }
730
731 fn parse_additive(&mut self) -> Result<Expr, EngineError> {
732 let mut left = self.parse_multiplicative()?;
733 loop {
734 let op = match self.peek() {
735 Some(Token::Plus) => BinaryOp::Add,
736 Some(Token::Minus) => BinaryOp::Sub,
737 _ => break,
738 };
739 self.next();
740 let right = self.parse_multiplicative()?;
741 left = Expr::Binary {
742 op,
743 left: Box::new(left),
744 right: Box::new(right),
745 };
746 }
747 Ok(left)
748 }
749
750 fn parse_multiplicative(&mut self) -> Result<Expr, EngineError> {
751 let mut left = self.parse_unary()?;
752 loop {
753 let op = match self.peek() {
754 Some(Token::Star) => BinaryOp::Mul,
755 Some(Token::Slash) => BinaryOp::Div,
756 Some(Token::Percent) => BinaryOp::Rem,
757 _ => break,
758 };
759 self.next();
760 let right = self.parse_unary()?;
761 left = Expr::Binary {
762 op,
763 left: Box::new(left),
764 right: Box::new(right),
765 };
766 }
767 Ok(left)
768 }
769
770 fn parse_unary(&mut self) -> Result<Expr, EngineError> {
771 match self.peek() {
772 Some(Token::Minus) => {
773 self.next();
774 self.enter()?;
775 let expr = self.parse_unary();
776 self.exit();
777 Ok(Expr::Unary {
778 op: UnaryOp::Neg,
779 expr: Box::new(expr?),
780 })
781 }
782 Some(Token::Plus) => {
783 self.next();
784 self.enter()?;
785 let expr = self.parse_unary();
786 self.exit();
787 Ok(Expr::Unary {
788 op: UnaryOp::Pos,
789 expr: Box::new(expr?),
790 })
791 }
792 _ => self.parse_power(),
793 }
794 }
795
796 fn parse_power(&mut self) -> Result<Expr, EngineError> {
797 let base = self.parse_primary()?;
798 if matches!(self.peek(), Some(Token::Caret)) {
799 self.next();
800 self.enter()?;
801 let exponent = self.parse_unary();
802 self.exit();
803 Ok(Expr::Binary {
804 op: BinaryOp::Pow,
805 left: Box::new(base),
806 right: Box::new(exponent?),
807 })
808 } else {
809 Ok(base)
810 }
811 }
812
813 fn parse_primary(&mut self) -> Result<Expr, EngineError> {
814 let token = self
815 .next()
816 .ok_or_else(|| EngineError::malformed("unexpected end of expression"))?;
817 match token {
818 Token::Number(text) => {
819 let number = Number::parse_literal(&text, self.limits)?;
820 Ok(Expr::Number(number))
821 }
822 Token::Text(text) => Ok(Expr::Text(text)),
823 Token::True => Ok(Expr::Bool(true)),
824 Token::False => Ok(Expr::Bool(false)),
825 Token::Ident(name) => {
826 let mut qualified = name;
828 let mut saw_dot = false;
829 while matches!(self.peek(), Some(Token::Dot)) {
830 self.next();
831 match self.next() {
832 Some(Token::Ident(part)) => {
833 qualified.push('.');
834 qualified.push_str(&part);
835 saw_dot = true;
836 }
837 other => {
838 return Err(EngineError::malformed(format!(
839 "expected identifier after '.', found {other:?}"
840 )));
841 }
842 }
843 }
844 if matches!(self.peek(), Some(Token::LParen)) {
845 self.next();
846 let args = self.parse_call_args()?;
847 Ok(Expr::Call {
848 name: qualified,
849 args,
850 })
851 } else if saw_dot {
852 Err(EngineError::malformed(format!(
853 "qualified name {qualified:?} must be called as a function"
854 )))
855 } else {
856 Ok(Expr::Ident(qualified))
857 }
858 }
859 Token::LParen => {
860 self.enter()?;
861 let expr = self.parse_or();
862 self.exit();
863 let expr = expr?;
864 self.expect(&Token::RParen, "')'")?;
865 Ok(expr)
866 }
867 Token::LBracket => {
868 self.enter()?;
869 let mut items = Vec::new();
870 if !matches!(self.peek(), Some(Token::RBracket)) {
871 loop {
872 items.push(self.parse_or()?);
873 if matches!(self.peek(), Some(Token::Comma)) {
874 self.next();
875 if matches!(self.peek(), Some(Token::RBracket)) {
876 break;
877 }
878 } else {
879 break;
880 }
881 }
882 }
883 self.exit();
884 self.expect(&Token::RBracket, "']'")?;
885 if items.len() > self.limits.max_array_len {
886 return Err(EngineError::new(
887 ErrorCode::ResourceLimit,
888 format!(
889 "array literal length {} exceeds the limit of {}",
890 items.len(),
891 self.limits.max_array_len
892 ),
893 ));
894 }
895 Ok(Expr::Array(items))
896 }
897 Token::LBrace => {
898 self.enter()?;
899 let mut fields = Vec::new();
900 if !matches!(self.peek(), Some(Token::RBrace)) {
901 loop {
902 let key = match self.next() {
903 Some(Token::Ident(name)) => name,
904 Some(Token::Text(text)) => text,
905 other => {
906 return Err(EngineError::malformed(format!(
907 "expected a record key, found {other:?}"
908 )));
909 }
910 };
911 self.expect(&Token::Colon, "':'")?;
912 let value = self.parse_or()?;
913 fields.push((key, value));
914 if matches!(self.peek(), Some(Token::Comma)) {
915 self.next();
916 if matches!(self.peek(), Some(Token::RBrace)) {
917 break;
918 }
919 } else {
920 break;
921 }
922 }
923 }
924 self.exit();
925 self.expect(&Token::RBrace, "'}'")?;
926 Ok(Expr::Record(fields))
927 }
928 other => Err(EngineError::malformed(format!(
929 "unexpected token {other:?} in expression"
930 ))),
931 }
932 }
933
934 fn parse_call_args(&mut self) -> Result<Vec<CallArg>, EngineError> {
935 let mut args = Vec::new();
936 let mut seen_named = false;
937 if matches!(self.peek(), Some(Token::RParen)) {
938 self.next();
939 return Ok(args);
940 }
941 loop {
942 self.enter()?;
943 let parsed = (|| -> Result<CallArg, EngineError> {
944 if let (Some(Token::Ident(name)), Some(Token::Assign)) =
946 (self.tokens.get(self.pos), self.tokens.get(self.pos + 1))
947 {
948 let name = name.clone();
949 self.pos += 2;
950 seen_named = true;
951 let value = self.parse_or()?;
952 Ok(CallArg {
953 name: Some(name),
954 value,
955 })
956 } else {
957 if seen_named {
958 return Err(EngineError::malformed(
959 "positional arguments must precede named arguments",
960 ));
961 }
962 let value = self.parse_or()?;
963 Ok(CallArg { name: None, value })
964 }
965 })();
966 self.exit();
967 args.push(parsed?);
968 if args.len() > 256 {
969 return Err(EngineError::new(
970 ErrorCode::ResourceLimit,
971 "too many call arguments",
972 ));
973 }
974 match self.next() {
975 Some(Token::Comma) => continue,
976 Some(Token::RParen) => break,
977 other => {
978 return Err(EngineError::malformed(format!(
979 "expected ',' or ')' in argument list, found {other:?}"
980 )));
981 }
982 }
983 }
984 Ok(args)
985 }
986}
987
988pub fn parse_expression(source: &str, limits: &Limits) -> Result<Expr, EngineError> {
990 let tokens = Lexer::new(source, limits)?.run()?;
991 if tokens.is_empty() {
992 return Err(EngineError::malformed("empty expression"));
993 }
994 Parser::new(&tokens, limits).parse()
995}
996
997#[cfg(test)]
998mod tests {
999 use super::*;
1000
1001 fn parse(source: &str) -> Expr {
1002 parse_expression(source, &Limits::conservative()).unwrap()
1003 }
1004
1005 #[test]
1006 fn precedence_of_unary_minus_and_power() {
1007 let expr = parse("-2^2");
1008 match expr {
1009 Expr::Unary {
1010 op: UnaryOp::Neg,
1011 expr,
1012 } => match *expr {
1013 Expr::Binary {
1014 op: BinaryOp::Pow, ..
1015 } => {}
1016 other => panic!("expected power, got {other:?}"),
1017 },
1018 other => panic!("expected negation, got {other:?}"),
1019 }
1020 }
1021
1022 #[test]
1023 fn power_is_right_associative() {
1024 let expr = parse("2^3^2");
1025 match expr {
1026 Expr::Binary {
1027 op: BinaryOp::Pow,
1028 right,
1029 ..
1030 } => match *right {
1031 Expr::Binary {
1032 op: BinaryOp::Pow, ..
1033 } => {}
1034 other => panic!("expected nested power, got {other:?}"),
1035 },
1036 other => panic!("expected power, got {other:?}"),
1037 }
1038 }
1039
1040 #[test]
1041 fn chained_comparison_parses() {
1042 let expr = parse("0 < x < 1");
1043 match expr {
1044 Expr::Compare { operands, ops } => {
1045 assert_eq!(operands.len(), 3);
1046 assert_eq!(ops, vec![CompareOp::Lt, CompareOp::Lt]);
1047 }
1048 other => panic!("expected comparison, got {other:?}"),
1049 }
1050 }
1051
1052 #[test]
1053 fn qualified_call_with_named_arguments() {
1054 let expr = parse("finance.npv(rate = 0.08, cashflows = [-10000, 4000])");
1055 match expr {
1056 Expr::Call { name, args } => {
1057 assert_eq!(name, "finance.npv");
1058 assert_eq!(args.len(), 2);
1059 assert_eq!(args[0].name.as_deref(), Some("rate"));
1060 }
1061 other => panic!("expected call, got {other:?}"),
1062 }
1063 }
1064
1065 #[test]
1066 fn unqualified_call_is_parsed_and_rejected_later() {
1067 let expr = parse("sin(1)");
1068 match expr {
1069 Expr::Call { name, .. } => assert_eq!(name, "sin"),
1070 other => panic!("expected call, got {other:?}"),
1071 }
1072 }
1073
1074 #[test]
1075 fn depth_limit_is_enforced() {
1076 let mut limits = Limits::conservative();
1077 limits.max_ast_depth = 8;
1078 let source = "((((((((((1))))))))))";
1079 assert!(parse_expression(source, &limits).is_err());
1080 }
1081
1082 #[test]
1083 fn record_and_array_literals() {
1084 let expr = parse("{a: 1, b: [2, 3,]}");
1085 match expr {
1086 Expr::Record(fields) => assert_eq!(fields.len(), 2),
1087 other => panic!("expected record, got {other:?}"),
1088 }
1089 }
1090
1091 #[test]
1092 fn canonical_json_is_stable() {
1093 let a = parse("1 + 2 * 3");
1094 let b = parse("1+2*3");
1095 assert_eq!(a.canonical_json(), b.canonical_json());
1096 }
1097}