1use std::collections::HashSet;
31use std::fmt;
32
33#[derive(Debug, Clone, Copy, PartialEq)]
39pub enum Value {
40 Int(i64),
42 Float(f64),
44}
45
46impl Value {
47 pub fn as_i64(self) -> i64 {
49 match self {
50 Value::Int(value) => value,
51 Value::Float(value) => value as i64,
52 }
53 }
54
55 pub fn as_f64(self) -> f64 {
57 match self {
58 Value::Int(value) => value as f64,
59 Value::Float(value) => value,
60 }
61 }
62
63 pub fn is_int(self) -> bool {
65 matches!(self, Value::Int(_))
66 }
67
68 pub fn is_truthy(self) -> bool {
70 match self {
71 Value::Int(value) => value != 0,
72 Value::Float(value) => value != 0.0,
73 }
74 }
75
76 fn from_bool(value: bool) -> Self {
77 Value::Int(i64::from(value))
78 }
79}
80
81impl fmt::Display for Value {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 Value::Int(value) => write!(f, "{value}"),
85 Value::Float(value) => write!(f, "{value}"),
86 }
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
96pub enum EvalMode {
97 Integer,
99 #[default]
101 Float,
102}
103
104#[derive(Debug, Clone)]
106pub enum AstNode {
107 Literal(Value),
109 Variable(String),
111 Unary {
113 op: UnaryOp,
115 expr: Box<AstNode>,
117 },
118 Binary {
120 op: BinaryOp,
122 left: Box<AstNode>,
124 right: Box<AstNode>,
126 },
127 Ternary {
129 cond: Box<AstNode>,
131 then_expr: Box<AstNode>,
133 else_expr: Box<AstNode>,
135 },
136 FnCall {
138 name: String,
140 args: Vec<AstNode>,
142 },
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum BinaryOp {
148 Add,
150 Sub,
151 Mul,
152 Div,
153 Mod,
154 Pow,
155 Lt,
157 Le,
158 Gt,
159 Ge,
160 Eq,
161 Ne,
162 And,
164 Or,
165 BitAnd,
167 BitOr,
168 BitXor,
169 Shl,
170 Shr,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
175pub enum UnaryOp {
176 Plus,
177 Minus,
178 Not,
179 BitNot,
180}
181
182#[derive(Debug, Clone)]
184pub struct ParseError {
185 msg: String,
186 offset: usize,
187}
188
189impl ParseError {
190 fn new<S: Into<String>>(msg: S, offset: usize) -> Self {
191 Self {
192 msg: msg.into(),
193 offset,
194 }
195 }
196
197 pub fn offset(&self) -> usize {
199 self.offset
200 }
201}
202
203impl fmt::Display for ParseError {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 write!(f, "{} (at offset {})", self.msg, self.offset)
206 }
207}
208
209impl std::error::Error for ParseError {}
210
211#[derive(Debug, Clone)]
213pub enum EvalError {
214 UnknownVariable(String),
216 DivisionByZero,
218 UnknownFunction(String),
220 ArityMismatch {
222 name: String,
223 expected: usize,
224 got: usize,
225 },
226}
227
228impl fmt::Display for EvalError {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 match self {
231 EvalError::UnknownVariable(var) => write!(f, "unknown variable {var}"),
232 EvalError::DivisionByZero => write!(f, "division by zero"),
233 EvalError::UnknownFunction(name) => write!(f, "unknown function {name}"),
234 EvalError::ArityMismatch {
235 name,
236 expected,
237 got,
238 } => {
239 write!(f, "function {name} expects {expected} args, got {got}")
240 }
241 }
242 }
243}
244
245impl std::error::Error for EvalError {}
246
247pub fn parse_expression(input: &str) -> Result<AstNode, ParseError> {
249 let mut parser = Parser::new(input)?;
250 let expr = parser.parse_ternary()?;
251 if !matches!(parser.lookahead, Token::End) {
252 return Err(ParseError::new("unexpected trailing tokens", parser.pos));
253 }
254 Ok(expr)
255}
256
257pub fn builtin_constant(name: &str) -> Option<Value> {
263 match name.to_ascii_lowercase().as_str() {
264 "e" => Some(Value::Float(std::f64::consts::E)),
265 "pi" => Some(Value::Float(std::f64::consts::PI)),
266 _ => None,
267 }
268}
269
270pub fn is_builtin_constant(name: &str) -> bool {
275 builtin_constant(name).is_some()
276}
277
278pub fn evaluate(
285 ast: &AstNode,
286 vars: &mut dyn FnMut(&str) -> Result<Value, EvalError>,
287 mode: EvalMode,
288) -> Result<Value, EvalError> {
289 match ast {
290 AstNode::Literal(value) => Ok(*value),
291 AstNode::Variable(name) => match vars(name) {
292 Ok(value) => Ok(value),
293 Err(EvalError::UnknownVariable(_)) => {
294 builtin_constant(name).ok_or_else(|| EvalError::UnknownVariable(name.clone()))
295 }
296 Err(other) => Err(other),
297 },
298 AstNode::Unary { op, expr } => {
299 let inner = evaluate(expr, vars, mode)?;
300 Ok(eval_unary(*op, inner, mode))
301 }
302 AstNode::Binary { op, left, right } => {
303 match op {
305 BinaryOp::And => {
306 let lhs = evaluate(left, vars, mode)?;
307 if !lhs.is_truthy() {
308 return Ok(Value::from_bool(false));
309 }
310 let rhs = evaluate(right, vars, mode)?;
311 Ok(Value::from_bool(rhs.is_truthy()))
312 }
313 BinaryOp::Or => {
314 let lhs = evaluate(left, vars, mode)?;
315 if lhs.is_truthy() {
316 return Ok(Value::from_bool(true));
317 }
318 let rhs = evaluate(right, vars, mode)?;
319 Ok(Value::from_bool(rhs.is_truthy()))
320 }
321 _ => {
322 let lhs = evaluate(left, vars, mode)?;
323 let rhs = evaluate(right, vars, mode)?;
324 eval_binary(*op, lhs, rhs, mode)
325 }
326 }
327 }
328 AstNode::Ternary {
329 cond,
330 then_expr,
331 else_expr,
332 } => {
333 if evaluate(cond, vars, mode)?.is_truthy() {
334 evaluate(then_expr, vars, mode)
335 } else {
336 evaluate(else_expr, vars, mode)
337 }
338 }
339 AstNode::FnCall { name, args } => {
340 let evaluated: Result<Vec<Value>, _> =
341 args.iter().map(|a| evaluate(a, vars, mode)).collect();
342 let arg_vals = evaluated?;
343 eval_function(name, &arg_vals, mode)
344 }
345 }
346}
347
348fn eval_unary(op: UnaryOp, value: Value, mode: EvalMode) -> Value {
349 match op {
350 UnaryOp::Plus => value,
351 UnaryOp::Minus => {
352 if mode == EvalMode::Integer || value.is_int() {
353 Value::Int(value.as_i64().wrapping_neg())
354 } else {
355 Value::Float(-value.as_f64())
356 }
357 }
358 UnaryOp::Not => Value::from_bool(!value.is_truthy()),
359 UnaryOp::BitNot => Value::Int(!value.as_i64()),
360 }
361}
362
363fn integral(mode: EvalMode, lhs: Value, rhs: Value) -> bool {
365 mode == EvalMode::Integer || (lhs.is_int() && rhs.is_int())
366}
367
368fn shift_amount(value: Value) -> u32 {
372 (value.as_i64() as u64 & 63) as u32
373}
374
375fn eval_binary(op: BinaryOp, lhs: Value, rhs: Value, mode: EvalMode) -> Result<Value, EvalError> {
376 Ok(match op {
377 BinaryOp::Add => {
378 if integral(mode, lhs, rhs) {
379 Value::Int(lhs.as_i64().wrapping_add(rhs.as_i64()))
380 } else {
381 Value::Float(lhs.as_f64() + rhs.as_f64())
382 }
383 }
384 BinaryOp::Sub => {
385 if integral(mode, lhs, rhs) {
386 Value::Int(lhs.as_i64().wrapping_sub(rhs.as_i64()))
387 } else {
388 Value::Float(lhs.as_f64() - rhs.as_f64())
389 }
390 }
391 BinaryOp::Mul => {
392 if integral(mode, lhs, rhs) {
393 Value::Int(lhs.as_i64().wrapping_mul(rhs.as_i64()))
394 } else {
395 Value::Float(lhs.as_f64() * rhs.as_f64())
396 }
397 }
398 BinaryOp::Div => {
402 if mode == EvalMode::Integer {
403 let divisor = rhs.as_i64();
404 if divisor == 0 {
405 return Err(EvalError::DivisionByZero);
406 }
407 Value::Int(lhs.as_i64().wrapping_div(divisor))
408 } else {
409 let divisor = rhs.as_f64();
410 if divisor == 0.0 {
411 return Err(EvalError::DivisionByZero);
412 }
413 Value::Float(lhs.as_f64() / divisor)
414 }
415 }
416 BinaryOp::Mod => {
417 let divisor = rhs.as_i64();
418 if divisor == 0 {
419 return Err(EvalError::DivisionByZero);
420 }
421 Value::Int(lhs.as_i64().wrapping_rem(divisor))
422 }
423 BinaryOp::Pow => {
424 let result = lhs.as_f64().powf(rhs.as_f64());
425 if mode == EvalMode::Integer {
426 Value::Int(result as i64)
427 } else {
428 Value::Float(result)
429 }
430 }
431 BinaryOp::Lt => compare(lhs, rhs, mode, |a, b| a < b, |a, b| a < b),
432 BinaryOp::Le => compare(lhs, rhs, mode, |a, b| a <= b, |a, b| a <= b),
433 BinaryOp::Gt => compare(lhs, rhs, mode, |a, b| a > b, |a, b| a > b),
434 BinaryOp::Ge => compare(lhs, rhs, mode, |a, b| a >= b, |a, b| a >= b),
435 BinaryOp::Eq => compare(lhs, rhs, mode, |a, b| a == b, |a, b| a == b),
436 BinaryOp::Ne => compare(lhs, rhs, mode, |a, b| a != b, |a, b| a != b),
437 BinaryOp::And | BinaryOp::Or => unreachable!("handled by short-circuit"),
438 BinaryOp::BitAnd => Value::Int(lhs.as_i64() & rhs.as_i64()),
439 BinaryOp::BitOr => Value::Int(lhs.as_i64() | rhs.as_i64()),
440 BinaryOp::BitXor => Value::Int(lhs.as_i64() ^ rhs.as_i64()),
441 BinaryOp::Shl => Value::Int(lhs.as_i64().wrapping_shl(shift_amount(rhs))),
442 BinaryOp::Shr => Value::Int(lhs.as_i64().wrapping_shr(shift_amount(rhs))),
443 })
444}
445
446fn compare(
451 lhs: Value,
452 rhs: Value,
453 mode: EvalMode,
454 int_cmp: fn(i64, i64) -> bool,
455 float_cmp: fn(f64, f64) -> bool,
456) -> Value {
457 if integral(mode, lhs, rhs) {
458 Value::from_bool(int_cmp(lhs.as_i64(), rhs.as_i64()))
459 } else {
460 Value::from_bool(float_cmp(lhs.as_f64(), rhs.as_f64()))
461 }
462}
463
464fn eval_function(name: &str, args: &[Value], mode: EvalMode) -> Result<Value, EvalError> {
465 let name_lower = name.to_ascii_lowercase();
468
469 let float_fn = |f: fn(f64) -> f64| -> Result<Value, EvalError> {
472 expect_args(name, args, 1).map(|a| Value::Float(f(a[0].as_f64())))
473 };
474 let rounding_fn = |f: fn(f64) -> f64| -> Result<Value, EvalError> {
476 expect_args(name, args, 1).map(|a| {
477 if mode == EvalMode::Integer || a[0].is_int() {
478 Value::Int(a[0].as_i64())
479 } else {
480 Value::Float(f(a[0].as_f64()))
481 }
482 })
483 };
484
485 match name_lower.as_str() {
486 "sin" => float_fn(f64::sin),
488 "cos" => float_fn(f64::cos),
489 "tan" => float_fn(f64::tan),
490 "asin" => float_fn(f64::asin),
491 "acos" => float_fn(f64::acos),
492 "atan" => float_fn(f64::atan),
493 "sqrt" => float_fn(f64::sqrt),
494 "exp" => float_fn(f64::exp),
495 "ln" => float_fn(f64::ln),
496 "lg" => float_fn(f64::log10),
499 "trunc" => rounding_fn(f64::trunc),
500 "floor" => rounding_fn(f64::floor),
501 "ceil" => rounding_fn(f64::ceil),
502 "round" => rounding_fn(f64::round),
503 "abs" => expect_args(name, args, 1).map(|a| {
504 if mode == EvalMode::Integer || a[0].is_int() {
505 Value::Int(a[0].as_i64().wrapping_abs())
506 } else {
507 Value::Float(a[0].as_f64().abs())
508 }
509 }),
510 "neg" => expect_args(name, args, 1).map(|a| eval_unary(UnaryOp::Minus, a[0], mode)),
511 "sgn" | "sign" => expect_args(name, args, 1).map(|a| {
512 if mode == EvalMode::Integer || a[0].is_int() {
513 Value::Int(a[0].as_i64().signum())
514 } else {
515 let value = a[0].as_f64();
516 Value::Float(if value > 0.0 {
517 1.0
518 } else if value < 0.0 {
519 -1.0
520 } else {
521 0.0
522 })
523 }
524 }),
525 "e" => expect_args(name, args, 0).map(|_| Value::Float(std::f64::consts::E)),
528 "pi" => expect_args(name, args, 0).map(|_| Value::Float(std::f64::consts::PI)),
529
530 "log" | "log10" => float_fn(f64::log10),
534 "log2" => float_fn(f64::log2),
535 "atan2" => {
536 expect_args(name, args, 2).map(|a| Value::Float(a[0].as_f64().atan2(a[1].as_f64())))
537 }
538 "pow" => eval_binary(BinaryOp::Pow, args_at(name, args, 2)?[0], args[1], mode),
539 "fmod" => eval_binary(BinaryOp::Mod, args_at(name, args, 2)?[0], args[1], mode),
540 "min" => expect_args(name, args, 2).map(|a| pick(a[0], a[1], mode, true)),
541 "max" => expect_args(name, args, 2).map(|a| pick(a[0], a[1], mode, false)),
542
543 _ => Err(EvalError::UnknownFunction(name.to_string())),
544 }
545}
546
547fn pick(lhs: Value, rhs: Value, mode: EvalMode, want_min: bool) -> Value {
548 let take_lhs = if integral(mode, lhs, rhs) {
549 (lhs.as_i64() <= rhs.as_i64()) == want_min
550 } else {
551 (lhs.as_f64() <= rhs.as_f64()) == want_min
552 };
553 if take_lhs { lhs } else { rhs }
554}
555
556fn args_at<'a>(name: &str, args: &'a [Value], expected: usize) -> Result<&'a [Value], EvalError> {
557 expect_args(name, args, expected)
558}
559
560fn expect_args<'a>(
561 name: &str,
562 args: &'a [Value],
563 expected: usize,
564) -> Result<&'a [Value], EvalError> {
565 if args.len() != expected {
566 Err(EvalError::ArityMismatch {
567 name: name.to_string(),
568 expected,
569 got: args.len(),
570 })
571 } else {
572 Ok(args)
573 }
574}
575
576pub fn substitute(ast: &mut AstNode, bindings: &std::collections::HashMap<String, AstNode>) {
593 match ast {
594 AstNode::Literal(_) => {}
595 AstNode::Variable(name) => {
596 if let Some(replacement) = bindings.get(name.as_str()) {
597 *ast = replacement.clone();
598 }
599 }
600 AstNode::Unary { expr, .. } => substitute(expr, bindings),
601 AstNode::Binary { left, right, .. } => {
602 substitute(left, bindings);
603 substitute(right, bindings);
604 }
605 AstNode::Ternary {
606 cond,
607 then_expr,
608 else_expr,
609 } => {
610 substitute(cond, bindings);
611 substitute(then_expr, bindings);
612 substitute(else_expr, bindings);
613 }
614 AstNode::FnCall { args, .. } => {
615 for arg in args {
616 substitute(arg, bindings);
617 }
618 }
619 }
620}
621
622pub fn collect_identifiers(ast: &AstNode, out: &mut HashSet<String>) {
628 match ast {
629 AstNode::Literal(_) => {}
630 AstNode::Variable(name) => {
631 out.insert(name.clone());
632 }
633 AstNode::Unary { expr, .. } => collect_identifiers(expr, out),
634 AstNode::Binary { left, right, .. } => {
635 collect_identifiers(left, out);
636 collect_identifiers(right, out);
637 }
638 AstNode::Ternary {
639 cond,
640 then_expr,
641 else_expr,
642 } => {
643 collect_identifiers(cond, out);
644 collect_identifiers(then_expr, out);
645 collect_identifiers(else_expr, out);
646 }
647 AstNode::FnCall { args, .. } => {
648 for arg in args {
649 collect_identifiers(arg, out);
650 }
651 }
652 }
653}
654
655#[derive(Debug, Clone, PartialEq)]
660enum Token {
661 Int(i64),
662 Float(f64),
663 Ident(String),
664 Plus,
666 Minus,
667 Star,
668 Slash,
669 Percent,
670 StarStar, Lt,
673 Le,
674 Gt,
675 Ge,
676 Eq, Ne, AmpAmp,
680 PipePipe,
681 Bang,
682 Amp,
684 Pipe,
685 Caret,
686 Tilde,
687 LtLt,
688 GtGt,
689 Question,
691 Colon,
692 LParen,
694 RParen,
695 Comma,
696 End,
697}
698
699struct Lexer<'a> {
700 input: &'a [u8],
701 pos: usize,
702}
703
704impl<'a> Lexer<'a> {
705 fn new(input: &'a str) -> Self {
706 Lexer {
707 input: input.as_bytes(),
708 pos: 0,
709 }
710 }
711
712 fn peek(&self) -> Option<u8> {
713 self.input.get(self.pos).copied()
714 }
715
716 fn peek_next(&self) -> Option<u8> {
717 self.input.get(self.pos + 1).copied()
718 }
719
720 fn advance_by(&mut self, n: usize) {
721 self.pos += n;
722 }
723
724 fn next_token(&mut self) -> Result<Token, ParseError> {
725 self.skip_ws();
726 let Some(byte) = self.peek() else {
727 return Ok(Token::End);
728 };
729
730 match byte {
731 b'0'..=b'9' | b'.' => self.lex_number(),
732 b'a'..=b'z' | b'A'..=b'Z' | b'_' => self.lex_ident(),
733 b'+' => {
734 self.advance_by(1);
735 Ok(Token::Plus)
736 }
737 b'-' => {
738 self.advance_by(1);
739 Ok(Token::Minus)
740 }
741 b'*' => {
742 if self.peek_next() == Some(b'*') {
743 self.advance_by(2);
744 Ok(Token::StarStar)
745 } else {
746 self.advance_by(1);
747 Ok(Token::Star)
748 }
749 }
750 b'/' => {
751 self.advance_by(1);
752 Ok(Token::Slash)
753 }
754 b'%' => {
755 self.advance_by(1);
756 Ok(Token::Percent)
757 }
758 b'<' => match self.peek_next() {
759 Some(b'=') => {
760 self.advance_by(2);
761 Ok(Token::Le)
762 }
763 Some(b'<') => {
764 self.advance_by(2);
765 Ok(Token::LtLt)
766 }
767 Some(b'>') => {
769 self.advance_by(2);
770 Ok(Token::Ne)
771 }
772 _ => {
773 self.advance_by(1);
774 Ok(Token::Lt)
775 }
776 },
777 b'>' => match self.peek_next() {
778 Some(b'=') => {
779 self.advance_by(2);
780 Ok(Token::Ge)
781 }
782 Some(b'>') => {
783 self.advance_by(2);
784 Ok(Token::GtGt)
785 }
786 _ => {
787 self.advance_by(1);
788 Ok(Token::Gt)
789 }
790 },
791 b'=' => {
794 if self.peek_next() == Some(b'=') {
795 self.advance_by(2);
796 } else {
797 self.advance_by(1);
798 }
799 Ok(Token::Eq)
800 }
801 b'!' => {
802 if self.peek_next() == Some(b'=') {
803 self.advance_by(2);
804 Ok(Token::Ne)
805 } else {
806 self.advance_by(1);
807 Ok(Token::Bang)
808 }
809 }
810 b'&' => {
811 if self.peek_next() == Some(b'&') {
812 self.advance_by(2);
813 Ok(Token::AmpAmp)
814 } else {
815 self.advance_by(1);
816 Ok(Token::Amp)
817 }
818 }
819 b'|' => {
820 if self.peek_next() == Some(b'|') {
821 self.advance_by(2);
822 Ok(Token::PipePipe)
823 } else {
824 self.advance_by(1);
825 Ok(Token::Pipe)
826 }
827 }
828 b'^' => {
829 self.advance_by(1);
830 Ok(Token::Caret)
831 }
832 b'~' => {
833 self.advance_by(1);
834 Ok(Token::Tilde)
835 }
836 b'?' => {
837 self.advance_by(1);
838 Ok(Token::Question)
839 }
840 b':' => {
841 self.advance_by(1);
842 Ok(Token::Colon)
843 }
844 b'(' => {
845 self.advance_by(1);
846 Ok(Token::LParen)
847 }
848 b')' => {
849 self.advance_by(1);
850 Ok(Token::RParen)
851 }
852 b',' => {
853 self.advance_by(1);
854 Ok(Token::Comma)
855 }
856 _ => Err(ParseError::new(
857 format!("unexpected character '{}'", byte as char),
858 self.pos,
859 )),
860 }
861 }
862
863 fn skip_ws(&mut self) {
864 while let Some(byte) = self.peek() {
865 if byte.is_ascii_whitespace() {
866 self.pos += 1;
867 } else {
868 break;
869 }
870 }
871 }
872
873 fn lex_number(&mut self) -> Result<Token, ParseError> {
874 let start = self.pos;
875
876 if self.peek() == Some(b'0') {
878 let next = self.input.get(self.pos + 1).copied();
879 if next == Some(b'x') || next == Some(b'X') {
880 self.pos += 2; let hex_start = self.pos;
882 while let Some(b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') = self.peek() {
883 self.pos += 1;
884 }
885 if self.pos == hex_start {
886 return Err(ParseError::new("hex literal has no digits", start));
887 }
888 let hex_text = std::str::from_utf8(&self.input[hex_start..self.pos])
889 .map_err(|_| ParseError::new("invalid UTF-8 in hex literal", start))?;
890 let value = u64::from_str_radix(hex_text, 16).map_err(|_| {
891 ParseError::new(format!("invalid hex literal: 0x{hex_text}"), start)
892 })?;
893 return Ok(Token::Int(value as i64));
896 }
897 }
898
899 let mut seen_digit = false;
900 let mut seen_dot = false;
901 let mut seen_exp = false;
902
903 while let Some(byte) = self.peek() {
904 match byte {
905 b'0'..=b'9' => {
906 seen_digit = true;
907 self.pos += 1;
908 }
909 b'.' if !seen_dot && !seen_exp => {
910 seen_dot = true;
911 self.pos += 1;
912 }
913 b'e' | b'E' if !seen_exp && seen_digit => {
914 match self.peek_next() {
918 Some(b'0'..=b'9' | b'+' | b'-') => {
919 seen_exp = true;
920 self.pos += 1;
921 if let Some(b'+' | b'-') = self.peek() {
922 self.pos += 1;
923 }
924 }
925 _ => break,
926 }
927 }
928 _ => break,
929 }
930 }
931 if !seen_digit {
932 return Err(ParseError::new("invalid number literal", start));
933 }
934 let slice = &self.input[start..self.pos];
935 let text = std::str::from_utf8(slice)
936 .map_err(|_| ParseError::new("invalid UTF-8 in number", start))?;
937 if !seen_dot
938 && !seen_exp
939 && let Ok(value) = text.parse::<i64>()
940 {
941 return Ok(Token::Int(value));
942 }
943 let value = text
944 .parse::<f64>()
945 .map_err(|_| ParseError::new(format!("failed to parse number: {text}"), start))?;
946 Ok(Token::Float(value))
947 }
948
949 fn lex_ident(&mut self) -> Result<Token, ParseError> {
950 let start = self.pos;
951 self.pos += 1;
952 while let Some(byte) = self.peek() {
953 if byte.is_ascii_alphanumeric() || byte == b'_' {
954 self.pos += 1;
955 } else {
956 break;
957 }
958 }
959 let slice = &self.input[start..self.pos];
960 let text = std::str::from_utf8(slice)
961 .map_err(|_| ParseError::new("invalid UTF-8 in identifier", start))?;
962 Ok(Token::Ident(text.to_string()))
963 }
964}
965
966struct Parser<'a> {
993 lexer: Lexer<'a>,
994 lookahead: Token,
995 pos: usize,
997}
998
999impl<'a> Parser<'a> {
1000 fn new(input: &'a str) -> Result<Self, ParseError> {
1001 let mut lexer = Lexer::new(input);
1002 let lookahead = lexer.next_token()?;
1003 let pos = lexer.pos;
1004 Ok(Parser {
1005 lexer,
1006 lookahead,
1007 pos,
1008 })
1009 }
1010
1011 fn advance(&mut self) -> Result<(), ParseError> {
1012 self.lookahead = self.lexer.next_token()?;
1013 self.pos = self.lexer.pos;
1014 Ok(())
1015 }
1016
1017 fn parse_ternary(&mut self) -> Result<AstNode, ParseError> {
1019 let cond = self.parse_or()?;
1020 if matches!(self.lookahead, Token::Question) {
1021 self.advance()?;
1022 let then_expr = self.parse_ternary()?;
1023 if !matches!(self.lookahead, Token::Colon) {
1024 return Err(ParseError::new(
1025 "expected ':' in ternary expression",
1026 self.pos,
1027 ));
1028 }
1029 self.advance()?;
1030 let else_expr = self.parse_ternary()?;
1031 Ok(AstNode::Ternary {
1032 cond: Box::new(cond),
1033 then_expr: Box::new(then_expr),
1034 else_expr: Box::new(else_expr),
1035 })
1036 } else {
1037 Ok(cond)
1038 }
1039 }
1040
1041 fn parse_or(&mut self) -> Result<AstNode, ParseError> {
1043 let mut node = self.parse_and()?;
1044 while matches!(self.lookahead, Token::PipePipe) {
1045 self.advance()?;
1046 let rhs = self.parse_and()?;
1047 node = AstNode::Binary {
1048 op: BinaryOp::Or,
1049 left: Box::new(node),
1050 right: Box::new(rhs),
1051 };
1052 }
1053 Ok(node)
1054 }
1055
1056 fn parse_and(&mut self) -> Result<AstNode, ParseError> {
1058 let mut node = self.parse_bitor()?;
1059 while matches!(self.lookahead, Token::AmpAmp) {
1060 self.advance()?;
1061 let rhs = self.parse_bitor()?;
1062 node = AstNode::Binary {
1063 op: BinaryOp::And,
1064 left: Box::new(node),
1065 right: Box::new(rhs),
1066 };
1067 }
1068 Ok(node)
1069 }
1070
1071 fn parse_bitor(&mut self) -> Result<AstNode, ParseError> {
1073 let mut node = self.parse_bitxor()?;
1074 while matches!(self.lookahead, Token::Pipe) {
1075 self.advance()?;
1076 let rhs = self.parse_bitxor()?;
1077 node = AstNode::Binary {
1078 op: BinaryOp::BitOr,
1079 left: Box::new(node),
1080 right: Box::new(rhs),
1081 };
1082 }
1083 Ok(node)
1084 }
1085
1086 fn parse_bitxor(&mut self) -> Result<AstNode, ParseError> {
1088 let mut node = self.parse_bitand()?;
1089 while matches!(self.lookahead, Token::Caret) {
1090 self.advance()?;
1091 let rhs = self.parse_bitand()?;
1092 node = AstNode::Binary {
1093 op: BinaryOp::BitXor,
1094 left: Box::new(node),
1095 right: Box::new(rhs),
1096 };
1097 }
1098 Ok(node)
1099 }
1100
1101 fn parse_bitand(&mut self) -> Result<AstNode, ParseError> {
1103 let mut node = self.parse_equality()?;
1104 while matches!(self.lookahead, Token::Amp) {
1105 self.advance()?;
1106 let rhs = self.parse_equality()?;
1107 node = AstNode::Binary {
1108 op: BinaryOp::BitAnd,
1109 left: Box::new(node),
1110 right: Box::new(rhs),
1111 };
1112 }
1113 Ok(node)
1114 }
1115
1116 fn parse_equality(&mut self) -> Result<AstNode, ParseError> {
1118 let mut node = self.parse_comparison()?;
1119 loop {
1120 let op = match &self.lookahead {
1121 Token::Eq => BinaryOp::Eq,
1122 Token::Ne => BinaryOp::Ne,
1123 _ => break,
1124 };
1125 self.advance()?;
1126 let rhs = self.parse_comparison()?;
1127 node = AstNode::Binary {
1128 op,
1129 left: Box::new(node),
1130 right: Box::new(rhs),
1131 };
1132 }
1133 Ok(node)
1134 }
1135
1136 fn parse_comparison(&mut self) -> Result<AstNode, ParseError> {
1138 let mut node = self.parse_shift()?;
1139 loop {
1140 let op = match &self.lookahead {
1141 Token::Lt => BinaryOp::Lt,
1142 Token::Le => BinaryOp::Le,
1143 Token::Gt => BinaryOp::Gt,
1144 Token::Ge => BinaryOp::Ge,
1145 _ => break,
1146 };
1147 self.advance()?;
1148 let rhs = self.parse_shift()?;
1149 node = AstNode::Binary {
1150 op,
1151 left: Box::new(node),
1152 right: Box::new(rhs),
1153 };
1154 }
1155 Ok(node)
1156 }
1157
1158 fn parse_shift(&mut self) -> Result<AstNode, ParseError> {
1160 let mut node = self.parse_additive()?;
1161 loop {
1162 let op = match &self.lookahead {
1163 Token::LtLt => BinaryOp::Shl,
1164 Token::GtGt => BinaryOp::Shr,
1165 _ => break,
1166 };
1167 self.advance()?;
1168 let rhs = self.parse_additive()?;
1169 node = AstNode::Binary {
1170 op,
1171 left: Box::new(node),
1172 right: Box::new(rhs),
1173 };
1174 }
1175 Ok(node)
1176 }
1177
1178 fn parse_additive(&mut self) -> Result<AstNode, ParseError> {
1180 let mut node = self.parse_multiplicative()?;
1181 loop {
1182 let op = match &self.lookahead {
1183 Token::Plus => BinaryOp::Add,
1184 Token::Minus => BinaryOp::Sub,
1185 _ => break,
1186 };
1187 self.advance()?;
1188 let rhs = self.parse_multiplicative()?;
1189 node = AstNode::Binary {
1190 op,
1191 left: Box::new(node),
1192 right: Box::new(rhs),
1193 };
1194 }
1195 Ok(node)
1196 }
1197
1198 fn parse_multiplicative(&mut self) -> Result<AstNode, ParseError> {
1200 let mut node = self.parse_power()?;
1201 loop {
1202 let op = match &self.lookahead {
1203 Token::Star => BinaryOp::Mul,
1204 Token::Slash => BinaryOp::Div,
1205 Token::Percent => BinaryOp::Mod,
1206 _ => break,
1207 };
1208 self.advance()?;
1209 let rhs = self.parse_power()?;
1210 node = AstNode::Binary {
1211 op,
1212 left: Box::new(node),
1213 right: Box::new(rhs),
1214 };
1215 }
1216 Ok(node)
1217 }
1218
1219 fn parse_power(&mut self) -> Result<AstNode, ParseError> {
1221 let base = self.parse_unary()?;
1222 if matches!(self.lookahead, Token::StarStar) {
1223 self.advance()?;
1224 let exp = self.parse_power()?; Ok(AstNode::Binary {
1226 op: BinaryOp::Pow,
1227 left: Box::new(base),
1228 right: Box::new(exp),
1229 })
1230 } else {
1231 Ok(base)
1232 }
1233 }
1234
1235 fn parse_unary(&mut self) -> Result<AstNode, ParseError> {
1237 let op = match &self.lookahead {
1238 Token::Plus => UnaryOp::Plus,
1239 Token::Minus => UnaryOp::Minus,
1240 Token::Bang => UnaryOp::Not,
1241 Token::Tilde => UnaryOp::BitNot,
1242 _ => return self.parse_primary(),
1243 };
1244 self.advance()?;
1245 let expr = self.parse_unary()?;
1246 Ok(AstNode::Unary {
1247 op,
1248 expr: Box::new(expr),
1249 })
1250 }
1251
1252 fn parse_primary(&mut self) -> Result<AstNode, ParseError> {
1254 match self.lookahead.clone() {
1255 Token::Int(value) => {
1256 self.advance()?;
1257 Ok(AstNode::Literal(Value::Int(value)))
1258 }
1259 Token::Float(value) => {
1260 self.advance()?;
1261 Ok(AstNode::Literal(Value::Float(value)))
1262 }
1263 Token::Ident(name) => {
1264 self.advance()?;
1265 if matches!(self.lookahead, Token::LParen) {
1267 self.advance()?;
1268 let mut args = Vec::new();
1269 if !matches!(self.lookahead, Token::RParen) {
1270 args.push(self.parse_ternary()?);
1271 while matches!(self.lookahead, Token::Comma) {
1272 self.advance()?;
1273 args.push(self.parse_ternary()?);
1274 }
1275 }
1276 if !matches!(self.lookahead, Token::RParen) {
1277 return Err(ParseError::new(
1278 "expected ')' after function arguments",
1279 self.pos,
1280 ));
1281 }
1282 self.advance()?;
1283 Ok(AstNode::FnCall { name, args })
1284 } else {
1285 Ok(AstNode::Variable(name))
1286 }
1287 }
1288 Token::LParen => {
1289 self.advance()?;
1290 let expr = self.parse_ternary()?;
1291 if !matches!(self.lookahead, Token::RParen) {
1292 return Err(ParseError::new("missing closing ')'", self.pos));
1293 }
1294 self.advance()?;
1295 Ok(expr)
1296 }
1297 Token::End => Err(ParseError::new("unexpected end of expression", self.pos)),
1298 other => Err(ParseError::new(
1299 format!("unexpected token {other:?}"),
1300 self.pos,
1301 )),
1302 }
1303 }
1304}
1305
1306#[cfg(test)]
1307mod tests {
1308 use super::*;
1309
1310 fn eval_with(expr: &str, vars: &[(&str, Value)], mode: EvalMode) -> Value {
1311 let ast = parse_expression(expr).expect("parse failed");
1312 let mut resolver = |name: &str| {
1313 vars.iter()
1314 .find(|(n, _)| *n == name)
1315 .map(|(_, v)| *v)
1316 .ok_or_else(|| EvalError::UnknownVariable(name.to_string()))
1317 };
1318 evaluate(&ast, &mut resolver, mode).expect("eval failed")
1319 }
1320
1321 fn eval_expr(expr: &str, vars: &[(&str, f64)]) -> f64 {
1323 let bound: Vec<(&str, Value)> = vars.iter().map(|(n, v)| (*n, Value::Float(*v))).collect();
1324 eval_with(expr, &bound, EvalMode::Float).as_f64()
1325 }
1326
1327 fn eval_int(expr: &str, vars: &[(&str, i64)]) -> i64 {
1330 let bound: Vec<(&str, Value)> = vars.iter().map(|(n, v)| (*n, Value::Int(*v))).collect();
1331 eval_with(expr, &bound, EvalMode::Integer).as_i64()
1332 }
1333
1334 #[test]
1335 fn basic_arithmetic() {
1336 assert!((eval_expr("(A + 2) * 3 - B / 4", &[("A", 4.0), ("B", 8.0)]) - 16.0).abs() < 1e-6);
1337 assert!((eval_expr("-A + 10 / (B - 5)", &[("A", 3.0), ("B", 7.0)]) - 2.0).abs() < 1e-6);
1338 }
1339
1340 #[test]
1341 fn comparisons() {
1342 assert_eq!(eval_expr("5 < 10", &[]), 1.0);
1343 assert_eq!(eval_expr("5 > 10", &[]), 0.0);
1344 assert_eq!(eval_expr("5 <= 5", &[]), 1.0);
1345 assert_eq!(eval_expr("5 >= 6", &[]), 0.0);
1346 assert_eq!(eval_expr("A < B", &[("A", 3.0), ("B", 5.0)]), 1.0);
1347 }
1348
1349 #[test]
1352 fn single_equals_is_equality() {
1353 assert_eq!(eval_int("5 = 5", &[]), 1);
1354 assert_eq!(eval_int("5 = 6", &[]), 0);
1355 assert_eq!(
1357 eval_int("(TEMPCTRLMODE = 1) ? 1 : 0", &[("TEMPCTRLMODE", 1)]),
1358 1
1359 );
1360 assert_eq!(
1361 eval_int("(TEMPCTRLMODE = 1) ? 1 : 0", &[("TEMPCTRLMODE", 0)]),
1362 0
1363 );
1364 assert_eq!(eval_int("5 == 5", &[]), 1);
1366 }
1367
1368 #[test]
1370 fn angle_brackets_are_inequality() {
1371 assert_eq!(eval_int("5 <> 6", &[]), 1);
1372 assert_eq!(eval_int("5 <> 5", &[]), 0);
1373 assert_eq!(eval_int("(DS <> 0) ? 1 : 0", &[("DS", 3)]), 1);
1374 assert_eq!(eval_int("1 << 3", &[]), 8);
1376 assert_eq!(eval_int("(1 < 3) = 1", &[]), 1);
1377 assert_eq!(eval_int("5 != 5", &[]), 0);
1379 }
1380
1381 #[test]
1382 fn equality_binds_looser_than_comparison() {
1383 assert_eq!(eval_int("1 < 3 = 1", &[]), 1);
1385 assert_eq!(eval_int("3 < 1 = 0", &[]), 1);
1386 assert_eq!(eval_int("1 & 1 = 1", &[]), 1);
1388 }
1389
1390 #[test]
1391 fn ternary_expression() {
1392 assert_eq!(eval_expr("1 ? 10 : 20", &[]), 10.0);
1393 assert_eq!(eval_expr("0 ? 10 : 20", &[]), 20.0);
1394 assert_eq!(eval_expr("A > 5 ? A : 5", &[("A", 3.0)]), 5.0);
1395 assert_eq!(eval_expr("A > 5 ? A : 5", &[("A", 10.0)]), 10.0);
1396 assert_eq!(
1398 eval_expr("A < 0 ? -1 : A > 0 ? 1 : 0", &[("A", -5.0)]),
1399 -1.0
1400 );
1401 assert_eq!(eval_expr("A < 0 ? -1 : A > 0 ? 1 : 0", &[("A", 5.0)]), 1.0);
1402 assert_eq!(eval_expr("A < 0 ? -1 : A > 0 ? 1 : 0", &[("A", 0.0)]), 0.0);
1403 }
1404
1405 #[test]
1406 fn logical_operators() {
1407 assert_eq!(eval_expr("1 && 1", &[]), 1.0);
1408 assert_eq!(eval_expr("1 && 0", &[]), 0.0);
1409 assert_eq!(eval_expr("0 || 1", &[]), 1.0);
1410 assert_eq!(eval_expr("0 || 0", &[]), 0.0);
1411 assert_eq!(eval_expr("!0", &[]), 1.0);
1412 assert_eq!(eval_expr("!1", &[]), 0.0);
1413 assert_eq!(eval_expr("!5", &[]), 0.0);
1414 }
1415
1416 #[test]
1417 fn bitwise_operators() {
1418 assert_eq!(eval_expr("5 & 3", &[]), 1.0); assert_eq!(eval_expr("5 | 3", &[]), 7.0); assert_eq!(eval_expr("5 ^ 3", &[]), 6.0); assert_eq!(eval_expr("1 << 3", &[]), 8.0);
1422 assert_eq!(eval_expr("8 >> 2", &[]), 2.0);
1423 assert_eq!(eval_int("~0", &[]), -1);
1424 }
1425
1426 #[test]
1430 fn wide_shifts_stay_exact() {
1431 assert_eq!(
1432 eval_int(
1433 "(HIGH << 32) | LOW",
1434 &[("HIGH", 0x1234_5678), ("LOW", 0x9ABC_DEF1)]
1435 ),
1436 0x1234_5678_9ABC_DEF1
1437 );
1438 assert_eq!(
1440 eval_int(
1441 "( ( HI & 0x0000FFFF ) << 32 ) | LO",
1442 &[("HI", 0x205C), ("LO", 0x208F_8000)]
1443 ),
1444 0x205C_208F_8000
1445 );
1446 assert_eq!(eval_int("V << 63", &[("V", 3)]), i64::MIN);
1448 assert_eq!(eval_int("V << 64", &[("V", 3)]), 3);
1449 }
1450
1451 #[test]
1453 fn division_follows_the_mode() {
1454 assert_eq!(eval_int("7 / 2", &[]), 3);
1455 assert_eq!(eval_int("(IDX / 2) * 4", &[("IDX", 3)]), 4);
1456 assert!((eval_expr("7 / 2", &[]) - 3.5).abs() < 1e-9);
1457 assert_eq!(eval_int("OFFSET * 4 / 2", &[("OFFSET", 3)]), 6);
1460 }
1461
1462 #[test]
1463 fn hex_literals() {
1464 assert_eq!(eval_expr("0xFF", &[]), 255.0);
1465 assert_eq!(eval_expr("0x10", &[]), 16.0);
1466 assert_eq!(eval_expr("0x0", &[]), 0.0);
1467 assert_eq!(eval_expr("0xDEAD", &[]), 0xDEAD as f64);
1468 assert_eq!(eval_expr("(0x01080001 >> 16) & 0xFF", &[]), 8.0);
1469 assert_eq!(
1471 eval_expr(
1472 "W * H * ((PF>>16)&0xFF) / 8",
1473 &[("W", 512.0), ("H", 512.0), ("PF", 0x01080001_u32 as f64)]
1474 ),
1475 512.0 * 512.0 * 8.0 / 8.0
1476 );
1477 assert_eq!(eval_int("PF = 0x0110000c", &[("PF", 0x0110000C)]), 1);
1479 }
1480
1481 #[test]
1482 fn power_operator() {
1483 assert!((eval_expr("2 ** 3", &[]) - 8.0).abs() < 1e-6);
1484 assert!((eval_expr("2 ** 3 ** 2", &[]) - 512.0).abs() < 1e-6); }
1486
1487 #[test]
1488 fn modulo_operator() {
1489 assert!((eval_expr("10 % 3", &[]) - 1.0).abs() < 1e-6);
1490 assert!((eval_expr("17 % 5", &[]) - 2.0).abs() < 1e-6);
1491 }
1492
1493 #[test]
1494 fn functions() {
1495 assert!((eval_expr("abs(-5)", &[]) - 5.0).abs() < 1e-6);
1496 assert!((eval_expr("sqrt(16)", &[]) - 4.0).abs() < 1e-6);
1497 assert!((eval_expr("min(3, 7)", &[]) - 3.0).abs() < 1e-6);
1498 assert!((eval_expr("max(3, 7)", &[]) - 7.0).abs() < 1e-6);
1499 assert!((eval_expr("pow(2, 10)", &[]) - 1024.0).abs() < 1e-6);
1500 assert!((eval_expr("floor(3.7)", &[]) - 3.0).abs() < 1e-6);
1501 assert!((eval_expr("ceil(3.2)", &[]) - 4.0).abs() < 1e-6);
1502 assert!((eval_expr("round(3.5)", &[]) - 4.0).abs() < 1e-6);
1503 assert!((eval_expr("sgn(-5)", &[]) - -1.0).abs() < 1e-6);
1504 assert!((eval_expr("sgn(5)", &[]) - 1.0).abs() < 1e-6);
1505 assert!((eval_expr("sgn(0)", &[]) - 0.0).abs() < 1e-6);
1506 }
1507
1508 #[test]
1510 fn genapi_standard_functions() {
1511 assert!((eval_expr("LG(1000)", &[]) - 3.0).abs() < 1e-9);
1512 assert!((eval_expr("(20 * (LG(TO/1024)))", &[("TO", 10240.0)]) - 20.0).abs() < 1e-9);
1513 assert!((eval_expr("LN(1)", &[]) - 0.0).abs() < 1e-9);
1514 assert!((eval_expr("TRUNC(3.9)", &[]) - 3.0).abs() < 1e-9);
1515 assert!((eval_expr("NEG(4)", &[]) + 4.0).abs() < 1e-9);
1516 assert!((eval_expr("Sqrt(9)", &[]) - 3.0).abs() < 1e-9);
1518 }
1519
1520 #[test]
1521 fn builtin_constants() {
1522 assert!((eval_expr("PI", &[]) - std::f64::consts::PI).abs() < 1e-12);
1523 assert!((eval_expr("E", &[]) - std::f64::consts::E).abs() < 1e-12);
1524 assert!((eval_expr("2 * PI", &[]) - std::f64::consts::TAU).abs() < 1e-12);
1525 assert_eq!(eval_expr("E", &[("E", 7.0)]), 7.0);
1527 assert!(is_builtin_constant("pi"));
1528 assert!(!is_builtin_constant("EXPMODE"));
1529 }
1530
1531 #[test]
1532 fn scientific_notation() {
1533 assert!((eval_expr("1e3", &[]) - 1000.0).abs() < 1e-6);
1534 assert!((eval_expr("1.5e-2", &[]) - 0.015).abs() < 1e-9);
1535 assert!((eval_expr("2.5E+3", &[]) - 2500.0).abs() < 1e-6);
1536 }
1537
1538 #[test]
1539 fn division_by_zero_error() {
1540 let ast = parse_expression("A / B").expect("parse");
1541 let mut vars = |name: &str| match name {
1542 "A" => Ok(Value::Float(5.0)),
1543 "B" => Ok(Value::Float(0.0)),
1544 _ => Err(EvalError::UnknownVariable(name.to_string())),
1545 };
1546 let err = evaluate(&ast, &mut vars, EvalMode::Float).expect_err("division by zero");
1547 assert!(matches!(err, EvalError::DivisionByZero));
1548 }
1549
1550 #[test]
1551 fn complex_basler_style_expression() {
1552 let expr = "RawValue < 0 ? 0 : RawValue * 1000 / TickFreq";
1554 assert!(
1555 (eval_expr(expr, &[("RawValue", 500.0), ("TickFreq", 1000.0)]) - 500.0).abs() < 1e-6
1556 );
1557 assert_eq!(
1558 eval_expr(expr, &[("RawValue", -10.0), ("TickFreq", 1000.0)]),
1559 0.0
1560 );
1561 }
1562
1563 #[test]
1567 fn vendor_formula_shapes_parse() {
1568 let formulas = [
1569 "( ( PINPRES = 1 ) && ( MODE = 0 ) && (SEL <> 2) ) ? 1 : 0",
1570 "((CTRL_REG & 0x03000000)=0x03000000)?1:0",
1571 "((CTRL_REG | 0xFDFFFFFF)=0xFFFFFFFF)?0:1",
1572 "ADDROFFSET = 0 ? 0 : ADDROFFSET * 0x80",
1573 "(FEAT<32)?((GEVOPT>>FEAT)&0x1):((FEAT<64)?((IPOPT>>(FEAT-32))&0x1) : ((FEAT=66)?SCCB:0))",
1574 "( ((TM = 0) && (LINE1 = 1)) \n || ((TL = 1) && (REVERSE = 0)) \n || (DS <> 0)) ? 1 : 0",
1575 "((TYPE = 0x011a) || (TYPE = 0x011b)) ? (0.4232 * DKELVIN - 334.83) : (DKELVIN /100)",
1576 "(SHUTTERMODE = 0) && (ROLLING = 1)",
1577 "INDEX = 0",
1578 "( MAX % UNIT ) ? ( MAX - ( MAX) % UNIT ) : ( MAX)",
1579 ];
1580 for formula in formulas {
1581 parse_expression(formula)
1582 .unwrap_or_else(|err| panic!("failed to parse {formula:?}: {err}"));
1583 }
1584 }
1585
1586 #[test]
1587 fn parse_error_reports_offset() {
1588 let err = parse_expression("A + ").expect_err("incomplete expression");
1589 assert!(err.offset() > 0, "offset should point past the operator");
1590 assert!(err.to_string().contains("at offset"));
1591 }
1592
1593 #[test]
1594 fn collect_identifiers_with_ternary() {
1595 let ast = parse_expression("A > B ? C + D : E * F").expect("parse");
1596 let mut ids = HashSet::new();
1597 collect_identifiers(&ast, &mut ids);
1598 assert!(ids.contains("A"));
1599 assert!(ids.contains("B"));
1600 assert!(ids.contains("C"));
1601 assert!(ids.contains("D"));
1602 assert!(ids.contains("E"));
1603 assert!(ids.contains("F"));
1604 assert_eq!(ids.len(), 6);
1605 }
1606
1607 #[test]
1608 fn collect_identifiers_with_functions() {
1609 let ast = parse_expression("max(A, min(B, C))").expect("parse");
1610 let mut ids = HashSet::new();
1611 collect_identifiers(&ast, &mut ids);
1612 assert!(ids.contains("A"));
1613 assert!(ids.contains("B"));
1614 assert!(ids.contains("C"));
1615 assert_eq!(ids.len(), 3);
1616 }
1617}