uni_core/primitives/
def.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 def builtin - defines new words in the dictionary
8// Usage: 'word-name definition def
9// Examples:
10//   'square [dup *] def     - defines a procedure
11//   'pi 3.14159 def         - defines a constant
12pub fn def_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
13    // RUST CONCEPT: Stack-based parameter passing
14    // def expects two values on the stack:
15    // 1. The definition (top of stack) - can be any Value
16    // 2. The word name (second on stack) - must be an Atom
17
18    let definition = interp.pop()?; // What to define the word as
19    let name_value = interp.pop()?; // Name of the word to define
20
21    // RUST CONCEPT: Pattern matching for type checking
22    // The word name must be an Atom (interned string)
23    match name_value {
24        Value::Atom(atom_name) => {
25            // RUST CONCEPT: Creating dict entry with executable flag
26            use crate::interpreter::DictEntry;
27            let doc_clone = interp
28                .dictionary
29                .get(&atom_name)
30                .and_then(|entry| entry.doc.clone());
31            let entry = DictEntry {
32                value: definition,
33                is_executable: true, // def marks entries as executable
34                doc: doc_clone,
35            };
36            interp.dictionary.insert(atom_name.clone(), entry);
37            interp.set_pending_doc_target(atom_name);
38            Ok(())
39        }
40
41        // RUST CONCEPT: Descriptive error messages
42        // If the name isn't an atom, we can't use it as a dictionary key
43        _ => Err(RuntimeError::TypeError(
44            "def expects an atom as the word name (use 'word-name definition def)".to_string(),
45        )),
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::interpreter::DictEntry;
53    use crate::value::Value;
54
55    fn setup_interpreter() -> Interpreter {
56        Interpreter::new()
57    }
58
59    #[test]
60    fn test_def_builtin_constant() {
61        let mut interp = setup_interpreter();
62
63        // RUST CONCEPT: Testing constant definition
64        // Define pi as 3.14159: 'pi 3.14159 def
65        let pi_atom = interp.intern_atom("pi");
66        interp.push(Value::Atom(pi_atom.clone())); // Word name
67        interp.push(Value::Number(3.14159)); // Definition
68        def_builtin(&mut interp).unwrap();
69
70        // Verify it was stored in dictionary
71        let pi_lookup = interp.intern_atom("pi");
72        assert!(interp.dictionary.contains_key(&pi_lookup));
73
74        // Verify we can retrieve the constant
75        match interp.dictionary.get(&pi_lookup) {
76            Some(DictEntry {
77                value: Value::Number(n),
78                ..
79            }) => assert!((n - 3.14159).abs() < 1e-10),
80            _ => panic!("Expected pi to be defined as a number"),
81        }
82
83        // Clear stack before next test
84        while interp.pop().is_ok() {}
85    }
86
87    #[test]
88    fn test_def_builtin_procedure() {
89        let mut interp = setup_interpreter();
90
91        // RUST CONCEPT: Testing procedure definition
92        // Define square as [dup *]: 'square [dup *] def
93        let square_atom = interp.intern_atom("square");
94        let dup_atom = interp.intern_atom("dup");
95        let mul_atom = interp.intern_atom("*");
96
97        // Create the procedure list [dup *]
98        let square_proc = interp.make_list(vec![Value::Atom(dup_atom), Value::Atom(mul_atom)]);
99
100        interp.push(Value::Atom(square_atom.clone())); // Word name
101        interp.push(square_proc); // Definition
102        def_builtin(&mut interp).unwrap();
103
104        // Verify it was stored in dictionary
105        let square_lookup = interp.intern_atom("square");
106        assert!(interp.dictionary.contains_key(&square_lookup));
107
108        // Verify we can retrieve the procedure
109        match interp.dictionary.get(&square_lookup) {
110            Some(DictEntry {
111                value: Value::Pair(_, _),
112                ..
113            }) => (), // It's a list (procedure)
114            _ => panic!("Expected square to be defined as a list"),
115        }
116
117        // Clear stack
118        while interp.pop().is_ok() {}
119    }
120
121    #[test]
122    fn test_def_builtin_string_definition() {
123        let mut interp = setup_interpreter();
124
125        // RUST CONCEPT: Testing string definition
126        // Define greeting as "Hello, Uni!": 'greeting "Hello, Uni!" def
127        let greeting_atom = interp.intern_atom("greeting");
128        let greeting_string: std::rc::Rc<str> = "Hello, Uni!".into();
129
130        interp.push(Value::Atom(greeting_atom.clone())); // Word name
131        interp.push(Value::String(greeting_string)); // Definition
132        def_builtin(&mut interp).unwrap();
133
134        // Verify it was stored
135        let greeting_lookup = interp.intern_atom("greeting");
136        match interp.dictionary.get(&greeting_lookup) {
137            Some(DictEntry {
138                value: Value::String(s),
139                ..
140            }) => assert_eq!(s.as_ref(), "Hello, Uni!"),
141            _ => panic!("Expected greeting to be defined as a string"),
142        }
143
144        // Clear stack
145        while interp.pop().is_ok() {}
146    }
147
148    #[test]
149    fn test_def_builtin_error_cases() {
150        let mut interp = setup_interpreter();
151
152        // RUST CONCEPT: Testing error handling
153        // def requires exactly two arguments
154        assert!(def_builtin(&mut interp).is_err()); // Empty stack
155
156        interp.push(Value::Number(42.0));
157        assert!(def_builtin(&mut interp).is_err()); // Only one argument
158
159        // RUST CONCEPT: Testing type safety
160        // First argument (word name) must be an Atom
161        interp.push(Value::Number(42.0)); // Invalid name (not atom)
162        interp.push(Value::Number(123.0)); // Definition
163        let result = def_builtin(&mut interp);
164        assert!(result.is_err());
165        assert!(
166            result
167                .unwrap_err()
168                .to_string()
169                .contains("def expects an atom")
170        );
171
172        // Clear stack
173        while interp.pop().is_ok() {}
174    }
175
176    #[test]
177    fn test_def_builtin_redefinition() {
178        let mut interp = setup_interpreter();
179
180        // RUST CONCEPT: Testing word redefinition
181        // First define foo as 123
182        let foo_atom = interp.intern_atom("foo");
183        interp.push(Value::Atom(foo_atom.clone()));
184        interp.push(Value::Number(123.0));
185        def_builtin(&mut interp).unwrap();
186
187        // Verify first definition
188        match interp.dictionary.get(&foo_atom) {
189            Some(DictEntry {
190                value: Value::Number(n),
191                ..
192            }) => assert_eq!(*n, 123.0),
193            _ => panic!("Expected foo to be 123"),
194        }
195
196        // Redefine foo as 456
197        interp.push(Value::Atom(foo_atom.clone()));
198        interp.push(Value::Number(456.0));
199        def_builtin(&mut interp).unwrap();
200
201        // Verify redefinition worked
202        match interp.dictionary.get(&foo_atom) {
203            Some(DictEntry {
204                value: Value::Number(n),
205                ..
206            }) => assert_eq!(*n, 456.0),
207            _ => panic!("Expected foo to be redefined as 456"),
208        }
209
210        // Clear stack
211        while interp.pop().is_ok() {}
212    }
213
214    #[test]
215    fn test_def_builtin_with_nil() {
216        let mut interp = setup_interpreter();
217
218        // RUST CONCEPT: Testing edge case - defining with empty list
219        let empty_atom = interp.intern_atom("empty");
220        interp.push(Value::Atom(empty_atom.clone()));
221        interp.push(Value::Nil);
222        def_builtin(&mut interp).unwrap();
223
224        // Verify nil definition
225        match interp.dictionary.get(&empty_atom) {
226            Some(DictEntry {
227                value: Value::Nil, ..
228            }) => (),
229            _ => panic!("Expected empty to be defined as Nil"),
230        }
231
232        // Clear stack
233        while interp.pop().is_ok() {}
234    }
235
236    #[test]
237    fn test_def_builtin_executable_flag() {
238        let mut interp = setup_interpreter();
239
240        // Test that def marks entries as executable
241        let test_atom = interp.intern_atom("test");
242        interp.push(Value::Atom(test_atom.clone()));
243        interp.push(Value::Number(42.0));
244        def_builtin(&mut interp).unwrap();
245
246        // Verify executable flag is set to true
247        match interp.dictionary.get(&test_atom) {
248            Some(DictEntry { is_executable, .. }) => assert!(*is_executable),
249            _ => panic!("Expected test to be defined"),
250        }
251
252        // Clear stack
253        while interp.pop().is_ok() {}
254    }
255
256    #[test]
257    fn test_def_builtin_various_types() {
258        let mut interp = setup_interpreter();
259
260        // Test defining various value types
261        let types_to_test = vec![
262            ("bool_val", Value::Boolean(true)),
263            ("null_val", Value::Null),
264            ("atom_val", Value::Atom(interp.intern_atom("test_atom"))),
265        ];
266
267        for (name, value) in types_to_test {
268            let name_atom = interp.intern_atom(name);
269            interp.push(Value::Atom(name_atom.clone()));
270            interp.push(value.clone());
271            def_builtin(&mut interp).unwrap();
272
273            // Verify it was stored
274            assert!(interp.dictionary.contains_key(&name_atom));
275            match interp.dictionary.get(&name_atom) {
276                Some(DictEntry {
277                    value: stored_value,
278                    ..
279                }) => {
280                    // Basic type check - more detailed equality would need custom implementation
281                    match (&value, stored_value) {
282                        (Value::Boolean(b1), Value::Boolean(b2)) => assert_eq!(b1, b2),
283                        (Value::Null, Value::Null) => (),
284                        (Value::Atom(a1), Value::Atom(a2)) => assert_eq!(a1, a2),
285                        _ => panic!("Value type mismatch for {}", name),
286                    }
287                }
288                _ => panic!("Expected {} to be defined", name),
289            }
290        }
291
292        // Clear stack
293        while interp.pop().is_ok() {}
294    }
295}