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    // Note: . does not output a newline - use 'cr' for that
27    let _ = interp.write_str(&output);
28
29    Ok(())
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35    use crate::value::Value;
36
37    fn setup_interpreter() -> Interpreter {
38        Interpreter::new()
39    }
40
41    #[test]
42    fn test_print_builtin_number() {
43        let mut interp = setup_interpreter();
44
45        // Test printing a number
46        interp.push(Value::Number(42.0));
47
48        // Note: We can't easily test the actual output without capturing stdout,
49        // but we can test that the function succeeds and pops the value
50        let result = print_builtin(&mut interp);
51        assert!(result.is_ok());
52
53        // Stack should be empty after printing
54        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
55    }
56
57    #[test]
58    fn test_print_builtin_string() {
59        let mut interp = setup_interpreter();
60
61        interp.push(Value::String("hello world".into()));
62        let result = print_builtin(&mut interp);
63        assert!(result.is_ok());
64
65        // Stack should be empty
66        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
67    }
68
69    #[test]
70    fn test_print_builtin_boolean() {
71        let mut interp = setup_interpreter();
72
73        interp.push(Value::Boolean(true));
74        let result = print_builtin(&mut interp);
75        assert!(result.is_ok());
76
77        interp.push(Value::Boolean(false));
78        let result = print_builtin(&mut interp);
79        assert!(result.is_ok());
80
81        // Stack should be empty
82        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
83    }
84
85    #[test]
86    fn test_print_builtin_null() {
87        let mut interp = setup_interpreter();
88
89        interp.push(Value::Null);
90        let result = print_builtin(&mut interp);
91        assert!(result.is_ok());
92
93        // Stack should be empty
94        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
95    }
96
97    #[test]
98    fn test_print_builtin_atom() {
99        let mut interp = setup_interpreter();
100
101        let atom = interp.intern_atom("test");
102        interp.push(Value::Atom(atom));
103        let result = print_builtin(&mut interp);
104        assert!(result.is_ok());
105
106        // Stack should be empty
107        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
108    }
109
110    #[test]
111    fn test_print_builtin_quoted_atom() {
112        let mut interp = setup_interpreter();
113
114        let quoted_atom = interp.intern_atom("quoted");
115        interp.push(Value::QuotedAtom(quoted_atom));
116        let result = print_builtin(&mut interp);
117        assert!(result.is_ok());
118
119        // Stack should be empty
120        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
121    }
122
123    #[test]
124    fn test_print_builtin_empty_list() {
125        let mut interp = setup_interpreter();
126
127        interp.push(Value::Nil);
128        let result = print_builtin(&mut interp);
129        assert!(result.is_ok());
130
131        // Stack should be empty
132        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
133    }
134
135    #[test]
136    fn test_print_builtin_list() {
137        let mut interp = setup_interpreter();
138
139        // Test printing a list [1, 2, 3]
140        let list = interp.make_list(vec![
141            Value::Number(1.0),
142            Value::Number(2.0),
143            Value::Number(3.0),
144        ]);
145        interp.push(list);
146        let result = print_builtin(&mut interp);
147        assert!(result.is_ok());
148
149        // Stack should be empty
150        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
151    }
152
153    #[test]
154    fn test_print_builtin_mixed_list() {
155        let mut interp = setup_interpreter();
156
157        // Test printing a mixed list ["hello", 42, true]
158        let mixed_list = interp.make_list(vec![
159            Value::String("hello".into()),
160            Value::Number(42.0),
161            Value::Boolean(true),
162        ]);
163        interp.push(mixed_list);
164        let result = print_builtin(&mut interp);
165        assert!(result.is_ok());
166
167        // Stack should be empty
168        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
169    }
170
171    #[test]
172    fn test_print_builtin_stack_underflow() {
173        let mut interp = setup_interpreter();
174
175        // Test with empty stack
176        let result = print_builtin(&mut interp);
177        assert!(matches!(result, Err(RuntimeError::StackUnderflow)));
178    }
179
180    #[test]
181    fn test_print_builtin_multiple_values() {
182        let mut interp = setup_interpreter();
183
184        // Test printing multiple values in sequence
185        interp.push(Value::Number(1.0));
186        interp.push(Value::Number(2.0));
187        interp.push(Value::Number(3.0));
188
189        // Print each value
190        let result1 = print_builtin(&mut interp);
191        assert!(result1.is_ok());
192
193        let result2 = print_builtin(&mut interp);
194        assert!(result2.is_ok());
195
196        let result3 = print_builtin(&mut interp);
197        assert!(result3.is_ok());
198
199        // Stack should be empty
200        assert!(matches!(interp.pop(), Err(RuntimeError::StackUnderflow)));
201    }
202}