Skip to content

Commit a92d103

Browse files
committed
Add Atom HeapObject variant wrapping Arc<Mutex<SharedValue>>
1 parent 48bd71f commit a92d103

File tree

3 files changed

+230
-0
lines changed

3 files changed

+230
-0
lines changed

lisp/test_atoms.lisp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
(println "Testing atoms...")
2+
3+
;; Test 1: Basic atom creation and deref
4+
(println "\n=== Test 1: Create and deref ===")
5+
(def counter (atom 0))
6+
(println "Initial value:" (deref counter))
7+
8+
;; Test 2: reset! to change value
9+
(println "\n=== Test 2: reset! ===")
10+
(reset! counter 42)
11+
(println "After reset:" (deref counter))
12+
(reset! counter 100)
13+
(println "After second reset:" (deref counter))
14+
15+
;; Test 3: swap! with increment
16+
(println "\n=== Test 3: swap! with increment ===")
17+
(def num (atom 10))
18+
(println "Initial:" (deref num))
19+
(swap! num + 5)
20+
(println "After (swap! num + 5):" (deref num))
21+
(swap! num + 10)
22+
(println "After (swap! num + 10):" (deref num))
23+
24+
;; Test 4: swap! with multiply
25+
(println "\n=== Test 4: swap! with multiply ===")
26+
(def val (atom 3))
27+
(println "Initial:" (deref val))
28+
(swap! val * 2)
29+
(println "After (swap! val * 2):" (deref val))
30+
(swap! val * 5)
31+
(println "After (swap! val * 5):" (deref val))
32+
33+
;; Test 5: Atoms with strings
34+
(println "\n=== Test 5: Atoms with strings ===")
35+
(def name (atom "Alice"))
36+
(println "Name:" (deref name))
37+
(reset! name "Bob")
38+
(println "After reset:" (deref name))
39+
40+
;; Test 6: Atoms with lists
41+
(println "\n=== Test 6: Atoms with lists ===")
42+
(def items (atom (list 1 2 3)))
43+
(println "Items:" (deref items))
44+
(reset! items (list 4 5 6))
45+
(println "After reset:" (deref items))
46+
47+
;; Test 7: Multiple atoms
48+
(println "\n=== Test 7: Multiple atoms ===")
49+
(def x (atom 1))
50+
(def y (atom 2))
51+
(def z (atom 3))
52+
(println "x:" (deref x) "y:" (deref y) "z:" (deref z))
53+
(swap! x + 10)
54+
(swap! y + 20)
55+
(swap! z + 30)
56+
(println "After swaps - x:" (deref x) "y:" (deref y) "z:" (deref z))
57+
(def sum (+ (deref x) (deref y) (deref z)))
58+
(println "Sum:" sum)
59+
60+
;; Test 8: Atom shared between threads (each thread gets its own reference)
61+
(println "\n=== Test 8: Thread-local atom usage ===")
62+
(def worker (fn ()
63+
(def local-atom (atom 0))
64+
(swap! local-atom + 1)
65+
(swap! local-atom + 2)
66+
(swap! local-atom + 3)
67+
(deref local-atom)))
68+
69+
(def t1 (spawn worker))
70+
(def result (join t1))
71+
(println "Thread result:" result)
72+
73+
;; Test 9: swap! with subtraction
74+
(println "\n=== Test 9: swap! with subtraction ===")
75+
(def balance (atom 100))
76+
(println "Balance:" (deref balance))
77+
(swap! balance - 30)
78+
(println "After withdrawal:" (deref balance))
79+
(swap! balance + 50)
80+
(println "After deposit:" (deref balance))
81+
82+
;; Test 10: Displaying atoms
83+
(println "\n=== Test 10: Display atoms ===")
84+
(def a1 (atom 42))
85+
(def a2 (atom "hello"))
86+
(def a3 (atom (list 1 2 3)))
87+
(println "Atom with int:" a1)
88+
(println "Atom with string:" a2)
89+
(println "Atom with list:" a3)
90+
91+
(println "\n=== All atom tests passed! ===")

