use myc::ast::environment::Environment; use myc::ast::types::Value; #[test] fn test_rtl_operators() { let env = Environment::new(); // --- Arithmetic --- assert_eq!(format!("{}", env.run_script("(+ 10 20)").unwrap()), "30"); assert_eq!(format!("{}", env.run_script("(- 20 10)").unwrap()), "10"); assert_eq!(format!("{}", env.run_script("(* 10 20)").unwrap()), "200"); assert_eq!(format!("{}", env.run_script("(/ 20 10)").unwrap()), "2.0"); assert_eq!(format!("{}", env.run_script("(// 20 3)").unwrap()), "6"); assert_eq!(format!("{}", env.run_script("(% 20 3)").unwrap()), "2"); // --- Logic / Bitwise --- assert_eq!( format!("{}", env.run_script("(and true false)").unwrap()), "false" ); assert_eq!( format!("{}", env.run_script("(or true false)").unwrap()), "true" ); assert_eq!( format!("{}", env.run_script("(xor true false)").unwrap()), "true" ); assert_eq!( format!("{}", env.run_script("(not true)").unwrap()), "false" ); assert_eq!(format!("{}", env.run_script("(<< 1 2)").unwrap()), "4"); assert_eq!(format!("{}", env.run_script("(>> 4 1)").unwrap()), "2"); assert_eq!(format!("{}", env.run_script("(and 3 1)").unwrap()), "1"); // --- Comparison --- assert_eq!(format!("{}", env.run_script("(= 10 10)").unwrap()), "true"); assert_eq!(format!("{}", env.run_script("(= 10 20)").unwrap()), "false"); assert_eq!(format!("{}", env.run_script("(<> 10 20)").unwrap()), "true"); assert_eq!(format!("{}", env.run_script("(< 10 20)").unwrap()), "true"); assert_eq!(format!("{}", env.run_script("(> 10 20)").unwrap()), "false"); assert_eq!(format!("{}", env.run_script("(<= 10 10)").unwrap()), "true"); assert_eq!(format!("{}", env.run_script("(>= 10 10)").unwrap()), "true"); // --- NaN --- assert_eq!(format!("{}", env.run_script("NaN").unwrap()), "NaN"); } #[test] fn test_random_isolation_between_environments() { let env1 = Environment::new(); let env2 = Environment::new(); let val1_a = env1.run_script("(do (def rand (make-random 123)) (rand))").unwrap(); let val2_a = env2.run_script("(do (def rand (make-random)) (rand))").unwrap(); assert_ne!( val1_a, val2_a, "Environments must have isolated PRNG states" ); let val2_b = env2.run_script("(do (def rand-same (make-random 123)) (rand-same))").unwrap(); assert_eq!( val1_a, val2_b, "Different environments with the same seed must produce the same sequence" ); } #[test] fn test_random_seeding_determinism() { let env = Environment::new(); let val1 = env.run_script("(do (def rand1 (make-random 42)) (rand1))").unwrap(); let val2 = env.run_script("(do (def rand2 (make-random 42)) (rand2))").unwrap(); assert_eq!(val1, val2, "Random results must be identical for the same seed"); let val3 = env.run_script("(do (def rand3 (make-random 123)) (rand3))").unwrap(); assert_ne!(val1, val3, "Random results must differ for different seeds"); } #[test] fn test_now_function_not_folded() { let env = Environment::new(); let source = "(now)"; let result = env.run_script(source).expect("Failed to run script"); if let Value::DateTime(ts) = result { let current = chrono::Utc::now().timestamp_millis(); assert!(ts > 0); assert!(ts <= current); } else { panic!("Expected DateTime, got {:?}", result); } let dump = env.dump_ast(source).expect("Failed to dump AST"); assert!( dump.contains("Call"), "now() should remain a Call, not a Constant. Dump: \n{}", dump ); assert!( !dump.contains("Constant: #"), "now() should NOT be folded into a specific timestamp constant. Dump: \n{}", dump ); } #[test] fn test_date_parsing() { let env = Environment::new(); let res = env.run_script("(date \"2023-01-01\")").unwrap(); if let Value::DateTime(_) = res { // OK } else { panic!("Expected DateTime, got {:?}", res); } } #[test] fn test_environments_from_shared_rtl_are_independent() { let rtl = Environment::new().rtl(); // Two fresh environments from the same frozen RTL snapshot. let env1 = Environment::from_rtl(rtl.clone()); let env2 = Environment::from_rtl(rtl.clone()); // Define a variable in env1 only. env1.run_script("(do (def x 42) 0)").unwrap(); // x must not be visible in env2. assert!(env2.run_script("x").is_err(), "User binding leaked across environments"); // RTL functions must work in both environments. assert_eq!(format!("{}", env1.run_script("(+ 1 2)").unwrap()), "3"); assert_eq!(format!("{}", env2.run_script("(+ 1 2)").unwrap()), "3"); } // --- Bug fixes for wildcard matches --- /// `type-of` must return :function for native functions (e.g. `print`). /// Previously returned :unknown because Value::Function.static_type() yields Any, /// and the type-of match did not handle Any. #[test] fn test_type_of_native_function() { let env = Environment::new(); assert_eq!( format!("{}", env.run_script("(type-of print)").unwrap()), ":function", ); } /// `type-of` must return :function for closures, not :closure. /// Closures are functions from the user's perspective. #[test] fn test_type_of_closure() { let env = Environment::new(); assert_eq!( format!("{}", env.run_script("(type-of (fn [x] x))").unwrap()), ":function", ); } /// `type-of` must return :field-accessor for field accessors. #[test] fn test_type_of_field_accessor() { let env = Environment::new(); assert_eq!( format!("{}", env.run_script("(type-of .name)").unwrap()), ":field-accessor", ); } /// The `=` operator must compare tuples structurally, not return false. #[test] fn test_equality_tuples() { let env = Environment::new(); // Use def bindings because vector literals in call position get flattened. assert_eq!( format!("{}", env.run_script("(do (def a [1 2 3]) (def b [1 2 3]) (= a b))").unwrap()), "true", ); assert_eq!( format!("{}", env.run_script("(do (def a [1 2 3]) (def b [1 2 4]) (= a b))").unwrap()), "false", ); // Nested tuples assert_eq!( format!("{}", env.run_script("(do (def a [[1 2] [3 4]]) (def b [[1 2] [3 4]]) (= a b))").unwrap()), "true", ); } /// The `<>` operator must compare tuples structurally. #[test] fn test_inequality_tuples() { let env = Environment::new(); assert_eq!( format!("{}", env.run_script("(do (def a [1 2 3]) (def b [1 2 3]) (<> a b))").unwrap()), "false", ); assert_eq!( format!("{}", env.run_script("(do (def a [1 2 3]) (def b [1 2 4]) (<> a b))").unwrap()), "true", ); } /// The `=` operator must compare field accessors by identity. #[test] fn test_equality_field_accessors() { let env = Environment::new(); assert_eq!( format!("{}", env.run_script("(do (def a .name) (def b .name) (= a b))").unwrap()), "true", ); assert_eq!( format!("{}", env.run_script("(do (def a .name) (def b .age) (= a b))").unwrap()), "false", ); } /// The `<>` operator must compare field accessors by identity. #[test] fn test_inequality_field_accessors() { let env = Environment::new(); assert_eq!( format!("{}", env.run_script("(do (def a .name) (def b .age) (<> a b))").unwrap()), "true", ); assert_eq!( format!("{}", env.run_script("(do (def a .name) (def b .name) (<> a b))").unwrap()), "false", ); } /// Arithmetic operators with incompatible types (e.g. Float - Record) /// must be caught at compile time, not silently return Void at runtime. #[test] fn test_arithmetic_type_mismatch_is_compile_error() { let env = Environment::new(); // Float - Record must fail let res = env.run_script("(do (def r {:price 42.0}) (- 10.0 r))"); assert!( res.is_err(), "Float - Record should be a compile error, not silently return Void" ); }