use rstest::*; use steel_decimal::{SteelDecimal, FunctionRegistry, FunctionRegistryBuilder}; use steel::steel_vm::engine::Engine; use steel::rvals::SteelVal; use std::collections::HashMap; #[fixture] fn steel_decimal_instance() -> SteelDecimal { SteelDecimal::new() } #[fixture] fn vm_with_functions() -> Engine { let mut vm = Engine::new(); FunctionRegistry::register_all(&mut vm); vm } // End-to-End Transformation and Execution Tests #[rstest] #[case("(+ 1.5 2.3)", "3.8")] #[case("(- 10 4)", "6")] #[case("(* 3 4)", "12")] #[case("(/ 15 3)", "5")] fn test_end_to_end_basic_arithmetic(steel_decimal_instance: SteelDecimal, #[case] input: &str, #[case] expected: &str) { let result = steel_decimal_instance.parse_and_execute(input).unwrap(); // Should return a single value assert_eq!(result.len(), 1); // Extract the string value if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), expected); } else { panic!("Expected StringV, got {:?}", result[0]); } } #[rstest] #[case("(+ (* 2 3) (/ 12 4))", "9")] #[case("(- (+ 10 5) (* 2 3))", "9")] #[case("(* (+ 2 3) (- 8 3))", "25")] fn test_end_to_end_complex_expressions(steel_decimal_instance: SteelDecimal, #[case] input: &str, #[case] expected: &str) { let result = steel_decimal_instance.parse_and_execute(input).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), expected); } else { panic!("Expected StringV, got {:?}", result[0]); } } // Test with variables #[rstest] fn test_end_to_end_with_variables() { let mut variables = HashMap::new(); variables.insert("x".to_string(), "10".to_string()); variables.insert("y".to_string(), "5".to_string()); let steel_decimal_instance = SteelDecimal::with_variables(variables); let result = steel_decimal_instance.parse_and_execute("(+ $x $y)").unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), "15"); } else { panic!("Expected StringV, got {:?}", result[0]); } } // Test transformation only #[rstest] #[case("(+ 1 2)", "(decimal-add \"1\" \"2\")")] #[case("(* $x $y)", "(decimal-mul (get-var \"x\") (get-var \"y\"))")] #[case("(sqrt 16)", "(decimal-sqrt \"16\")")] fn test_transformation_only(steel_decimal_instance: SteelDecimal, #[case] input: &str, #[case] expected: &str) { let result = steel_decimal_instance.transform(input); assert_eq!(result, expected); } // Test function registration #[rstest] fn test_function_registration_with_vm() { let mut vm = Engine::new(); FunctionRegistry::register_all(&mut vm); // Test that we can execute decimal functions directly let script = r#"(decimal-add "2.5" "3.7")"#; let result = vm.compile_and_run_raw_program(script.to_string()).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), "6.2"); } else { panic!("Expected StringV, got {:?}", result[0]); } } // Test selective function registration #[rstest] fn test_selective_function_registration() { let mut vm = Engine::new(); FunctionRegistryBuilder::new() .basic_arithmetic(true) .advanced_math(false) .trigonometric(false) .comparison(true) .utility(false) .constants(true) .financial(false) .conversion(true) .register(&mut vm); // Basic arithmetic should work let script = r#"(decimal-add "1" "2")"#; let result = vm.compile_and_run_raw_program(script.to_string()); assert!(result.is_ok()); // Constants should work let script = r#"(decimal-pi)"#; let result = vm.compile_and_run_raw_program(script.to_string()); assert!(result.is_ok()); // Comparison should work let script = r#"(decimal-gt "5" "3")"#; let result = vm.compile_and_run_raw_program(script.to_string()); assert!(result.is_ok()); } // Test variable registration #[rstest] fn test_variable_registration() { let mut vm = Engine::new(); let mut variables = HashMap::new(); variables.insert("test_var".to_string(), "42.5".to_string()); variables.insert("another_var".to_string(), "10.0".to_string()); FunctionRegistryBuilder::new() .basic_arithmetic(true) .with_variables(variables) .register(&mut vm); // Test getting a variable let script = r#"(get-var "test_var")"#; let result = vm.compile_and_run_raw_program(script.to_string()).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), "42.5"); } else { panic!("Expected StringV, got {:?}", result[0]); } // Test checking if variable exists let script = r#"(has-var? "test_var")"#; let result = vm.compile_and_run_raw_program(script.to_string()).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::BoolV(b) = &result[0] { assert!(b); } else { panic!("Expected BoolV, got {:?}", result[0]); } // Test non-existent variable let script = r#"(has-var? "nonexistent")"#; let result = vm.compile_and_run_raw_program(script.to_string()).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::BoolV(b) = &result[0] { assert!(!b); } else { panic!("Expected BoolV, got {:?}", result[0]); } } // Test script validation #[rstest] #[case("(+ 1 2)", true, "Valid script")] #[case("(+ 1 2", false, "Unbalanced parentheses")] #[case("(+ $undefined_var 2)", false, "Undefined variable")] fn test_script_validation(#[case] script: &str, #[case] should_be_valid: bool, #[case] _description: &str) { let steel_decimal_instance = SteelDecimal::new(); let result = steel_decimal_instance.validate_script(script); if should_be_valid { assert!(result.is_ok(), "Script should be valid: {}", script); } else { assert!(result.is_err(), "Script should be invalid: {}", script); } } // Test with defined variables #[rstest] fn test_script_validation_with_variables() { let mut variables = HashMap::new(); variables.insert("x".to_string(), "10".to_string()); variables.insert("y".to_string(), "20".to_string()); let steel_decimal_instance = SteelDecimal::with_variables(variables); // Should be valid now assert!(steel_decimal_instance.validate_script("(+ $x $y)").is_ok()); // Still invalid variable assert!(steel_decimal_instance.validate_script("(+ $x $undefined)").is_err()); } // Test dependency extraction #[rstest] #[case("(+ $x $y)", vec!["x", "y"])] #[case("(* $price $quantity)", vec!["price", "quantity"])] #[case("(+ 1 2)", vec![])] fn test_dependency_extraction(steel_decimal_instance: SteelDecimal, #[case] script: &str, #[case] expected_deps: Vec<&str>) { let deps = steel_decimal_instance.extract_dependencies(script); let expected: std::collections::HashSet = expected_deps.into_iter().map(|s| s.to_string()).collect(); assert_eq!(deps, expected); } // Test adding variables dynamically #[rstest] fn test_dynamic_variable_addition() { let mut steel_decimal_instance = SteelDecimal::new(); // Should fail initially assert!(steel_decimal_instance.validate_script("(+ $x $y)").is_err()); // Add variables steel_decimal_instance.add_variable("x".to_string(), "10".to_string()); steel_decimal_instance.add_variable("y".to_string(), "20".to_string()); // Should work now assert!(steel_decimal_instance.validate_script("(+ $x $y)").is_ok()); // Test execution let result = steel_decimal_instance.parse_and_execute("(+ $x $y)").unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), "30"); } } // Test error handling #[rstest] fn test_error_handling() { let steel_decimal_instance = SteelDecimal::new(); // Test division by zero let result = steel_decimal_instance.parse_and_execute("(/ 10 0)"); assert!(result.is_err()); assert!(result.unwrap_err().contains("Division by zero")); } // Test complex mathematical expressions #[rstest] #[case("(sqrt (+ (* 3 3) (* 4 4)))", "5")] // Pythagorean theorem: sqrt(3² + 4²) = 5 #[case("(abs (- 10 15))", "5")] // |10 - 15| = 5 #[case("(max (min 10 5) 3)", "5")] // max(min(10, 5), 3) = max(5, 3) = 5 fn test_complex_mathematical_expressions(steel_decimal_instance: SteelDecimal, #[case] input: &str, #[case] expected: &str) { let result = steel_decimal_instance.parse_and_execute(input).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { if input.contains("sqrt") { // For sqrt, just check it starts with the expected digit due to high precision assert!(s.to_string().starts_with(expected), "Expected sqrt result to start with {}, got: {}", expected, s); } else { assert_eq!(s.to_string(), expected); } } else { panic!("Expected StringV, got {:?}", result[0]); } } // Test financial calculations #[rstest] fn test_financial_calculations() { let steel_decimal_instance = SteelDecimal::new(); // Test percentage calculation let result = steel_decimal_instance.parse_and_execute("(decimal-percentage \"1000\" \"15\")").unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), "150"); } // Test compound interest (simplified) - expect precision from rust_decimal let result = steel_decimal_instance.parse_and_execute("(decimal-compound \"1000\" \"0.05\" \"2\")").unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), "1102.5000"); // 1000 * (1.05)^2 = 1102.5000 } } // Test constants #[rstest] fn test_constants_integration() { let steel_decimal_instance = SteelDecimal::new(); // Test pi let result = steel_decimal_instance.parse_and_execute("(decimal-pi)").unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert!(s.to_string().starts_with("3.14159")); } // Test using pi in calculation let result = steel_decimal_instance.parse_and_execute("(decimal-mul (decimal-pi) \"2\")").unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert!(s.to_string().starts_with("6.28")); } }