uni_core/primitives/
stack.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;
6
7#[cfg(not(target_os = "none"))]
8use std::vec::Vec;
9#[cfg(target_os = "none")]
10use alloc::vec::Vec;
11
12// RUST CONCEPT: Stack builtin - displays the current stack contents
13// Usage: stack  (displays all stack items from top to bottom)
14pub fn stack_builtin(interp: &mut Interpreter) -> Result<(), RuntimeError> {
15    if interp.stack.is_empty() {
16        let _ = interp.writeln("Stack is empty");
17    } else {
18        // RUST CONCEPT: Collect all lines first to avoid borrow checker issues
19        // We can't iterate over stack (immutable borrow) while calling writeln (mutable borrow)
20        let mut lines = Vec::new();
21
22        let msg = format!("Stack ({} items):", interp.stack.len());
23        lines.push(msg);
24
25        // RUST CONCEPT: Platform-specific limits
26        // Show fewer items on micro:bit due to limited screen space
27        let limit = if cfg!(target_os = "none") { 5 } else { 10 };
28
29        for (i, value) in interp.stack.iter().rev().enumerate() {
30            if i >= limit {
31                let msg = format!("  ... and {} more", interp.stack.len() - limit);
32                lines.push(msg);
33                break;
34            }
35            let msg = format!("  {}: {}", i, value);
36            lines.push(msg);
37        }
38
39        // Now write all lines with mutable borrow
40        for line in lines {
41            let _ = interp.writeln(&line);
42        }
43    }
44
45    Ok(())
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::value::Value;
52
53    fn setup_interpreter() -> Interpreter {
54        Interpreter::new()
55    }
56
57    #[test]
58    fn test_stack_builtin_empty() {
59        let mut interp = setup_interpreter();
60
61        // Stack should succeed with empty stack
62        let result = stack_builtin(&mut interp);
63        assert!(result.is_ok());
64    }
65
66    #[test]
67    fn test_stack_builtin_with_values() {
68        let mut interp = setup_interpreter();
69
70        // Push some values
71        interp.push(Value::Number(1.0));
72        interp.push(Value::Number(2.0));
73        interp.push(Value::Number(3.0));
74
75        // Stack should succeed and display values
76        let result = stack_builtin(&mut interp);
77        assert!(result.is_ok());
78    }
79
80    #[test]
81    fn test_stack_builtin_no_stack_effect() {
82        let mut interp = setup_interpreter();
83
84        // Push some values on the stack
85        interp.push(Value::Number(1.0));
86        interp.push(Value::Number(2.0));
87        interp.push(Value::Number(3.0));
88
89        let stack_before = interp.stack.len();
90
91        // Stack should not affect the stack
92        let result = stack_builtin(&mut interp);
93        assert!(result.is_ok());
94
95        let stack_after = interp.stack.len();
96        assert_eq!(stack_before, stack_after);
97    }
98
99    #[test]
100    fn test_stack_builtin_many_items() {
101        let mut interp = setup_interpreter();
102
103        // Push many values (more than the display limit)
104        for i in 0..20 {
105            interp.push(Value::Number(i as f64));
106        }
107
108        // Stack should succeed and truncate display
109        let result = stack_builtin(&mut interp);
110        assert!(result.is_ok());
111
112        // All items should still be on stack
113        assert_eq!(interp.stack.len(), 20);
114    }
115}