src/value.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,9 @@ pub enum HeapObject {
271271
/// Channel receiver for inter-thread communication
272272
/// Wrapped in Arc<Mutex<>> to allow thread-safe access
273273
ChannelReceiver(Arc<Mutex<Receiver<SharedValue>>>),
274+
/// Atomic reference for shared mutable state
275+
/// Wrapped in Arc<Mutex<>> to allow thread-safe mutation
276+
Atom(Arc<Mutex<SharedValue>>),
274277
}
275278

276279
impl Clone for HeapObject {
@@ -286,6 +289,7 @@ impl Clone for HeapObject {
286289
HeapObject::ThreadHandle(h) => HeapObject::ThreadHandle(h.clone()),
287290
HeapObject::ChannelSender(s) => HeapObject::ChannelSender(s.clone()),
288291
HeapObject::ChannelReceiver(r) => HeapObject::ChannelReceiver(r.clone()),
292+
HeapObject::Atom(a) => HeapObject::Atom(a.clone()),
289293
}
290294
}
291295
}
@@ -790,6 +794,7 @@ impl Value {
790794
HeapObject::ThreadHandle(_) => "thread-handle",
791795
HeapObject::ChannelSender(_) => "channel-sender",
792796
HeapObject::ChannelReceiver(_) => "channel-receiver",
797+
HeapObject::Atom(_) => "atom",
793798
}
794799
} else {
795800
"unknown"
@@ -988,6 +993,11 @@ impl Value {
988993
let heap = Rc::new(HeapObject::ChannelReceiver(r.clone()));
989994
Value::from_heap(heap)
990995
}
996+
Some(HeapObject::Atom(a)) => {
997+
// Atoms are already Arc-based, just clone the Value
998+
let heap = Rc::new(HeapObject::Atom(a.clone()));
999+
Value::from_heap(heap)
1000+
}
9911001
None => Value::nil(),
9921002
}
9931003
} else if self.is_ptr() {
@@ -1111,6 +1121,9 @@ impl Value {
11111121
Some(HeapObject::ChannelReceiver(_)) => {
11121122
Err("Channel receivers cannot be converted to SharedValue (they are already thread-safe)".to_string())
11131123
}
1124+
Some(HeapObject::Atom(_)) => {
1125+
Err("Atoms cannot be converted to SharedValue (they are already thread-safe)".to_string())
1126+
}
11141127
None => Err("Cannot convert unknown value to SharedValue".to_string()),
11151128
}
11161129
}
@@ -1252,6 +1265,11 @@ impl fmt::Display for Value {
12521265
HeapObject::ThreadHandle(_) => write!(f, "<thread-handle>"),
12531266
HeapObject::ChannelSender(_) => write!(f, "<channel-sender>"),
12541267
HeapObject::ChannelReceiver(_) => write!(f, "<channel-receiver>"),
1268+
HeapObject::Atom(a) => {
1269+
// Display the current value inside the atom
1270+
let value = a.lock().unwrap();
1271+
write!(f, "(atom {})", value)
1272+
}
12551273
}
12561274
} else {
12571275
write!(f, "<unknown>")

src/vm.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2121,6 +2121,127 @@ pub fn standard_vm() -> VM {
21212121
}
21222122
}));
21232123

