use rhai_wrapper::wrap_for_rhai; use rhai_wrapper::{ToRhaiMap, FromRhaiMap}; use rhai::{CustomType, TypeBuilder, Engine, INT, FLOAT, Array}; use rhai_macros_derive::{ToRhaiMap as ToRhaiMapDerive, FromRhaiMap as FromRhaiMapDerive, export_fn}; #[export_fn(rhai_name = "add_rhai")] fn add(a: INT, b: INT) -> INT { a + b } #[export_fn(rhai_name = "mul_rhai")] fn mul(a: INT, b: INT) -> INT { a * b } #[export_fn(rhai_name = "greet_rhai")] fn greet(name: String) -> String { format!("Hello, {name}!") } #[export_fn(rhai_name = "get_forty_two_rhai")] fn get_forty_two() -> INT { 42 } #[export_fn(rhai_name = "shout_rhai")] fn shout() -> String { "HEY!".to_string() } #[export_fn(rhai_name = "add_float_rhai")] fn add_float(a: FLOAT, b: FLOAT) -> FLOAT { a + b } #[export_fn(rhai_name = "is_even_rhai")] fn is_even(n: INT) -> bool { n % 2 == 0 } #[export_fn(rhai_name = "maybe_add_rhai")] fn maybe_add(a: INT, b: INT, do_add: bool) -> Option { if do_add { Some(a + b) } else { None } } // Renamed from sum_vec, takes rhai::Array fn sum_arr(arr: Array) -> INT { arr.into_iter().map(|x| x.as_int().unwrap_or(0)).sum() } // New sum_vec, takes Vec fn sum_vec(v: Vec) -> INT { v.iter().sum() } fn describe(name: String, age: INT, height: FLOAT) -> String { format!("{name} is {age} years old and {height:.1}m tall.") } fn swap(a: INT, b: INT) -> (INT, INT) { (b, a) } fn join_strings(strings: Vec) -> String { strings.join(", ") } // New function for Vec fn sum_float_vec(floats: Vec) -> FLOAT { floats.iter().sum() } #[derive(Debug, Clone, PartialEq, CustomType, ToRhaiMapDerive, FromRhaiMapDerive)] pub struct Point { pub x: INT, pub y: INT, } // --- Test Custom Struct: Line (contains Point structs) --- #[derive(Debug, Clone, PartialEq, rhai::CustomType, ToRhaiMapDerive, FromRhaiMapDerive)] struct Line { start: Point, end: Point, label: String, } // --- Test Custom Struct: Polygon (contains Vec and Point) --- #[derive(Debug, Clone, PartialEq, rhai::CustomType, ToRhaiMapDerive, FromRhaiMapDerive)] struct Polygon { id: String, vertices: Vec, center_approx: Point, } // New function for Vec fn sum_points(points: Vec) -> INT { points.iter().map(|p| p.x + p.y).sum() } // New function: Vec -> String fn points_to_string(points: Vec) -> String { points.iter() .map(|p| format!("(x:{},y:{})", p.x, p.y)) .collect::>() .join("; ") } // New function: Vec -> Vec fn get_all_x_coordinates(points: Vec) -> Vec { points.iter().map(|p| p.x).collect() } // New function: Vec -> Vec fn make_points_origin_symmetric(points: Vec) -> Vec { points.into_iter().map(|p| Point { x: -p.x, y: -p.y }).collect() } fn get_line_midpoints(lines: Vec) -> Vec { lines.into_iter().map(|line| { Point { x: (line.start.x + line.end.x) / 2, y: (line.start.y + line.end.y) / 2, } }).collect() } fn create_line(p1: Point, p2: Point, label: String) -> Line { Line { start: p1, end: p2, label } } fn create_sample_polygon(id: String, center_x: INT, center_y: INT) -> Polygon { Polygon { id, vertices: vec![ Point { x: center_x - 10, y: center_y - 10 }, Point { x: center_x + 10, y: center_y - 10 }, Point { x: center_x + 10, y: center_y + 10 }, Point { x: center_x - 10, y: center_y + 10 }, ], center_approx: Point { x: center_x, y: center_y }, } } fn get_polygon_id_and_num_vertices(poly: Polygon) -> String { format!("ID: {}, Vertices: {}", poly.id, poly.vertices.len()) } #[test] fn test_add() { let mut engine = Engine::new(); engine.register_fn("add_rhai", add_rhai_wrapper); let result = engine.eval::("add_rhai(2, 3)").unwrap(); assert_eq!(result, 5); } #[test] fn test_mul() { let mut engine = Engine::new(); engine.register_fn("mul_rhai", mul_rhai_wrapper); let result = engine.eval::("mul_rhai(4, 5)").unwrap(); assert_eq!(result, 20); } #[test] fn test_greet() { let mut engine = Engine::new(); engine.register_fn("greet_rhai", greet_rhai_wrapper); let result = engine.eval::(r#"greet_rhai("Alice")"#).unwrap(); assert_eq!(result, "Hello, Alice!"); } #[test] fn test_get_forty_two() { let mut engine = Engine::new(); engine.register_fn("get_forty_two_rhai", get_forty_two_rhai_wrapper); let result = engine.eval::("get_forty_two_rhai()").unwrap(); assert_eq!(result, 42); } #[test] fn test_shout() { let mut engine = Engine::new(); engine.register_fn("shout_rhai", shout_rhai_wrapper); let result = engine.eval::("shout_rhai()").unwrap(); assert_eq!(result, "HEY!"); } #[test] fn test_add_float() { let mut engine = Engine::new(); engine.register_fn("add_float_rhai", add_float_rhai_wrapper); let result = engine.eval::("add_float_rhai(2.5, 3.5)").unwrap(); assert_eq!(result, 6.0); } #[test] fn test_is_even() { let mut engine = Engine::new(); engine.register_fn("is_even_rhai", is_even_rhai_wrapper); let result_true = engine.eval::("is_even_rhai(4)").unwrap(); assert_eq!(result_true, true); let result_false = engine.eval::("is_even_rhai(3)").unwrap(); assert_eq!(result_false, false); } #[test] fn test_maybe_add() { let mut engine = Engine::new(); engine.register_fn("maybe_add_rhai", maybe_add_rhai_wrapper); // Test case where None is returned (expecting an error or specific handling in Rhai) // Rhai treats Option::None as an empty Dynamic, which can lead to type mismatch if not handled. // For now, let's check if the script produces a specific type or if it can be evaluated to Dynamic. // If the function returns None, eval might return an error if trying to cast to INT. // Let's eval to Dynamic and check if it's empty (Rhai's representation of None). let result_none = engine.eval::("maybe_add_rhai(2, 3, false)").unwrap(); // Debug prints println!("Debug [test_maybe_add]: result_none = {:?}", result_none); println!("Debug [test_maybe_add]: result_none.type_name() = {}", result_none.type_name()); println!("Debug [test_maybe_add]: result_none.is::<()>() = {}", result_none.is::<()>()); assert!(result_none.is_unit(), "Expected Rhai None (unit Dynamic)"); // Test case where Some is returned let result_some = engine.eval::("maybe_add_rhai(2, 3, true)").unwrap(); assert_eq!(result_some, 5); } #[test] // Renamed from test_sum_vec fn test_sum_arr() { let mut engine = Engine::new(); engine.register_fn("sum_arr", wrap_for_rhai!(sum_arr)); let result = engine.eval::("sum_arr([1, 2, 3, 4])").unwrap(); assert_eq!(result, 10); } #[test] // Test for the new sum_vec(v: Vec) fn test_sum_vec() { let mut engine = Engine::new(); engine.register_fn("sum_vec", wrap_for_rhai!(sum_vec, Vec -> INT)); let result = engine.eval::("sum_vec([1, 2, 3, 4])").unwrap(); assert_eq!(result, 10); } #[test] fn test_join_strings() { let mut engine = Engine::new(); engine.register_fn("join_strings_rhai", wrap_for_rhai!(join_strings, Vec -> String)); let result = engine.eval::(r#"join_strings_rhai(["hello", "world", "rhai"]);"#).unwrap(); assert_eq!(result, "hello, world, rhai"); } #[test] fn test_sum_float_vec() { let mut engine = Engine::new(); engine.register_fn("sum_float_vec_rhai", wrap_for_rhai!(sum_float_vec, Vec -> FLOAT)); let result = engine.eval::(r#"sum_float_vec_rhai([1.1, 2.2, 3.3]);"#).unwrap(); assert!((result - 6.6).abs() < std::f64::EPSILON); } #[test] fn test_sum_points() { let mut engine = Engine::new(); // Register the Point type with Rhai. The name "Point" will be used in Rhai scripts. // This is crucial for the generic macro arm to work with Vec. engine.build_type::(); engine.register_fn("sum_points_rhai", wrap_for_rhai!(sum_points, Vec -> INT)); let script = r#" sum_points_rhai([ #{ x: 1, y: 2 }, #{ x: 3, y: 4 }, #{ x: 5, y: 6 } ]) "#; let result = engine.eval::(script).unwrap(); assert_eq!(result, 1 + 2 + 3 + 4 + 5 + 6); // 21 } #[test] fn test_points_to_string() { let mut engine = Engine::new(); engine.build_type::(); // Register Point type // Wrap the new function engine.register_fn("points_to_string_rhai", wrap_for_rhai!(points_to_string, Vec -> String)); let script = r#" points_to_string_rhai([ #{ x: 1, y: 2 }, #{ x: 10, y: 20 } ]) "#; let result: String = engine.eval(script).unwrap(); assert_eq!(result, "(x:1,y:2); (x:10,y:20)"); } #[test] fn test_get_all_x_coordinates() { let mut engine = Engine::new(); engine.build_type::(); // Register Point type engine.register_fn("get_all_x_rhai", wrap_for_rhai!(get_all_x_coordinates, Vec -> Vec)); let script = r#" let result = get_all_x_rhai([ #{ x: 1, y: 2 }, #{ x: 10, y: 20 }, #{ x: 50, y: 60 } ]); result // Rhai returns the last expression value "#; let result_array: rhai::Array = engine.eval(script).unwrap(); // Convert rhai::Array to Vec for comparison let result_vec: Vec = result_array.into_iter().map(|d| d.as_int().unwrap()).collect(); assert_eq!(result_vec, vec![1, 10, 50]); } #[test] fn test_make_points_origin_symmetric() { let mut engine = Engine::new(); engine.build_type::(); engine.register_fn("make_symmetric_rhai", wrap_for_rhai!(make_points_origin_symmetric, Vec -> Vec)); let script = r#" let points = [ #{x:1, y:2}, #{x:-3, y:4} ]; make_symmetric_rhai(points) "#; let result_array: rhai::Array = engine.eval(script).unwrap(); assert_eq!(result_array.len(), 2); // Check first point let p1_map = result_array[0].clone().try_cast::().expect("Failed to cast result[0] to Map"); assert_eq!(p1_map.get("x").and_then(|d| d.as_int().ok()).expect("p1.x not found or not INT"), -1); assert_eq!(p1_map.get("y").and_then(|d| d.as_int().ok()).expect("p1.y not found or not INT"), -2); // Check second point let p2_map = result_array[1].clone().try_cast::().expect("Failed to cast result[1] to Map"); assert_eq!(p2_map.get("x").and_then(|d| d.as_int().ok()).expect("p2.x not found or not INT"), 3); assert_eq!(p2_map.get("y").and_then(|d| d.as_int().ok()).expect("p2.y not found or not INT"), -4); } #[test] fn test_describe() { let mut engine = Engine::new(); engine.register_fn("describe", wrap_for_rhai!(describe)); let result = engine.eval::(r#"describe("Bob", 30, 1.8)"#).unwrap(); assert_eq!(result, "Bob is 30 years old and 1.8m tall."); } #[test] fn test_swap() { let mut engine = Engine::new(); engine.register_fn("swap", wrap_for_rhai!(swap)); let result = engine.eval::<(INT, INT)>("swap(1, 2)").unwrap(); assert_eq!(result, (2, 1)); } #[test] fn test_get_line_midpoints() { let mut engine = Engine::new(); engine.build_type::(); engine.build_type::(); engine.register_fn("get_midpoints_rhai", wrap_for_rhai!(get_line_midpoints, Vec -> Vec)); let script = r#" let lines = [ #{ start: #{x:0, y:0}, end: #{x:2, y:2}, label: "A" }, #{ start: #{x:10, y:10}, end: #{x:20, y:30}, label: "B" } ]; get_midpoints_rhai(lines) "#; let result_array: rhai::Array = engine.eval(script).unwrap(); assert_eq!(result_array.len(), 2); // Check first midpoint (Point) let p1_map = result_array[0].clone().try_cast::().expect("Result[0] not a Map for Point"); assert_eq!(p1_map.get("x").and_then(|d| d.as_int().ok()).expect("p1.x not INT"), 1); assert_eq!(p1_map.get("y").and_then(|d| d.as_int().ok()).expect("p1.y not INT"), 1); // Check second midpoint (Point) let p2_map = result_array[1].clone().try_cast::().expect("Result[1] not a Map for Point"); assert_eq!(p2_map.get("x").and_then(|d| d.as_int().ok()).expect("p2.x not INT"), 15); assert_eq!(p2_map.get("y").and_then(|d| d.as_int().ok()).expect("p2.y not INT"), 20); } #[test] fn test_create_line() { let mut engine = Engine::new(); engine.build_type::(); engine.build_type::(); engine.register_fn("create_line_rhai", wrap_for_rhai!(create_line, Point, Point, String -> Line)); let script = r#" let p_start = #{ x: 1, y: 2 }; let p_end = #{ x: 3, y: 4 }; create_line_rhai(p_start, p_end, "MyLine") "#; let result_line_map: rhai::Map = engine.eval(script).unwrap(); // Check label assert_eq!(result_line_map.get("label").unwrap().clone().into_string().unwrap(), "MyLine"); // Check start point let start_map = result_line_map.get("start").unwrap().clone().try_cast::().unwrap(); assert_eq!(start_map.get("x").unwrap().as_int().unwrap(), 1); assert_eq!(start_map.get("y").unwrap().as_int().unwrap(), 2); // Check end point let end_map = result_line_map.get("end").unwrap().clone().try_cast::().unwrap(); assert_eq!(end_map.get("x").unwrap().as_int().unwrap(), 3); assert_eq!(end_map.get("y").unwrap().as_int().unwrap(), 4); } #[test] fn test_create_sample_polygon() { let mut engine = Engine::new(); engine.build_type::(); engine.build_type::(); engine.register_fn("new_polygon_rhai", wrap_for_rhai!(create_sample_polygon, String, INT, INT -> Polygon)); let script = r#" new_polygon_rhai("poly1", 100, 200) "#; let result_poly_map: rhai::Map = engine.eval(script).unwrap(); assert_eq!(result_poly_map.get("id").unwrap().clone().into_string().unwrap(), "poly1"); let center_map = result_poly_map.get("center_approx").unwrap().clone().try_cast::().unwrap(); assert_eq!(center_map.get("x").unwrap().as_int().unwrap(), 100); assert_eq!(center_map.get("y").unwrap().as_int().unwrap(), 200); let vertices_array = result_poly_map.get("vertices").unwrap().clone().try_cast::().unwrap(); assert_eq!(vertices_array.len(), 4); // Check one vertex (e.g., the first one: center_x - 10, center_y - 10 -> 90, 190) let v1_map = vertices_array[0].clone().try_cast::().unwrap(); assert_eq!(v1_map.get("x").unwrap().as_int().unwrap(), 90); assert_eq!(v1_map.get("y").unwrap().as_int().unwrap(), 190); } #[test] fn test_get_polygon_id_and_num_vertices() { let mut engine = Engine::new(); engine.build_type::(); // Needed if Polygon::from_rhai_map reconstructs Points, even if not directly used by this func's signature in Rhai engine.build_type::(); engine.register_fn("poly_info_rhai", wrap_for_rhai!(get_polygon_id_and_num_vertices, Polygon -> String)); let script = r#" let my_poly = #{ id: "test_poly", vertices: [ #{x:0,y:0}, #{x:1,y:0}, #{x:0,y:1} ], center_approx: #{x:0,y:0} }; poly_info_rhai(my_poly) "#; let result_string: String = engine.eval(script).unwrap(); assert_eq!(result_string, "ID: test_poly, Vertices: 3"); } #[test] fn test_polygon_derives() { let original_polygon = Polygon { id: "poly_derive_test".to_string(), vertices: vec![ Point { x: 10, y: 20 }, Point { x: 30, y: 40 }, ], center_approx: Point { x: 20, y: 30 }, }; // Test ToRhaiMap (derived) let rhai_map = original_polygon.to_rhai_map(); // Verify id assert_eq!( rhai_map.get("id").unwrap().clone().into_string().unwrap(), "poly_derive_test" ); // Verify center_approx (which uses Point's derived ToRhaiMap) let center_map_dyn = rhai_map.get("center_approx").unwrap().clone(); let center_map = center_map_dyn.try_cast::().unwrap(); assert_eq!(center_map.get("x").unwrap().as_int().unwrap(), 20); assert_eq!(center_map.get("y").unwrap().as_int().unwrap(), 30); // Verify vertices (Vec) let vertices_array_dyn = rhai_map.get("vertices").unwrap().clone(); let vertices_array = vertices_array_dyn.try_cast::().unwrap(); assert_eq!(vertices_array.len(), 2); let v1_map_dyn = vertices_array[0].clone(); let v1_map = v1_map_dyn.try_cast::().unwrap(); assert_eq!(v1_map.get("x").unwrap().as_int().unwrap(), 10); assert_eq!(v1_map.get("y").unwrap().as_int().unwrap(), 20); let v2_map_dyn = vertices_array[1].clone(); let v2_map = v2_map_dyn.try_cast::().unwrap(); assert_eq!(v2_map.get("x").unwrap().as_int().unwrap(), 30); assert_eq!(v2_map.get("y").unwrap().as_int().unwrap(), 40); // Test FromRhaiMap (derived) let deserialized_polygon = Polygon::from_rhai_map(rhai_map).unwrap(); assert_eq!(original_polygon, deserialized_polygon); assert_eq!(deserialized_polygon.id, "poly_derive_test"); assert_eq!(deserialized_polygon.vertices.len(), 2); assert_eq!(deserialized_polygon.vertices[0], Point { x: 10, y: 20 }); assert_eq!(deserialized_polygon.vertices[1], Point { x: 30, y: 40 }); assert_eq!(deserialized_polygon.center_approx, Point { x: 20, y: 30 }); } #[cfg(test)] mod new_export_fn_tests { use rhai::{Engine, INT, CustomType, TypeBuilder}; use rhai_wrapper::{FromRhaiMap, ToRhaiMap}; use crate::Point; use rhai_macros_derive::{export_fn, FromRhaiMap as FromRhaiMapDerive, ToRhaiMap as ToRhaiMapDerive}; // Define the exported functions directly in this module or ensure they are in scope. // Assuming 'add_for_attr_test' and 'offset_simple_point' are defined here or imported. // Correctly define the SampleStruct and its FromRhaiMap implementation if not already present #[derive(Debug, Clone, PartialEq, FromRhaiMapDerive, ToRhaiMapDerive, CustomType)] struct SampleStruct { value: INT, name: String, } #[export_fn] fn add_for_attr_test(a: INT, b: INT) -> INT { a + b } #[export_fn] fn offset_simple_point(mut pt: Point, dx: INT) -> Point { pt.x += dx; pt } #[test] fn test_export_fn_simple_add() { let mut engine = Engine::new(); engine.register_fn("add_for_attr_test", add_for_attr_test_rhai_wrapper); let result = engine.eval::("add_for_attr_test(5, 10)").unwrap(); assert_eq!(result, 15); } #[test] fn test_export_fn_custom_type_arg_return_new() { let mut engine = Engine::new(); engine.build_type::(); engine.register_fn("offset_simple_point", offset_simple_point_rhai_wrapper); let script = r#" let p = #{ x: 10, y: 20 }; let p_offset = offset_simple_point(p, 5); p_offset.x "#; let result = engine.eval::(script).unwrap(); assert_eq!(result, 15); } #[derive(Debug, Clone, PartialEq, FromRhaiMapDerive, ToRhaiMapDerive, CustomType)] struct AnotherSampleStruct { id: String, value: INT, maybe_value: Option, nested_vec: Vec, optional_nested_vec: Option> } #[test] fn test_from_rhai_map_derive_full_struct() { let mut engine = Engine::new(); engine.build_type::(); engine.build_type::(); let script = r#" let data = #{ id: "test_id", value: 123, maybe_value: 456, nested_vec: [ #{value: 1, name: "n1"}, #{value: 2, name: "n2"} ], optional_nested_vec: [ #{value: 3, name: "n3"} ] }; data // Return the map directly for Rust to process "#; let map = engine.eval::(script).unwrap(); let result_struct = AnotherSampleStruct::from_rhai_map(map).unwrap(); assert_eq!(result_struct.id, "test_id"); assert_eq!(result_struct.value, 123); assert_eq!(result_struct.maybe_value, Some(456)); assert_eq!(result_struct.nested_vec.len(), 2); assert_eq!(result_struct.nested_vec[0], SampleStruct { value: 1, name: "n1".to_string() }); assert_eq!(result_struct.nested_vec[1], SampleStruct { value: 2, name: "n2".to_string() }); assert_eq!(result_struct.optional_nested_vec.as_ref().unwrap().len(), 1); assert_eq!(result_struct.optional_nested_vec.as_ref().unwrap()[0], SampleStruct { value: 3, name: "n3".to_string() }); } }