uni_core/primitives/
val.rs

1// RUST CONCEPT: Modular primitive organization
2// Each primitive gets its own file with implementation and tests
3use crate::compat::ToString;
4use crate::interpreter::Interpreter;
5use crate::value::{RuntimeError, Value};
6
7// RUST CONCEPT: The val builtin - defines constants only (like Scheme's define for constants)
8// Usage: 'constant-name value val
9// Examples:
10//   'pi 3.14159 val         - defines a constant
11//   'greeting "Hello!" val  - defines a string constant
12// Unlike def, val is specifically for constants that shouldn't be evaluated
13pub fn val_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
14    // RUST CONCEPT: Same implementation as def for now
15    // The distinction is semantic - val is for constants, def is for procedures
16    let definition = interp.pop()?; // The constant value
17    let name_value = interp.pop()?; // Name of the constant
18
19    match name_value {
20        Value::Atom(atom_name) => {
21            // RUST CONCEPT: Creating dict entry with constant flag
22            use crate::interpreter::DictEntry;
23            let doc_clone = interp
24                .dictionary
25                .get(&atom_name)
26                .and_then(|entry| entry.doc.clone());
27            let entry = DictEntry {
28                value: definition,
29                is_executable: false, // val marks entries as constants
30                doc: doc_clone,
31            };
32            interp.dictionary.insert(atom_name.clone(), entry);
33            interp.set_pending_doc_target(atom_name);
34            Ok(())
35        }
36        _ => Err(RuntimeError::TypeError(
37            "val expects an atom as the constant name (use 'name value val)".to_string(),
38        )),
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use crate::interpreter::DictEntry;
46    use crate::value::Value;
47
48    fn setup_interpreter() -> Interpreter {
49        Interpreter::new()
50    }
51
52    #[test]
53    fn test_val_builtin_constants() {
54        let mut interp = setup_interpreter();
55
56        // RUST CONCEPT: Testing val for defining constants
57        // Define pi: 'pi 3.14159 val
58        let pi_atom = interp.intern_atom("pi");
59        interp.push(Value::Atom(pi_atom.clone()));
60        interp.push(Value::Number(3.14159));
61        val_builtin(&mut interp).unwrap();
62
63        // Verify it was stored
64        match interp.dictionary.get(&pi_atom) {
65            Some(DictEntry {
66                value: Value::Number(n),
67                ..
68            }) => assert!((n - 3.14159).abs() < 1e-10),
69            _ => panic!("Expected pi constant"),
70        }
71
72        // Define string constant: 'greeting "Hello!" val
73        let greeting_atom = interp.intern_atom("greeting");
74        let hello_str: std::rc::Rc<str> = "Hello!".into();
75        interp.push(Value::Atom(greeting_atom.clone()));
76        interp.push(Value::String(hello_str));
77        val_builtin(&mut interp).unwrap();
78
79        // Verify string constant
80        match interp.dictionary.get(&greeting_atom) {
81            Some(DictEntry {
82                value: Value::String(s),
83                ..
84            }) => assert_eq!(s.as_ref(), "Hello!"),
85            _ => panic!("Expected greeting string constant"),
86        }
87
88        // Clear stack
89        while interp.pop().is_ok() {}
90    }
91
92    #[test]
93    fn test_val_builtin_error_cases() {
94        let mut interp = setup_interpreter();
95
96        // RUST CONCEPT: Testing val error handling
97        // Same error conditions as def
98        assert!(val_builtin(&mut interp).is_err()); // Empty stack
99
100        interp.push(Value::Number(42.0));
101        assert!(val_builtin(&mut interp).is_err()); // Only one argument
102
103        // Non-atom name should fail
104        interp.push(Value::Number(42.0)); // Invalid name
105        interp.push(Value::Number(123.0)); // Value
106        let result = val_builtin(&mut interp);
107        assert!(result.is_err());
108        assert!(
109            result
110                .unwrap_err()
111                .to_string()
112                .contains("val expects an atom")
113        );
114
115        // Clear stack
116        while interp.pop().is_ok() {}
117    }
118
119    #[test]
120    fn test_val_builtin_constant_flag() {
121        let mut interp = setup_interpreter();
122
123        // Test that val marks entries as non-executable (constants)
124        let test_atom = interp.intern_atom("test_constant");
125        interp.push(Value::Atom(test_atom.clone()));
126        interp.push(Value::Number(42.0));
127        val_builtin(&mut interp).unwrap();
128
129        // Verify executable flag is set to false
130        match interp.dictionary.get(&test_atom) {
131            Some(DictEntry { is_executable, .. }) => assert!(!*is_executable),
132            _ => panic!("Expected test_constant to be defined"),
133        }
134
135        // Clear stack
136        while interp.pop().is_ok() {}
137    }
138
139    #[test]
140    fn test_val_builtin_various_types() {
141        let mut interp = setup_interpreter();
142
143        // Test defining various constant types
144        let constants_to_test = vec![
145            ("bool_const", Value::Boolean(false)),
146            ("null_const", Value::Null),
147            ("string_const", Value::String("constant value".into())),
148            (
149                "atom_const",
150                Value::Atom(interp.intern_atom("constant_atom")),
151            ),
152        ];
153
154        for (name, value) in constants_to_test {
155            let name_atom = interp.intern_atom(name);
156            interp.push(Value::Atom(name_atom.clone()));
157            interp.push(value.clone());
158            val_builtin(&mut interp).unwrap();
159
160            // Verify it was stored as non-executable
161            match interp.dictionary.get(&name_atom) {
162                Some(DictEntry {
163                    value: stored_value,
164                    is_executable,
165                    ..
166                }) => {
167                    assert!(!*is_executable, "Constants should not be executable");
168
169                    // Basic type check
170                    match (&value, stored_value) {
171                        (Value::Boolean(b1), Value::Boolean(b2)) => assert_eq!(b1, b2),
172                        (Value::Null, Value::Null) => (),
173                        (Value::String(s1), Value::String(s2)) => assert_eq!(s1, s2),
174                        (Value::Atom(a1), Value::Atom(a2)) => assert_eq!(a1, a2),
175                        _ => panic!("Value type mismatch for {}", name),
176                    }
177                }
178                _ => panic!("Expected {} to be defined", name),
179            }
180        }
181
182        // Clear stack
183        while interp.pop().is_ok() {}
184    }
185
186    #[test]
187    fn test_val_builtin_redefinition() {
188        let mut interp = setup_interpreter();
189
190        // Test redefining a constant
191        let const_atom = interp.intern_atom("my_const");
192
193        // First definition
194        interp.push(Value::Atom(const_atom.clone()));
195        interp.push(Value::Number(100.0));
196        val_builtin(&mut interp).unwrap();
197
198        // Verify first definition
199        match interp.dictionary.get(&const_atom) {
200            Some(DictEntry {
201                value: Value::Number(n),
202                ..
203            }) => assert_eq!(*n, 100.0),
204            _ => panic!("Expected first definition"),
205        }
206
207        // Redefine the constant
208        interp.push(Value::Atom(const_atom.clone()));
209        interp.push(Value::Number(200.0));
210        val_builtin(&mut interp).unwrap();
211
212        // Verify redefinition
213        match interp.dictionary.get(&const_atom) {
214            Some(DictEntry {
215                value: Value::Number(n),
216                ..
217            }) => assert_eq!(*n, 200.0),
218            _ => panic!("Expected redefinition"),
219        }
220
221        // Clear stack
222        while interp.pop().is_ok() {}
223    }
224
225    #[test]
226    fn test_val_builtin_with_nil() {
227        let mut interp = setup_interpreter();
228
229        // Test defining nil as a constant
230        let nil_atom = interp.intern_atom("nil_const");
231        interp.push(Value::Atom(nil_atom.clone()));
232        interp.push(Value::Nil);
233        val_builtin(&mut interp).unwrap();
234
235        // Verify nil constant
236        match interp.dictionary.get(&nil_atom) {
237            Some(DictEntry {
238                value: Value::Nil,
239                is_executable,
240                ..
241            }) => {
242                assert!(!*is_executable, "Nil constant should not be executable");
243            }
244            _ => panic!("Expected nil_const to be defined as Nil"),
245        }
246
247        // Clear stack
248        while interp.pop().is_ok() {}
249    }
250
251    #[test]
252    fn test_val_builtin_with_list() {
253        let mut interp = setup_interpreter();
254
255        // Test defining a list as a constant (even though it's unusual)
256        let list_atom = interp.intern_atom("list_const");
257        let constant_list = interp.make_list(vec![
258            Value::Number(1.0),
259            Value::Number(2.0),
260            Value::Number(3.0),
261        ]);
262
263        interp.push(Value::Atom(list_atom.clone()));
264        interp.push(constant_list);
265        val_builtin(&mut interp).unwrap();
266
267        // Verify list constant is non-executable
268        match interp.dictionary.get(&list_atom) {
269            Some(DictEntry {
270                value: Value::Pair(_, _),
271                is_executable,
272                ..
273            }) => {
274                assert!(!*is_executable, "List constants should not be executable");
275            }
276            _ => panic!("Expected list_const to be defined as a list"),
277        }
278
279        // Clear stack
280        while interp.pop().is_ok() {}
281    }
282}