use rstest::*; use steel_decimal::{FunctionRegistry, FunctionRegistryBuilder}; use steel::steel_vm::engine::Engine; use steel::rvals::SteelVal; use std::collections::HashMap; #[fixture] fn fresh_vm() -> Engine { Engine::new() } // Test basic function registration #[rstest] fn test_register_all_functions(mut fresh_vm: Engine) { FunctionRegistry::register_all(&mut fresh_vm); // Test that all major function categories work let scripts = vec![ r#"(decimal-add "1" "2")"#, r#"(decimal-pow "2" "3")"#, r#"(decimal-sin "0")"#, r#"(decimal-gt "5" "3")"#, r#"(decimal-abs "-5")"#, r#"(decimal-zero)"#, r#"(decimal-percentage "100" "10")"#, r#"(to-decimal "123.45")"#, ]; for script in scripts { let result = fresh_vm.compile_and_run_raw_program(script.to_string()); assert!(result.is_ok(), "Failed to execute: {}", script); } } // Test selective function registration #[rstest] fn test_basic_arithmetic_only(mut fresh_vm: Engine) { FunctionRegistryBuilder::new() .basic_arithmetic(true) .advanced_math(false) .trigonometric(false) .comparison(false) .utility(false) .constants(false) .financial(false) .conversion(false) .register(&mut fresh_vm); // Basic arithmetic should work let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-add "1" "2")"#.to_string()); assert!(result.is_ok()); let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-mul "3" "4")"#.to_string()); assert!(result.is_ok()); // Note: Advanced math, trig, etc. won't be available but we can't easily test // their absence without expecting compilation/runtime errors } #[rstest] fn test_advanced_math_only(mut fresh_vm: Engine) { FunctionRegistryBuilder::new() .basic_arithmetic(false) .advanced_math(true) .trigonometric(false) .comparison(false) .utility(false) .constants(false) .financial(false) .conversion(false) .register(&mut fresh_vm); // Advanced math should work let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-pow "2" "3")"#.to_string()); assert!(result.is_ok()); let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-sqrt "16")"#.to_string()); assert!(result.is_ok()); } #[rstest] fn test_trigonometric_only(mut fresh_vm: Engine) { FunctionRegistryBuilder::new() .basic_arithmetic(false) .advanced_math(false) .trigonometric(true) .comparison(false) .utility(false) .constants(false) .financial(false) .conversion(false) .register(&mut fresh_vm); // Trigonometric functions should work let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-sin "0")"#.to_string()); assert!(result.is_ok()); let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-cos "0")"#.to_string()); assert!(result.is_ok()); } #[rstest] fn test_comparison_only(mut fresh_vm: Engine) { FunctionRegistryBuilder::new() .basic_arithmetic(false) .advanced_math(false) .trigonometric(false) .comparison(true) .utility(false) .constants(false) .financial(false) .conversion(false) .register(&mut fresh_vm); // Comparison functions should work let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-gt "5" "3")"#.to_string()); assert!(result.is_ok()); let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-eq "5" "5")"#.to_string()); assert!(result.is_ok()); } #[rstest] fn test_utility_only(mut fresh_vm: Engine) { FunctionRegistryBuilder::new() .basic_arithmetic(false) .advanced_math(false) .trigonometric(false) .comparison(false) .utility(true) .constants(false) .financial(false) .conversion(false) .register(&mut fresh_vm); // Utility functions should work let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-abs "-5")"#.to_string()); assert!(result.is_ok()); let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-min "3" "5")"#.to_string()); assert!(result.is_ok()); } #[rstest] fn test_constants_only(mut fresh_vm: Engine) { FunctionRegistryBuilder::new() .basic_arithmetic(false) .advanced_math(false) .trigonometric(false) .comparison(false) .utility(false) .constants(true) .financial(false) .conversion(false) .register(&mut fresh_vm); // Constants should work let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-pi)"#.to_string()); assert!(result.is_ok()); let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-zero)"#.to_string()); assert!(result.is_ok()); } #[rstest] fn test_financial_only(mut fresh_vm: Engine) { FunctionRegistryBuilder::new() .basic_arithmetic(false) .advanced_math(false) .trigonometric(false) .comparison(false) .utility(false) .constants(false) .financial(true) .conversion(false) .register(&mut fresh_vm); // Financial functions should work let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-percentage "100" "10")"#.to_string()); assert!(result.is_ok()); let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-compound "1000" "0.05" "1")"#.to_string()); assert!(result.is_ok()); } #[rstest] fn test_conversion_only(mut fresh_vm: Engine) { FunctionRegistryBuilder::new() .basic_arithmetic(false) .advanced_math(false) .trigonometric(false) .comparison(false) .utility(false) .constants(false) .financial(false) .conversion(true) .register(&mut fresh_vm); // Conversion functions should work let result = fresh_vm.compile_and_run_raw_program(r#"(to-decimal "123.45")"#.to_string()); assert!(result.is_ok()); } // Test variable registration #[rstest] fn test_variable_registration(mut fresh_vm: Engine) { let mut variables = HashMap::new(); variables.insert("x".to_string(), "10.5".to_string()); variables.insert("y".to_string(), "20.3".to_string()); variables.insert("name".to_string(), "test_value".to_string()); FunctionRegistryBuilder::new() .basic_arithmetic(true) .with_variables(variables) .register(&mut fresh_vm); // Test getting variables let result = fresh_vm.compile_and_run_raw_program(r#"(get-var "x")"#.to_string()).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), "10.5"); } let result = fresh_vm.compile_and_run_raw_program(r#"(get-var "y")"#.to_string()).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), "20.3"); } // Test checking if variables exist let result = fresh_vm.compile_and_run_raw_program(r#"(has-var? "x")"#.to_string()).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::BoolV(b) = &result[0] { assert!(b); } let result = fresh_vm.compile_and_run_raw_program(r#"(has-var? "nonexistent")"#.to_string()).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::BoolV(b) = &result[0] { assert!(!b); } // Test using variables in arithmetic let result = fresh_vm.compile_and_run_raw_program(r#"(decimal-add (get-var "x") (get-var "y"))"#.to_string()).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), "30.8"); } } // Test error handling in variable access #[rstest] fn test_variable_error_handling(mut fresh_vm: Engine) { let variables = HashMap::new(); // Empty variables FunctionRegistryBuilder::new() .with_variables(variables) .register(&mut fresh_vm); // Should get an error for non-existent variable let result = fresh_vm.compile_and_run_raw_program(r#"(get-var "nonexistent")"#.to_string()); // The function should return an error, but Steel might handle it differently // This test ensures the function is registered and callable assert!(result.is_ok() || result.is_err()); // Either way is fine, just shouldn't panic } // Test function name listing #[rstest] fn test_function_names() { let names = FunctionRegistry::get_function_names(); // Check that all expected function categories are present assert!(names.contains(&"decimal-add")); assert!(names.contains(&"decimal-pow")); assert!(names.contains(&"decimal-sin")); assert!(names.contains(&"decimal-gt")); assert!(names.contains(&"decimal-abs")); assert!(names.contains(&"decimal-pi")); assert!(names.contains(&"decimal-percentage")); assert!(names.contains(&"to-decimal")); assert!(names.contains(&"get-var")); assert!(names.contains(&"has-var?")); // Check total count is reasonable assert!(names.len() >= 25); // Should have at least 25 functions } // Test builder pattern combinations #[rstest] fn test_builder_combinations(mut fresh_vm: Engine) { FunctionRegistryBuilder::new() .basic_arithmetic(true) .comparison(true) .constants(true) .register(&mut fresh_vm); // Should be able to use arithmetic, comparison, and constants let result = fresh_vm.compile_and_run_raw_program( r#"(decimal-gt (decimal-add "1" "2") (decimal-zero))"#.to_string() ).unwrap(); assert_eq!(result.len(), 1); if let SteelVal::BoolV(b) = &result[0] { assert!(b); // 3 > 0 should be true } } // Test default builder behavior #[rstest] fn test_builder_defaults(mut fresh_vm: Engine) { // Default should include everything FunctionRegistryBuilder::new().register(&mut fresh_vm); // Should be able to use any function let scripts = vec![ r#"(decimal-add "1" "2")"#, r#"(decimal-pow "2" "3")"#, r#"(decimal-sin "0")"#, r#"(decimal-gt "5" "3")"#, r#"(decimal-abs "-5")"#, r#"(decimal-pi)"#, r#"(decimal-percentage "100" "10")"#, r#"(to-decimal "123.45")"#, ]; for script in scripts { let result = fresh_vm.compile_and_run_raw_program(script.to_string()); assert!(result.is_ok(), "Failed to execute with default builder: {}", script); } } // Test multiple variable sets #[rstest] fn test_multiple_variable_registration(mut fresh_vm: Engine) { let mut variables1 = HashMap::new(); variables1.insert("a".to_string(), "1".to_string()); variables1.insert("b".to_string(), "2".to_string()); // Register first set FunctionRegistryBuilder::new() .with_variables(variables1) .register(&mut fresh_vm); // Test first set works let result = fresh_vm.compile_and_run_raw_program(r#"(get-var "a")"#.to_string()).unwrap(); if let SteelVal::StringV(s) = &result[0] { assert_eq!(s.to_string(), "1"); } // Note: In a real scenario, you probably wouldn't register variables twice // This is just testing that the registration mechanism works }