1use crate::error::PdfError;
7use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
11pub struct JavaScriptEngine {
12 variables: HashMap<String, f64>,
14 field_getter: Option<FieldGetter>,
16}
17
18type FieldGetter = fn(&str) -> Option<f64>;
20
21#[derive(Debug, Clone, PartialEq)]
23#[allow(dead_code)]
24enum Token {
25 Number(f64),
26 Identifier(String),
27 Plus,
28 Minus,
29 Multiply,
30 Divide,
31 LeftParen,
32 RightParen,
33 Equals,
34 NotEquals,
35 LessThan,
36 LessThanEquals,
37 GreaterThan,
38 GreaterThanEquals,
39 And,
40 Or,
41 Not,
42 If,
43 Else,
44 Return,
45 Semicolon,
46 Comma,
47 Dot,
48 #[allow(clippy::upper_case_acronyms)]
49 EOF,
50}
51
52struct Parser {
54 tokens: Vec<Token>,
55 current: usize,
56}
57
58#[derive(Debug, Clone)]
60#[allow(dead_code)]
61enum ASTNode {
62 Number(f64),
63 Identifier(String),
64 BinaryOp {
65 op: BinaryOperator,
66 left: Box<ASTNode>,
67 right: Box<ASTNode>,
68 },
69 UnaryOp {
70 op: UnaryOperator,
71 operand: Box<ASTNode>,
72 },
73 FieldAccess {
74 object: String,
75 field: String,
76 },
77 FunctionCall {
78 name: String,
79 args: Vec<ASTNode>,
80 },
81 Conditional {
82 condition: Box<ASTNode>,
83 then_expr: Box<ASTNode>,
84 else_expr: Option<Box<ASTNode>>,
85 },
86}
87
88#[derive(Debug, Clone, PartialEq)]
89enum BinaryOperator {
90 Add,
91 Subtract,
92 Multiply,
93 Divide,
94 Equals,
95 NotEquals,
96 LessThan,
97 LessThanEquals,
98 GreaterThan,
99 GreaterThanEquals,
100 And,
101 Or,
102}
103
104#[derive(Debug, Clone, PartialEq)]
105enum UnaryOperator {
106 Negate,
107 Not,
108}
109
110#[allow(clippy::derivable_impls)]
111impl Default for JavaScriptEngine {
112 fn default() -> Self {
113 Self {
114 variables: HashMap::new(),
115 field_getter: None,
116 }
117 }
118}
119
120impl JavaScriptEngine {
121 pub fn new() -> Self {
123 Self::default()
124 }
125
126 pub fn set_variable(&mut self, name: impl Into<String>, value: f64) {
128 self.variables.insert(name.into(), value);
129 }
130
131 pub fn set_field_getter(&mut self, getter: FieldGetter) {
133 self.field_getter = Some(getter);
134 }
135
136 pub fn evaluate(&self, code: &str) -> Result<f64, PdfError> {
138 let tokens = self.tokenize(code)?;
140
141 let mut parser = Parser::new(tokens);
143 let ast = parser.parse()?;
144
145 self.eval_node(&ast)
147 }
148
149 fn tokenize(&self, code: &str) -> Result<Vec<Token>, PdfError> {
151 let mut tokens = Vec::new();
152 let mut chars = code.chars().peekable();
153
154 while let Some(ch) = chars.next() {
155 match ch {
156 ' ' | '\t' | '\n' | '\r' => continue,
157 '+' => tokens.push(Token::Plus),
158 '-' => tokens.push(Token::Minus),
159 '*' => tokens.push(Token::Multiply),
160 '/' => {
161 if chars.peek() == Some(&'/') {
162 chars.next();
164 for c in chars.by_ref() {
165 if c == '\n' {
166 break;
167 }
168 }
169 } else {
170 tokens.push(Token::Divide);
171 }
172 }
173 '(' => tokens.push(Token::LeftParen),
174 ')' => tokens.push(Token::RightParen),
175 '=' => {
176 if chars.peek() == Some(&'=') {
177 chars.next();
178 tokens.push(Token::Equals);
179 }
180 }
181 '!' => {
182 if chars.peek() == Some(&'=') {
183 chars.next();
184 tokens.push(Token::NotEquals);
185 } else {
186 tokens.push(Token::Not);
187 }
188 }
189 '<' => {
190 if chars.peek() == Some(&'=') {
191 chars.next();
192 tokens.push(Token::LessThanEquals);
193 } else {
194 tokens.push(Token::LessThan);
195 }
196 }
197 '>' => {
198 if chars.peek() == Some(&'=') {
199 chars.next();
200 tokens.push(Token::GreaterThanEquals);
201 } else {
202 tokens.push(Token::GreaterThan);
203 }
204 }
205 '&' => {
206 if chars.peek() == Some(&'&') {
207 chars.next();
208 tokens.push(Token::And);
209 }
210 }
211 '|' => {
212 if chars.peek() == Some(&'|') {
213 chars.next();
214 tokens.push(Token::Or);
215 }
216 }
217 ';' => tokens.push(Token::Semicolon),
218 ',' => tokens.push(Token::Comma),
219 '.' => tokens.push(Token::Dot),
220 '0'..='9' => {
221 let mut num_str = String::new();
222 num_str.push(ch);
223 while let Some(&next_ch) = chars.peek() {
224 if next_ch.is_ascii_digit() || next_ch == '.' {
225 num_str.push(chars.next().ok_or_else(|| {
226 PdfError::InvalidFormat(
227 "Unexpected end of number literal".to_string(),
228 )
229 })?);
230 } else {
231 break;
232 }
233 }
234 let num = num_str
235 .parse::<f64>()
236 .map_err(|_| PdfError::InvalidFormat("Invalid number".to_string()))?;
237 tokens.push(Token::Number(num));
238 }
239 'a'..='z' | 'A'..='Z' | '_' => {
240 let mut ident = String::new();
241 ident.push(ch);
242 while let Some(&next_ch) = chars.peek() {
243 if next_ch.is_alphanumeric() || next_ch == '_' {
244 ident.push(chars.next().ok_or_else(|| {
245 PdfError::InvalidFormat("Unexpected end of identifier".to_string())
246 })?);
247 } else {
248 break;
249 }
250 }
251
252 let token = match ident.as_str() {
254 "if" => Token::If,
255 "else" => Token::Else,
256 "return" => Token::Return,
257 _ => Token::Identifier(ident),
258 };
259 tokens.push(token);
260 }
261 _ => {
262 }
264 }
265 }
266
267 tokens.push(Token::EOF);
268 Ok(tokens)
269 }
270
271 fn eval_node(&self, node: &ASTNode) -> Result<f64, PdfError> {
273 match node {
274 ASTNode::Number(n) => Ok(*n),
275 ASTNode::Identifier(name) => {
276 if let Some(&value) = self.variables.get(name) {
278 return Ok(value);
279 }
280
281 if let Some(getter) = self.field_getter {
283 if let Some(value) = getter(name) {
284 return Ok(value);
285 }
286 }
287
288 Ok(0.0)
290 }
291 ASTNode::BinaryOp { op, left, right } => {
292 let left_val = self.eval_node(left)?;
293 let right_val = self.eval_node(right)?;
294
295 match op {
296 BinaryOperator::Add => Ok(left_val + right_val),
297 BinaryOperator::Subtract => Ok(left_val - right_val),
298 BinaryOperator::Multiply => Ok(left_val * right_val),
299 BinaryOperator::Divide => {
300 if right_val != 0.0 {
301 Ok(left_val / right_val)
302 } else {
303 Ok(0.0)
304 }
305 }
306 BinaryOperator::Equals => Ok(if left_val == right_val { 1.0 } else { 0.0 }),
307 BinaryOperator::NotEquals => Ok(if left_val != right_val { 1.0 } else { 0.0 }),
308 BinaryOperator::LessThan => Ok(if left_val < right_val { 1.0 } else { 0.0 }),
309 BinaryOperator::LessThanEquals => {
310 Ok(if left_val <= right_val { 1.0 } else { 0.0 })
311 }
312 BinaryOperator::GreaterThan => Ok(if left_val > right_val { 1.0 } else { 0.0 }),
313 BinaryOperator::GreaterThanEquals => {
314 Ok(if left_val >= right_val { 1.0 } else { 0.0 })
315 }
316 BinaryOperator::And => Ok(if left_val != 0.0 && right_val != 0.0 {
317 1.0
318 } else {
319 0.0
320 }),
321 BinaryOperator::Or => Ok(if left_val != 0.0 || right_val != 0.0 {
322 1.0
323 } else {
324 0.0
325 }),
326 }
327 }
328 ASTNode::UnaryOp { op, operand } => {
329 let val = self.eval_node(operand)?;
330 match op {
331 UnaryOperator::Negate => Ok(-val),
332 UnaryOperator::Not => Ok(if val == 0.0 { 1.0 } else { 0.0 }),
333 }
334 }
335 ASTNode::FieldAccess { object, field } => {
336 if object == "this" {
338 if let Some(getter) = self.field_getter {
339 if let Some(value) = getter(field) {
340 return Ok(value);
341 }
342 }
343 }
344 Ok(0.0)
345 }
346 ASTNode::FunctionCall { name, args } => {
347 match name.as_str() {
349 "Math.min" => {
350 let values: Result<Vec<f64>, _> =
351 args.iter().map(|arg| self.eval_node(arg)).collect();
352 let values = values?;
353 Ok(values.iter().cloned().fold(f64::INFINITY, f64::min))
354 }
355 "Math.max" => {
356 let values: Result<Vec<f64>, _> =
357 args.iter().map(|arg| self.eval_node(arg)).collect();
358 let values = values?;
359 Ok(values.iter().cloned().fold(f64::NEG_INFINITY, f64::max))
360 }
361 "Math.round" => {
362 if let Some(arg) = args.first() {
363 Ok(self.eval_node(arg)?.round())
364 } else {
365 Ok(0.0)
366 }
367 }
368 "Math.floor" => {
369 if let Some(arg) = args.first() {
370 Ok(self.eval_node(arg)?.floor())
371 } else {
372 Ok(0.0)
373 }
374 }
375 "Math.ceil" => {
376 if let Some(arg) = args.first() {
377 Ok(self.eval_node(arg)?.ceil())
378 } else {
379 Ok(0.0)
380 }
381 }
382 "Math.abs" => {
383 if let Some(arg) = args.first() {
384 Ok(self.eval_node(arg)?.abs())
385 } else {
386 Ok(0.0)
387 }
388 }
389 _ => Ok(0.0),
390 }
391 }
392 ASTNode::Conditional {
393 condition,
394 then_expr,
395 else_expr,
396 } => {
397 let cond_val = self.eval_node(condition)?;
398 if cond_val != 0.0 {
399 self.eval_node(then_expr)
400 } else if let Some(else_expr) = else_expr {
401 self.eval_node(else_expr)
402 } else {
403 Ok(0.0)
404 }
405 }
406 }
407 }
408}
409
410impl Parser {
411 fn new(tokens: Vec<Token>) -> Self {
412 Self { tokens, current: 0 }
413 }
414
415 fn parse(&mut self) -> Result<ASTNode, PdfError> {
416 self.parse_expression()
417 }
418
419 fn parse_expression(&mut self) -> Result<ASTNode, PdfError> {
420 self.parse_conditional()
421 }
422
423 fn parse_conditional(&mut self) -> Result<ASTNode, PdfError> {
424 let expr = self.parse_logical_or()?;
425
426 Ok(expr)
430 }
431
432 fn parse_logical_or(&mut self) -> Result<ASTNode, PdfError> {
433 let mut left = self.parse_logical_and()?;
434
435 while self.current_token() == Some(&Token::Or) {
436 self.advance();
437 let right = self.parse_logical_and()?;
438 left = ASTNode::BinaryOp {
439 op: BinaryOperator::Or,
440 left: Box::new(left),
441 right: Box::new(right),
442 };
443 }
444
445 Ok(left)
446 }
447
448 fn parse_logical_and(&mut self) -> Result<ASTNode, PdfError> {
449 let mut left = self.parse_equality()?;
450
451 while self.current_token() == Some(&Token::And) {
452 self.advance();
453 let right = self.parse_equality()?;
454 left = ASTNode::BinaryOp {
455 op: BinaryOperator::And,
456 left: Box::new(left),
457 right: Box::new(right),
458 };
459 }
460
461 Ok(left)
462 }
463
464 fn parse_equality(&mut self) -> Result<ASTNode, PdfError> {
465 let mut left = self.parse_relational()?;
466
467 while let Some(token) = self.current_token() {
468 let op = match token {
469 Token::Equals => BinaryOperator::Equals,
470 Token::NotEquals => BinaryOperator::NotEquals,
471 _ => break,
472 };
473
474 self.advance();
475 let right = self.parse_relational()?;
476 left = ASTNode::BinaryOp {
477 op,
478 left: Box::new(left),
479 right: Box::new(right),
480 };
481 }
482
483 Ok(left)
484 }
485
486 fn parse_relational(&mut self) -> Result<ASTNode, PdfError> {
487 let mut left = self.parse_additive()?;
488
489 while let Some(token) = self.current_token() {
490 let op = match token {
491 Token::LessThan => BinaryOperator::LessThan,
492 Token::LessThanEquals => BinaryOperator::LessThanEquals,
493 Token::GreaterThan => BinaryOperator::GreaterThan,
494 Token::GreaterThanEquals => BinaryOperator::GreaterThanEquals,
495 _ => break,
496 };
497
498 self.advance();
499 let right = self.parse_additive()?;
500 left = ASTNode::BinaryOp {
501 op,
502 left: Box::new(left),
503 right: Box::new(right),
504 };
505 }
506
507 Ok(left)
508 }
509
510 fn parse_additive(&mut self) -> Result<ASTNode, PdfError> {
511 let mut left = self.parse_multiplicative()?;
512
513 while let Some(token) = self.current_token() {
514 let op = match token {
515 Token::Plus => BinaryOperator::Add,
516 Token::Minus => BinaryOperator::Subtract,
517 _ => break,
518 };
519
520 self.advance();
521 let right = self.parse_multiplicative()?;
522 left = ASTNode::BinaryOp {
523 op,
524 left: Box::new(left),
525 right: Box::new(right),
526 };
527 }
528
529 Ok(left)
530 }
531
532 fn parse_multiplicative(&mut self) -> Result<ASTNode, PdfError> {
533 let mut left = self.parse_unary()?;
534
535 while let Some(token) = self.current_token() {
536 let op = match token {
537 Token::Multiply => BinaryOperator::Multiply,
538 Token::Divide => BinaryOperator::Divide,
539 _ => break,
540 };
541
542 self.advance();
543 let right = self.parse_unary()?;
544 left = ASTNode::BinaryOp {
545 op,
546 left: Box::new(left),
547 right: Box::new(right),
548 };
549 }
550
551 Ok(left)
552 }
553
554 fn parse_unary(&mut self) -> Result<ASTNode, PdfError> {
555 if let Some(token) = self.current_token() {
556 match token {
557 Token::Minus => {
558 self.advance();
559 let operand = self.parse_unary()?;
560 return Ok(ASTNode::UnaryOp {
561 op: UnaryOperator::Negate,
562 operand: Box::new(operand),
563 });
564 }
565 Token::Not => {
566 self.advance();
567 let operand = self.parse_unary()?;
568 return Ok(ASTNode::UnaryOp {
569 op: UnaryOperator::Not,
570 operand: Box::new(operand),
571 });
572 }
573 _ => {}
574 }
575 }
576
577 self.parse_primary()
578 }
579
580 fn parse_primary(&mut self) -> Result<ASTNode, PdfError> {
581 if let Some(token) = self.current_token().cloned() {
582 match token {
583 Token::Number(n) => {
584 self.advance();
585 Ok(ASTNode::Number(n))
586 }
587 Token::Identifier(name) => {
588 self.advance();
589
590 if self.current_token() == Some(&Token::Dot) {
592 self.advance();
593 if let Some(Token::Identifier(field)) = self.current_token().cloned() {
594 self.advance();
595
596 if self.current_token() == Some(&Token::LeftParen) {
598 self.advance();
599 let args = self.parse_arguments()?;
600 self.expect(Token::RightParen)?;
601 return Ok(ASTNode::FunctionCall {
602 name: format!("{}.{}", name, field),
603 args,
604 });
605 } else {
606 return Ok(ASTNode::FieldAccess {
607 object: name,
608 field,
609 });
610 }
611 }
612 }
613
614 Ok(ASTNode::Identifier(name))
615 }
616 Token::LeftParen => {
617 self.advance();
618 let expr = self.parse_expression()?;
619 self.expect(Token::RightParen)?;
620 Ok(expr)
621 }
622 _ => Err(PdfError::InvalidFormat("Unexpected token".to_string())),
623 }
624 } else {
625 Err(PdfError::InvalidFormat(
626 "Unexpected end of input".to_string(),
627 ))
628 }
629 }
630
631 fn parse_arguments(&mut self) -> Result<Vec<ASTNode>, PdfError> {
632 let mut args = Vec::new();
633
634 if self.current_token() != Some(&Token::RightParen) {
635 loop {
636 args.push(self.parse_expression()?);
637
638 if self.current_token() == Some(&Token::Comma) {
639 self.advance();
640 } else {
641 break;
642 }
643 }
644 }
645
646 Ok(args)
647 }
648
649 fn current_token(&self) -> Option<&Token> {
650 self.tokens.get(self.current)
651 }
652
653 fn advance(&mut self) {
654 self.current += 1;
655 }
656
657 fn expect(&mut self, expected: Token) -> Result<(), PdfError> {
658 if self.current_token() == Some(&expected) {
659 self.advance();
660 Ok(())
661 } else {
662 Err(PdfError::InvalidFormat(format!(
663 "Expected {:?}, got {:?}",
664 expected,
665 self.current_token()
666 )))
667 }
668 }
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674
675 #[test]
676 fn test_simple_arithmetic() {
677 let engine = JavaScriptEngine::new();
678
679 assert_eq!(engine.evaluate("2 + 3").unwrap(), 5.0);
680 assert_eq!(engine.evaluate("10 - 4").unwrap(), 6.0);
681 assert_eq!(engine.evaluate("3 * 4").unwrap(), 12.0);
682 assert_eq!(engine.evaluate("15 / 3").unwrap(), 5.0);
683 }
684
685 #[test]
686 fn test_parentheses() {
687 let engine = JavaScriptEngine::new();
688
689 assert_eq!(engine.evaluate("2 * (3 + 4)").unwrap(), 14.0);
690 assert_eq!(engine.evaluate("(10 - 2) / 4").unwrap(), 2.0);
691 }
692
693 #[test]
694 fn test_variables() {
695 let mut engine = JavaScriptEngine::new();
696 engine.set_variable("x", 10.0);
697 engine.set_variable("y", 5.0);
698
699 assert_eq!(engine.evaluate("x + y").unwrap(), 15.0);
700 assert_eq!(engine.evaluate("x * 2 - y").unwrap(), 15.0);
701 }
702
703 #[test]
704 fn test_comparison() {
705 let engine = JavaScriptEngine::new();
706
707 assert_eq!(engine.evaluate("5 > 3").unwrap(), 1.0);
708 assert_eq!(engine.evaluate("2 < 1").unwrap(), 0.0);
709 assert_eq!(engine.evaluate("3 == 3").unwrap(), 1.0);
710 assert_eq!(engine.evaluate("4 != 4").unwrap(), 0.0);
711 }
712
713 #[test]
714 fn test_logical_operators() {
715 let engine = JavaScriptEngine::new();
716
717 assert_eq!(engine.evaluate("1 && 1").unwrap(), 1.0);
718 assert_eq!(engine.evaluate("1 && 0").unwrap(), 0.0);
719 assert_eq!(engine.evaluate("0 || 1").unwrap(), 1.0);
720 assert_eq!(engine.evaluate("0 || 0").unwrap(), 0.0);
721 }
722
723 #[test]
724 fn test_math_functions() {
725 let engine = JavaScriptEngine::new();
726
727 assert_eq!(engine.evaluate("Math.min(5, 3, 7)").unwrap(), 3.0);
728 assert_eq!(engine.evaluate("Math.max(5, 3, 7)").unwrap(), 7.0);
729 assert_eq!(engine.evaluate("Math.round(3.7)").unwrap(), 4.0);
730 assert_eq!(engine.evaluate("Math.floor(3.7)").unwrap(), 3.0);
731 assert_eq!(engine.evaluate("Math.ceil(3.2)").unwrap(), 4.0);
732 assert_eq!(engine.evaluate("Math.abs(-5)").unwrap(), 5.0);
733 }
734
735 #[test]
740 fn test_division_by_zero() {
741 let engine = JavaScriptEngine::new();
742
743 let result = engine
745 .evaluate("10 / 0")
746 .expect("Division by zero must not panic");
747 assert_eq!(result, 0.0, "Division by zero must return 0.0");
748
749 let result = engine
751 .evaluate("(5 + 5) / (2 - 2)")
752 .expect("Must handle division by zero");
753 assert_eq!(result, 0.0);
754 }
755
756 #[test]
757 fn test_unary_minus_operator() {
758 let engine = JavaScriptEngine::new();
759
760 assert_eq!(
762 engine.evaluate("-5").unwrap(),
763 -5.0,
764 "Unary minus must negate"
765 );
766
767 assert_eq!(engine.evaluate("-(3 + 2)").unwrap(), -5.0);
769
770 assert_eq!(
772 engine.evaluate("--7").unwrap(),
773 7.0,
774 "Double negation must cancel out"
775 );
776
777 assert_eq!(engine.evaluate("10 + -5").unwrap(), 5.0);
779 }
780
781 #[test]
782 fn test_unary_not_operator() {
783 let engine = JavaScriptEngine::new();
784
785 assert_eq!(engine.evaluate("!0").unwrap(), 1.0, "NOT of 0 must be 1");
787
788 assert_eq!(engine.evaluate("!5").unwrap(), 0.0, "NOT of 5 must be 0");
790
791 assert_eq!(
793 engine.evaluate("!!10").unwrap(),
794 1.0,
795 "Double NOT of truthy must be 1"
796 );
797
798 assert_eq!(engine.evaluate("!0 && 1").unwrap(), 1.0);
800 }
801
802 #[test]
803 fn test_line_comments() {
804 let engine = JavaScriptEngine::new();
805
806 let result = engine
808 .evaluate("5 + 3 // This is a comment")
809 .expect("Comments must be ignored");
810 assert_eq!(result, 8.0, "Comments must not affect evaluation");
811
812 let result = engine
814 .evaluate("10 // comment\n * 2")
815 .expect("Comments must be skipped");
816 assert_eq!(result, 20.0);
817 }
818
819 #[test]
820 fn test_field_getter_integration() {
821 let mut engine = JavaScriptEngine::new();
822
823 fn test_getter(field_name: &str) -> Option<f64> {
825 match field_name {
826 "price" => Some(100.0),
827 "quantity" => Some(5.0),
828 "tax" => Some(0.08),
829 _ => None,
830 }
831 }
832
833 engine.set_field_getter(test_getter);
834
835 assert_eq!(
837 engine.evaluate("price").unwrap(),
838 100.0,
839 "Field getter must resolve field names"
840 );
841
842 assert_eq!(engine.evaluate("price * quantity").unwrap(), 500.0);
843
844 assert_eq!(engine.evaluate("price * (1 + tax)").unwrap(), 108.0);
846
847 assert_eq!(
849 engine.evaluate("nonexistent").unwrap(),
850 0.0,
851 "Unknown fields must return 0.0"
852 );
853 }
854
855 #[test]
856 fn test_this_field_access() {
857 let mut engine = JavaScriptEngine::new();
858
859 fn field_getter(field_name: &str) -> Option<f64> {
860 match field_name {
861 "total" => Some(250.0),
862 "discount" => Some(0.10),
863 _ => None,
864 }
865 }
866
867 engine.set_field_getter(field_getter);
868
869 let result = engine.evaluate("this.total");
873 assert!(result.is_ok(), "this.field syntax must be supported");
874 }
875
876 #[test]
877 fn test_math_functions_with_empty_args() {
878 let engine = JavaScriptEngine::new();
879
880 let result = engine
882 .evaluate("Math.round()")
883 .expect("Empty args must not panic");
884 assert_eq!(result, 0.0, "Math.round() with no args must return 0.0");
885
886 let result = engine
887 .evaluate("Math.floor()")
888 .expect("Empty args must not panic");
889 assert_eq!(result, 0.0);
890
891 let result = engine
892 .evaluate("Math.ceil()")
893 .expect("Empty args must not panic");
894 assert_eq!(result, 0.0);
895
896 let result = engine
897 .evaluate("Math.abs()")
898 .expect("Empty args must not panic");
899 assert_eq!(result, 0.0);
900 }
901
902 #[test]
903 fn test_math_min_max_with_single_value() {
904 let engine = JavaScriptEngine::new();
905
906 assert_eq!(
908 engine.evaluate("Math.min(42)").unwrap(),
909 42.0,
910 "Math.min with single arg must return that value"
911 );
912
913 assert_eq!(engine.evaluate("Math.max(42)").unwrap(), 42.0);
914 }
915
916 #[test]
917 fn test_complex_nested_expressions() {
918 let engine = JavaScriptEngine::new();
919
920 let result = engine
922 .evaluate("((((5))))")
923 .expect("Nested parens must work");
924 assert_eq!(result, 5.0);
925
926 let result = engine.evaluate("2 + 3 * 4 - 5 / 5").unwrap();
928 assert_eq!(
929 result, 13.0,
930 "Operator precedence must be correct: 2 + 12 - 1 = 13"
931 );
932
933 let result = engine.evaluate("Math.max(Math.min(10, 5), 3)").unwrap();
935 assert_eq!(result, 5.0, "Math.max(5, 3) = 5");
936 }
937
938 #[test]
939 fn test_comparison_with_equals_vs_assignment() {
940 let engine = JavaScriptEngine::new();
941
942 assert_eq!(
944 engine.evaluate("5 == 5").unwrap(),
945 1.0,
946 "== must be comparison returning 1.0 (true)"
947 );
948
949 assert_eq!(
950 engine.evaluate("5 == 3").unwrap(),
951 0.0,
952 "== must return 0.0 (false) for unequal values"
953 );
954
955 assert_eq!(engine.evaluate("5 <= 5").unwrap(), 1.0);
957 assert_eq!(engine.evaluate("5 >= 5").unwrap(), 1.0);
958 assert_eq!(engine.evaluate("3 <= 5").unwrap(), 1.0);
959 assert_eq!(engine.evaluate("5 >= 3").unwrap(), 1.0);
960 }
961
962 #[test]
963 fn test_logical_operators_chaining() {
964 let engine = JavaScriptEngine::new();
965
966 assert_eq!(
968 engine.evaluate("1 && 1 && 1").unwrap(),
969 1.0,
970 "All truthy values ANDed must return 1.0"
971 );
972
973 assert_eq!(
974 engine.evaluate("1 && 0 && 1").unwrap(),
975 0.0,
976 "Any falsy value ANDed must return 0.0"
977 );
978
979 assert_eq!(
981 engine.evaluate("0 || 0 || 1").unwrap(),
982 1.0,
983 "Any truthy value ORed must return 1.0"
984 );
985
986 assert_eq!(engine.evaluate("0 || 0 || 0").unwrap(), 0.0);
987
988 assert_eq!(
990 engine.evaluate("1 && 1 || 0").unwrap(),
991 1.0,
992 "(1 AND 1) OR 0 = 1"
993 );
994
995 assert_eq!(
996 engine.evaluate("0 || 1 && 1").unwrap(),
997 1.0,
998 "0 OR (1 AND 1) = 1"
999 );
1000 }
1001
1002 #[test]
1003 fn test_whitespace_handling() {
1004 let engine = JavaScriptEngine::new();
1005
1006 assert_eq!(
1008 engine.evaluate(" 5 + 3 ").unwrap(),
1009 8.0,
1010 "Extra whitespace must be ignored"
1011 );
1012
1013 assert_eq!(engine.evaluate("5\t+\n3").unwrap(), 8.0);
1015
1016 assert_eq!(engine.evaluate("5+3*2").unwrap(), 11.0);
1018 }
1019
1020 #[test]
1021 fn test_decimal_numbers() {
1022 let engine = JavaScriptEngine::new();
1023
1024 assert_eq!(engine.evaluate("3.14").unwrap(), 3.14);
1026
1027 assert_eq!(engine.evaluate("10.5 + 2.5").unwrap(), 13.0);
1029
1030 assert_eq!(engine.evaluate("0.1 + 0.2").unwrap(), 0.30000000000000004); assert_eq!(engine.evaluate("0.5 * 10").unwrap(), 5.0);
1035 }
1036
1037 #[test]
1038 fn test_error_handling_invalid_syntax() {
1039 let engine = JavaScriptEngine::new();
1040
1041 let result = engine.evaluate("5 +");
1043 assert!(result.is_err(), "Incomplete expression must return error");
1044
1045 let result = engine.evaluate("(5 + 3");
1047 assert!(result.is_err(), "Unclosed parenthesis must return error");
1048
1049 let result = engine.evaluate("");
1051 assert!(result.is_err(), "Empty expression must return error");
1052 }
1053
1054 #[test]
1055 fn test_unknown_function() {
1056 let engine = JavaScriptEngine::new();
1057
1058 let result = engine
1060 .evaluate("UnknownFunction()")
1061 .expect("Unknown functions must not panic");
1062 assert_eq!(result, 0.0, "Unknown functions must return 0.0");
1063
1064 let result = engine
1066 .evaluate("Math.unknownFunc(5)")
1067 .expect("Must not panic");
1068 assert_eq!(result, 0.0);
1069 }
1070}