uni_core/primitives/
print.rs

1// RUST CONCEPT: Modular primitive organization
2// Each primitive gets its own file with implementation and tests
3use crate::compat::format;
4use crate::interpreter::Interpreter;
5use crate::value::{RuntimeError, Value};
6
7// RUST CONCEPT: Print builtin - pops and displays the top stack value
8// Usage: 42 .  (prints "42" and removes it from stack)
9// Note: The "." primitive prints values. Improper lists use "|" for their syntax
10pub fn print_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
11    let value = interp.pop()?;
12
13    // RUST CONCEPT: User-friendly printing - strings without quotes for readability
14    let output = match &value {
15        Value::String(s) => {
16            // For . primitive, show strings without quotes for user output
17            format!("{}", s)
18        }
19        _ => {
20            // For non-strings, use the standard Display format (with quotes for strings in data structures)
21            format!("{}", value)
22        }
23    };
24
25    // Write to terminal if available (silently succeeds if no terminal)
26    let _ = interp.writeln(&output);
27
28    Ok(())
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34    use crate::value::Value;
35
36    fn setup_interpreter() -> Interpreter {
37        Interpreter::new()
38    }
39
40    #[test]
41    fn test_print_builtin_number() {
42        let mut interp = setup_interpreter();
43
44        // Test printing a number
45        interp.push(Value::Number(42.0));
46
47        // Note: We can't easily test the actual output without capturing stdout,
48        // but we can test that the function succeeds and pops the value
49        let result = print_builtin(&mut interp);
50        assert!(result.is_ok());
51
52        // Stack should be empty after printing
53        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
54    }
55
56    #[test]
57    fn test_print_builtin_string() {
58        let mut interp = setup_interpreter();
59
60        interp.push(Value::String("hello world".into()));
61        let result = print_builtin(&mut interp);
62        assert!(result.is_ok());
63
64        // Stack should be empty
65        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
66    }
67
68    #[test]
69    fn test_print_builtin_boolean() {
70        let mut interp = setup_interpreter();
71
72        interp.push(Value::Boolean(true));
73        let result = print_builtin(&mut interp);
74        assert!(result.is_ok());
75
76        interp.push(Value::Boolean(false));
77        let result = print_builtin(&mut interp);
78        assert!(result.is_ok());
79
80        // Stack should be empty
81        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
82    }
83
84    #[test]
85    fn test_print_builtin_null() {
86        let mut interp = setup_interpreter();
87
88        interp.push(Value::Null);
89        let result = print_builtin(&mut interp);
90        assert!(result.is_ok());
91
92        // Stack should be empty
93        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
94    }
95
96    #[test]
97    fn test_print_builtin_atom() {
98        let mut interp = setup_interpreter();
99
100        let atom = interp.intern_atom("test");
101        interp.push(Value::Atom(atom));
102        let result = print_builtin(&mut interp);
103        assert!(result.is_ok());
104
105        // Stack should be empty
106        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
107    }
108
109    #[test]
110    fn test_print_builtin_quoted_atom() {
111        let mut interp = setup_interpreter();
112
113        let quoted_atom = interp.intern_atom("quoted");
114        interp.push(Value::QuotedAtom(quoted_atom));
115        let result = print_builtin(&mut interp);
116        assert!(result.is_ok());
117
118        // Stack should be empty
119        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
120    }
121
122    #[test]
123    fn test_print_builtin_empty_list() {
124        let mut interp = setup_interpreter();
125
126        interp.push(Value::Nil);
127        let result = print_builtin(&mut interp);
128        assert!(result.is_ok());
129
130        // Stack should be empty
131        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
132    }
133
134    #[test]
135    fn test_print_builtin_list() {
136        let mut interp = setup_interpreter();
137
138        // Test printing a list [1, 2, 3]
139        let list = interp.make_list(vec![
140            Value::Number(1.0),
141            Value::Number(2.0),
142            Value::Number(3.0),
143        ]);
144        interp.push(list);
145        let result = print_builtin(&mut interp);
146        assert!(result.is_ok());
147
148        // Stack should be empty
149        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
150    }
151
152    #[test]
153    fn test_print_builtin_mixed_list() {
154        let mut interp = setup_interpreter();
155
156        // Test printing a mixed list ["hello", 42, true]
157        let mixed_list = interp.make_list(vec![
158            Value::String("hello".into()),
159            Value::Number(42.0),
160            Value::Boolean(true),
161        ]);
162        interp.push(mixed_list);
163        let result = print_builtin(&mut interp);
164        assert!(result.is_ok());
165
166        // Stack should be empty
167        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
168    }
169
170    #[test]
171    fn test_print_builtin_stack_underflow() {
172        let mut interp = setup_interpreter();
173
174        // Test with empty stack
175        let result = print_builtin(&mut interp);
176        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
177    }
178
179    #[test]
180    fn test_print_builtin_multiple_values() {
181        let mut interp = setup_interpreter();
182
183        // Test printing multiple values in sequence
184        interp.push(Value::Number(1.0));
185        interp.push(Value::Number(2.0));
186        interp.push(Value::Number(3.0));
187
188        // Print each value
189        let result1 = print_builtin(&mut interp);
190        assert!(result1.is_ok());
191
192        let result2 = print_builtin(&mut interp);
193        assert!(result2.is_ok());
194
195        let result3 = print_builtin(&mut interp);
196        assert!(result3.is_ok());
197
198        // Stack should be empty
199        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
200    }
201}