ruchy/runtime/
object_helpers.rs1use super::{InterpreterError, Value};
5use std::collections::HashMap;
6use std::sync::{Arc, Mutex};
7
8#[inline]
11pub fn is_mutable_object(value: &Value) -> bool {
12 matches!(value, Value::ObjectMut(_))
13}
14
15#[inline]
18pub fn is_object(value: &Value) -> bool {
19 matches!(value, Value::Object(_) | Value::ObjectMut(_))
20}
21
22pub fn get_object_field(value: &Value, field: &str) -> Option<Value> {
44 match value {
45 Value::Object(map) => map.get(field).cloned(),
46 Value::ObjectMut(cell) => cell.lock().unwrap().get(field).cloned(),
47 _ => None,
48 }
49}
50
51pub fn set_object_field(
75 value: &Value,
76 field: &str,
77 new_value: Value,
78) -> Result<(), InterpreterError> {
79 match value {
80 Value::Object(_) => Err(InterpreterError::RuntimeError(format!(
81 "Cannot mutate immutable object field '{field}'"
82 ))),
83 Value::ObjectMut(cell) => {
84 cell.lock().unwrap().insert(field.to_string(), new_value);
85 Ok(())
86 }
87 _ => Err(InterpreterError::RuntimeError(format!(
88 "Cannot access field '{field}' on non-object"
89 ))),
90 }
91}
92
93#[inline]
112pub fn new_mutable_object(map: HashMap<String, Value>) -> Value {
113 Value::ObjectMut(Arc::new(Mutex::new(map)))
114}
115
116#[inline]
135pub fn new_immutable_object(map: HashMap<String, Value>) -> Value {
136 Value::Object(Arc::new(map))
137}
138
139pub fn to_mutable(value: &Value) -> Value {
159 match value {
160 Value::Object(map) => Value::ObjectMut(Arc::new(Mutex::new((**map).clone()))),
161 Value::ObjectMut(_) => value.clone(),
162 _ => value.clone(),
163 }
164}
165
166pub fn to_immutable(value: &Value) -> Value {
186 match value {
187 Value::ObjectMut(cell) => Value::Object(Arc::new(cell.lock().unwrap().clone())),
188 Value::Object(_) => value.clone(),
189 _ => value.clone(),
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196
197 #[test]
198 fn test_is_mutable_object() {
199 let mut map = HashMap::new();
200 map.insert("key".to_string(), Value::Integer(42));
201
202 let immutable = new_immutable_object(map.clone());
203 let mutable = new_mutable_object(map);
204
205 assert!(!is_mutable_object(&immutable));
206 assert!(is_mutable_object(&mutable));
207 }
208
209 #[test]
210 fn test_is_object() {
211 let mut map = HashMap::new();
212 map.insert("key".to_string(), Value::Integer(42));
213
214 let immutable = new_immutable_object(map.clone());
215 let mutable = new_mutable_object(map);
216
217 assert!(is_object(&immutable));
218 assert!(is_object(&mutable));
219 assert!(!is_object(&Value::Integer(42)));
220 }
221
222 #[test]
223 fn test_get_object_field() {
224 let mut map = HashMap::new();
225 map.insert("key".to_string(), Value::Integer(42));
226
227 let immutable = new_immutable_object(map.clone());
228 let mutable = new_mutable_object(map);
229
230 assert_eq!(
231 get_object_field(&immutable, "key"),
232 Some(Value::Integer(42))
233 );
234 assert_eq!(get_object_field(&mutable, "key"), Some(Value::Integer(42)));
235 assert_eq!(get_object_field(&immutable, "missing"), None);
236 }
237
238 #[test]
239 fn test_set_object_field() {
240 let mut map = HashMap::new();
241 map.insert("key".to_string(), Value::Integer(42));
242
243 let immutable = new_immutable_object(map.clone());
244 let mutable = new_mutable_object(map);
245
246 assert!(set_object_field(&immutable, "key", Value::Integer(99)).is_err());
248
249 assert!(set_object_field(&mutable, "key", Value::Integer(99)).is_ok());
251 assert_eq!(get_object_field(&mutable, "key"), Some(Value::Integer(99)));
252 }
253
254 #[test]
255 fn test_set_new_field() {
256 let map = HashMap::new();
257 let mutable = new_mutable_object(map);
258
259 assert!(set_object_field(&mutable, "new_key", Value::Integer(123)).is_ok());
261 assert_eq!(
262 get_object_field(&mutable, "new_key"),
263 Some(Value::Integer(123))
264 );
265 }
266
267 #[test]
268 fn test_to_mutable() {
269 let mut map = HashMap::new();
270 map.insert("key".to_string(), Value::Integer(42));
271
272 let immutable = new_immutable_object(map);
273 let mutable = to_mutable(&immutable);
274
275 assert!(is_mutable_object(&mutable));
276 assert_eq!(get_object_field(&mutable, "key"), Some(Value::Integer(42)));
277
278 assert!(set_object_field(&mutable, "key", Value::Integer(99)).is_ok());
280 assert_eq!(get_object_field(&mutable, "key"), Some(Value::Integer(99)));
281 }
282
283 #[test]
284 fn test_to_immutable() {
285 let mut map = HashMap::new();
286 map.insert("key".to_string(), Value::Integer(42));
287
288 let mutable = new_mutable_object(map);
289 let immutable = to_immutable(&mutable);
290
291 assert!(!is_mutable_object(&immutable));
292 assert_eq!(
293 get_object_field(&immutable, "key"),
294 Some(Value::Integer(42))
295 );
296 }
297
298 #[test]
299 fn test_to_mutable_idempotent() {
300 let mut map = HashMap::new();
301 map.insert("key".to_string(), Value::Integer(42));
302
303 let mutable1 = new_mutable_object(map);
304 let mutable2 = to_mutable(&mutable1);
305
306 assert!(is_mutable_object(&mutable2));
308 }
309
310 #[test]
311 fn test_non_object_type() {
312 let int_val = Value::Integer(42);
313
314 assert_eq!(get_object_field(&int_val, "key"), None);
316
317 assert!(set_object_field(&int_val, "key", Value::Integer(99)).is_err());
319 }
320}
321
322#[test]
323fn test_to_immutable_object_match_arm() {
324 use std::sync::Arc;
326
327 let mut map = HashMap::new();
328 map.insert("test".to_string(), Value::Integer(42));
329
330 let immutable_obj = Value::Object(Arc::new(map));
331 let result = to_immutable(&immutable_obj);
332
333 if let Value::Object(obj) = result {
335 assert_eq!(obj.get("test"), Some(&Value::Integer(42)));
336 } else {
337 panic!("Should return Object variant");
338 }
339}