2124+
vm.define_global("atom", native("atom", |args| {
2125+
use std::sync::{Arc, Mutex};
2126+
use crate::value::HeapObject;
2127+
2128+
if args.len() != 1 {
2129+
return Err("atom expects 1 argument (initial value)".to_string());
2130+
}
2131+
2132+
// Convert initial value to SharedValue
2133+
let shared_value = args[0].make_shared()
2134+
.map_err(|e| format!("atom failed to convert initial value: {}", e))?;
2135+
2136+
// Create atom with Arc<Mutex<SharedValue>>
2137+
let atom_arc = Arc::new(Mutex::new(shared_value));
2138+
let atom_heap = Rc::new(HeapObject::Atom(atom_arc));
2139+
2140+
Ok(Value::from_heap(atom_heap))
2141+
}));
2142+
2143+
vm.define_global("deref", native("deref", |args| {
2144+
use crate::value::HeapObject;
2145+
2146+
if args.len() != 1 {
2147+
return Err("deref expects 1 argument (atom)".to_string());
2148+
}
2149+
2150+
// Get the atom
2151+
let atom_obj = args[0].as_heap()
2152+
.ok_or_else(|| "deref expects an atom".to_string())?;
2153+
2154+
if let HeapObject::Atom(atom_mutex) = atom_obj {
2155+
// Read the current value
2156+
let shared_value = atom_mutex.lock()
2157+
.map_err(|_| "Failed to lock atom".to_string())?;
2158+
2159+
// Convert back to Rc-based Value
2160+
Ok(Value::from_shared(&shared_value))
2161+
} else {
2162+
Err("deref expects an atom".to_string())
2163+
}
2164+
}));
2165+
2166+
vm.define_global("reset!", native("reset!", |args| {
2167+
use crate::value::HeapObject;
2168+
2169+
if args.len() != 2 {
2170+
return Err("reset! expects 2 arguments (atom new-value)".to_string());
2171+
}
2172+
2173+
// Get the atom
2174+
let atom_obj = args[0].as_heap()
2175+
.ok_or_else(|| "reset! expects an atom as first argument".to_string())?;
2176+
2177+
if let HeapObject::Atom(atom_mutex) = atom_obj {
2178+
// Convert new value to SharedValue
2179+
let new_shared = args[1].make_shared()
2180+
.map_err(|e| format!("reset! failed to convert new value: {}", e))?;
2181+
2182+
// Replace the value
2183+
let mut current = atom_mutex.lock()
2184+
.map_err(|_| "Failed to lock atom".to_string())?;
2185+
*current = new_shared.clone();
2186+
2187+
// Return the new value
2188+
Ok(Value::from_shared(&new_shared))
2189+
} else {
2190+
Err("reset! expects an atom as first argument".to_string())
2191+
}
2192+
}));
2193+
2194+
vm.define_global("swap!", native("swap!", |args| {
2195+
use crate::value::HeapObject;
2196+
2197+
if args.len() < 2 {
2198+
return Err("swap! expects at least 2 arguments (atom function [args...])".to_string());
2199+
}
2200+
2201+
// Get the atom
2202+
let atom_obj = args[0].as_heap()
2203+
.ok_or_else(|| "swap! expects an atom as first argument".to_string())?;
2204+
2205+
if let HeapObject::Atom(atom_mutex) = atom_obj {
2206+
// Get the function
2207+
let func = &args[1];
2208+
2209+
// Lock the atom
2210+
let mut current = atom_mutex.lock()
2211+
.map_err(|_| "Failed to lock atom".to_string())?;
2212+
2213+
// Convert current value back to Rc-based for function call
2214+
let current_value = Value::from_shared(&current);
2215+
2216+
// Build function call arguments: current value + any extra args
2217+
let mut func_args = vec![current_value];
2218+
if args.len() > 2 {
2219+
func_args.extend_from_slice(&args[2..]);
2220+
}
2221+
2222+
// Apply the function
2223+
let new_value = if let Some(nf) = func.as_native_function() {
2224+
(nf.func)(&func_args)?
2225+
} else if let Some(_chunk) = func.as_compiled_function() {
2226+
// For compiled functions, we need to create a VM and run the chunk
2227+
// For now, we'll only support native functions in swap!
2228+
return Err("swap! currently only supports native functions".to_string());
2229+
} else {
2230+
return Err("swap! expects a function as second argument".to_string());
2231+
};
2232+
2233+
// Convert result to SharedValue and update atom
2234+
let new_shared = new_value.make_shared()
2235+
.map_err(|e| format!("swap! failed to convert result: {}", e))?;
2236+
*current = new_shared.clone();
2237+
2238+
// Return the new value
2239+
Ok(Value::from_shared(&new_shared))
2240+
} else {
2241+
Err("swap! expects an atom as first argument".to_string())
2242+
}
2243+
}));
2244+
21242245
vm
21252246
}
21262247

0 commit comments

Comments
 (0)