uni_core/
interpreter.rs

1use crate::compat::{Rc, String, Vec, Box, ToString};
2use crate::tokenizer::SourcePos;
3use crate::value::{RuntimeError, Value};
4use crate::output::Output;
5use num_traits::Zero;
6
7#[cfg(target_os = "none")]
8use num_traits::Float;
9
10#[cfg(not(target_os = "none"))]
11use std::collections::HashMap;
12#[cfg(target_os = "none")]
13use alloc::collections::BTreeMap as HashMap;
14
15// RUST CONCEPT: Dictionary entry with metadata
16// Each entry contains the value and a flag indicating execution behavior
17#[derive(Debug, Clone)]
18pub struct DictEntry {
19    pub value: Value,
20    pub is_executable: bool, // true = execute lists (def), false = push as data (val)
21    pub doc: Option<Rc<str>>, // Optional documentation string for help
22}
23
24pub struct Interpreter {
25    pub stack: Vec<Value>,
26    pub return_stack: Vec<Value>, // RUST CONCEPT: Return stack for Forth-like operations
27    pub dictionary: HashMap<Rc<str>, DictEntry>,
28    pub atoms: HashMap<String, Rc<str>>,
29    pub current_pos: Option<SourcePos>, // Track current execution position for error messages
30    pending_doc_target: Option<Rc<str>>, // Remember most recent definition for doc
31    output: Option<Box<dyn Output>>, // Optional output for print/display (REPL mode)
32
33    // Hardware peripherals (micro:bit only)
34    #[cfg(feature = "hardware-microbit")]
35    pub buttons: Option<microbit::board::Buttons>,
36    #[cfg(feature = "hardware-microbit")]
37    pub display_buffer: [[u8; 5]; 5],  // Raw pixel buffer for LED matrix
38}
39
40impl Interpreter {
41    pub fn new() -> Self {
42        let mut interpreter = Self {
43            stack: Vec::new(),
44            return_stack: Vec::new(), // RUST CONCEPT: Initialize empty return stack
45            dictionary: HashMap::new(),
46            atoms: HashMap::new(),
47            current_pos: None, // No position initially
48            pending_doc_target: None,
49            output: None, // No output by default (for file execution, tests)
50
51            // Hardware peripherals start as None, set by platform initialization
52            #[cfg(feature = "hardware-microbit")]
53            buttons: None,
54            #[cfg(feature = "hardware-microbit")]
55            display_buffer: [[0u8; 5]; 5],  // All LEDs off initially
56        };
57
58        // RUST CONCEPT: Automatic initialization
59        // Load builtins first (primitives and core operations)
60        crate::builtins::register_builtins(&mut interpreter);
61
62        // Then load prelude (higher-level operations built on primitives)
63        if let Err(_e) = crate::prelude::load_prelude(&mut interpreter) {
64            // In a constructor, we can't easily return errors
65            // For now, just continue without prelude
66            // TODO: Better error handling for prelude loading
67        }
68
69        interpreter
70    }
71
72    pub fn intern_atom(&mut self, text: &str) -> Rc<str> {
73        if let Some(existing) = self.atoms.get(text) {
74            existing.clone()
75        } else {
76            let atom: Rc<str> = text.into();
77            self.atoms.insert(text.to_string(), atom.clone());
78            atom
79        }
80    }
81
82    pub fn set_pending_doc_target(&mut self, atom: Rc<str>) {
83        self.pending_doc_target = Some(atom);
84    }
85
86    pub fn take_pending_doc_target(&mut self) -> Option<Rc<str>> {
87        self.pending_doc_target.take()
88    }
89
90    pub fn attach_doc(&mut self, atom: &Rc<str>, doc: Rc<str>) -> Result<(), RuntimeError> {
91        if let Some(entry) = self.dictionary.get_mut(atom) {
92            entry.doc = Some(doc);
93            Ok(())
94        } else {
95            Err(RuntimeError::UndefinedWord(atom.to_string()))
96        }
97    }
98
99    pub fn push(&mut self, value: Value) {
100        self.stack.push(value);
101    }
102
103    pub fn pop(&mut self) -> Result<Value, RuntimeError> {
104        self.stack.pop().ok_or(RuntimeError::StackUnderflow)
105    }
106
107    pub fn pop_number(&mut self) -> Result<f64, RuntimeError> {
108        let value = self.pop()?;
109        match value {
110            Value::Number(n) => Ok(n),
111            Value::Int32(i) => Ok(i as f64),
112            _ => Err(RuntimeError::TypeError("Expected number".to_string())),
113        }
114    }
115
116    pub fn pop_integer(&mut self) -> Result<usize, RuntimeError> {
117        use num_traits::ToPrimitive;
118        let value = self.pop()?;
119        match value {
120            Value::Int32(i) => {
121                if i >= 0 {
122                    Ok(i as usize)
123                } else {
124                    Err(RuntimeError::TypeError("Expected non-negative integer".to_string()))
125                }
126            }
127            Value::Integer(i) => i.to_usize().ok_or_else(|| {
128                RuntimeError::TypeError("Integer value too large for index".to_string())
129            }),
130            Value::Number(n) => {
131                if n.fract() == 0.0 && n >= 0.0 && n.is_finite() {
132                    Ok(n as usize)
133                } else {
134                    Err(RuntimeError::TypeError("Expected non-negative integer".to_string()))
135                }
136            }
137            _ => Err(RuntimeError::TypeError("Expected integer".to_string())),
138        }
139    }
140
141    pub fn make_list(&self, items: Vec<Value>) -> Value {
142        items.into_iter().rev().fold(Value::Nil, |acc, item| {
143            Value::Pair(Rc::new(item), Rc::new(acc))
144        })
145    }
146
147    pub fn make_array(&self, items: Vec<Value>) -> Value {
148        #[cfg(not(target_os = "none"))]
149        {
150            Value::Array(Rc::new(std::cell::RefCell::new(items)))
151        }
152        #[cfg(target_os = "none")]
153        {
154            use core::cell::RefCell;
155            Value::Array(Rc::new(RefCell::new(items)))
156        }
157    }
158
159    pub fn is_null(&self, value: &Value) -> bool {
160        matches!(value, Value::Null)
161    }
162
163    // RUST CONCEPT: Defensive truthiness check
164    // Following JS rules: check for falsy cases explicitly, everything else is truthy
165    // This is more maintainable - new types automatically become truthy by default
166    pub fn is_truthy(&self, value: &Value) -> bool {
167        match value {
168            // Falsy cases only:
169            Value::Boolean(false) => false,    // false is falsy
170            Value::Null => false,              // null is falsy (like JS)
171            Value::String(s) if s.is_empty() => false, // "" is falsy (like JS)
172
173            // Zero in all numeric representations is falsy
174            Value::Int32(0) => false,
175            Value::Number(n) if *n == 0.0 || n.is_nan() => false, // 0 and NaN are falsy (like JS)
176            Value::Integer(i) if i.is_zero() => false,
177            Value::Rational(r) if r.is_zero() => false,
178            #[cfg(feature = "complex_numbers")]
179            Value::GaussianInt(re, im) if re.is_zero() && im.is_zero() => false, // 0+0i
180            #[cfg(feature = "complex_numbers")]
181            Value::Complex(c) if (c.re == 0.0 && c.im == 0.0) || c.re.is_nan() || c.im.is_nan() => false, // 0+0i or NaN parts
182
183            // Everything else is truthy (including Nil, atoms, pairs, arrays, buffers, etc.)
184            _ => true,
185        }
186    }
187
188    // RUST CONCEPT: Return stack operations for Forth-like control structures
189    // These operations enable temporary storage of values outside the main computation stack
190
191    pub fn push_return(&mut self, value: Value) {
192        self.return_stack.push(value);
193    }
194
195    pub fn pop_return(&mut self) -> Result<Value, RuntimeError> {
196        self.return_stack.pop().ok_or(RuntimeError::StackUnderflow)
197    }
198
199    pub fn peek_return(&self) -> Result<&Value, RuntimeError> {
200        self.return_stack.last().ok_or(RuntimeError::StackUnderflow)
201    }
202
203    // TODO: Position management for error context - uncomment when connecting to execution pipeline
204    // pub fn set_position(&mut self, pos: SourcePos) {
205    //     self.current_pos = Some(pos);
206    // }
207
208    // TODO: Method for clearing position context - uncomment when needed for multi-statement execution
209    // pub fn clear_position(&mut self) {
210    //     self.current_pos = None;
211    // }
212
213    // Position-aware pop method for better error messages
214    pub fn pop_with_context(&mut self, context: &str) -> Result<Value, RuntimeError> {
215        if let Some(pos) = &self.current_pos {
216            self.stack
217                .pop()
218                .ok_or_else(|| RuntimeError::StackUnderflowAt {
219                    pos: pos.clone(),
220                    context: context.to_string(),
221                })
222        } else {
223            self.stack.pop().ok_or(RuntimeError::StackUnderflow)
224        }
225    }
226
227    // Output management (used by print/help builtins)
228    pub fn set_output(&mut self, output: Box<dyn Output>) {
229        self.output = Some(output);
230    }
231
232    #[allow(dead_code)]
233    pub fn has_output(&self) -> bool {
234        self.output.is_some()
235    }
236
237    // Write a line to the output if available
238    pub fn writeln(&mut self, text: &str) -> Result<(), ()> {
239        if let Some(output) = &mut self.output {
240            output.write(text.as_bytes())?;
241            // Use platform-appropriate line ending
242            #[cfg(not(target_os = "none"))]
243            output.write(b"\n")?;
244            #[cfg(target_os = "none")]
245            output.write(b"\r\n")?;
246            output.flush()?;
247        }
248        Ok(())
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255
256    // Test-only helper methods
257    impl Interpreter {
258        fn pop_string(&mut self) -> Result<Rc<str>, RuntimeError> {
259            let value = self.pop()?;
260            match value {
261                Value::String(s) => Ok(s),
262                _ => Err(RuntimeError::TypeError("Expected string".to_string())),
263            }
264        }
265
266        fn pop_boolean(&mut self) -> Result<bool, RuntimeError> {
267            let value = self.pop()?;
268            match value {
269                Value::Boolean(b) => Ok(b),
270                _ => Err(RuntimeError::TypeError("Expected boolean".to_string())),
271            }
272        }
273    }
274
275    #[test]
276    fn test_atom_interning() {
277        let mut interp = Interpreter::new();
278
279        let atom1 = interp.intern_atom("hello");
280        let atom2 = interp.intern_atom("hello");
281
282        assert!(Rc::ptr_eq(&atom1, &atom2));
283    }
284
285    #[test]
286    fn test_stack_operations() {
287        let mut interp = Interpreter::new();
288
289        interp.push(Value::Number(42.0));
290        let popped = interp.pop().unwrap();
291
292        match popped {
293            Value::Number(n) => assert_eq!(n, 42.0),
294            _ => panic!("Expected number"),
295        }
296
297        assert!(interp.pop().is_err());
298    }
299
300    #[test]
301    fn test_list_construction() {
302        let interp = Interpreter::new();
303
304        let empty = interp.make_list(vec![]);
305        match empty {
306            Value::Nil => (),
307            _ => panic!("Expected Nil for empty list"),
308        }
309
310        let single = interp.make_list(vec![Value::Number(42.0)]);
311        match single {
312            Value::Pair(car, cdr) => match (car.as_ref(), cdr.as_ref()) {
313                (Value::Number(n), Value::Nil) => assert_eq!(*n, 42.0),
314                _ => panic!("Expected Pair(42, Nil)"),
315            },
316            _ => panic!("Expected Pair for single element list"),
317        }
318
319        // Test multi-element list
320        let multi = interp.make_list(vec![
321            Value::Number(1.0),
322            Value::Number(2.0),
323            Value::Number(3.0),
324        ]);
325        match multi {
326            Value::Pair(car1, cdr1) => match (car1.as_ref(), cdr1.as_ref()) {
327                (Value::Number(n1), Value::Pair(car2, cdr2)) => {
328                    assert_eq!(*n1, 1.0);
329                    match (car2.as_ref(), cdr2.as_ref()) {
330                        (Value::Number(n2), Value::Pair(car3, cdr3)) => {
331                            assert_eq!(*n2, 2.0);
332                            match (car3.as_ref(), cdr3.as_ref()) {
333                                (Value::Number(n3), Value::Nil) => assert_eq!(*n3, 3.0),
334                                _ => panic!("Expected third element to be 3.0 followed by Nil"),
335                            }
336                        }
337                        _ => panic!("Expected second element to be 2.0"),
338                    }
339                }
340                _ => panic!("Expected first element to be 1.0"),
341            },
342            _ => panic!("Expected Pair for multi-element list"),
343        }
344    }
345
346    #[test]
347    fn test_pop_number_success() {
348        let mut interp = Interpreter::new();
349        interp.push(Value::Number(42.0));
350        assert_eq!(interp.pop_number().unwrap(), 42.0);
351    }
352
353    #[test]
354    fn test_pop_number_type_error() {
355        let mut interp = Interpreter::new();
356        interp.push(Value::Nil);
357        assert!(matches!(
358            interp.pop_number(),
359            Err(RuntimeError::TypeError(msg)) if msg == "Expected number"
360        ));
361    }
362
363    #[test]
364    fn test_pop_number_underflow() {
365        let mut interp = Interpreter::new();
366        assert!(matches!(
367            interp.pop_number(),
368            Err(RuntimeError::StackUnderflow)
369        ));
370    }
371
372    #[test]
373    fn test_dictionary_operations() {
374        let mut interp = Interpreter::new();
375
376        // Test inserting and retrieving from dictionary
377        let key = interp.intern_atom("test");
378        let entry = DictEntry {
379            value: Value::Number(99.0),
380            is_executable: false, // Constants are not executable
381            doc: None,
382        };
383        interp.dictionary.insert(key.clone(), entry);
384
385        match interp.dictionary.get(&key) {
386            Some(dict_entry) => {
387                match &dict_entry.value {
388                    Value::Number(n) => assert_eq!(*n, 99.0),
389                    _ => panic!("Expected to find Number(99.0) in dictionary entry"),
390                }
391                assert!(!dict_entry.is_executable);
392            }
393            _ => panic!("Expected to find dictionary entry"),
394        }
395
396        // Test that non-existent keys return None
397        let missing = interp.intern_atom("missing");
398        assert!(interp.dictionary.get(&missing).is_none());
399    }
400
401    #[test]
402    fn test_atom_interning_different_atoms() {
403        let mut interp = Interpreter::new();
404
405        let atom1 = interp.intern_atom("hello");
406        let atom2 = interp.intern_atom("world");
407        let atom3 = interp.intern_atom("hello");
408
409        // Same text should return same Rc
410        assert!(Rc::ptr_eq(&atom1, &atom3));
411
412        // Different text should return different Rc
413        assert!(!Rc::ptr_eq(&atom1, &atom2));
414
415        // Verify the actual content
416        assert_eq!(&*atom1, "hello");
417        assert_eq!(&*atom2, "world");
418    }
419
420    #[test]
421    fn test_pop_string() {
422        let mut interp = Interpreter::new();
423
424        // Test successful pop_string
425        let string_val: Rc<str> = "hello world".into();
426        interp.push(Value::String(string_val));
427        let s = interp.pop_string().unwrap();
428        assert_eq!(&*s, "hello world");
429
430        // Test type error when popping non-string
431        interp.push(Value::Number(42.0));
432        assert!(matches!(
433            interp.pop_string(),
434            Err(RuntimeError::TypeError(msg)) if msg == "Expected string"
435        ));
436
437        // Test stack underflow
438        assert!(matches!(
439            interp.pop_string(),
440            Err(RuntimeError::StackUnderflow)
441        ));
442    }
443
444    #[test]
445    fn test_string_vs_atom_distinction() {
446        let mut interp = Interpreter::new();
447
448        // Strings are not interned - each is separate
449        let string1 = Value::String("hello".into());
450        let string2 = Value::String("hello".into());
451
452        // Atoms are interned - same text gives same Rc
453        let atom1 = Value::Atom(interp.intern_atom("hello"));
454        let atom2 = Value::Atom(interp.intern_atom("hello"));
455
456        // Strings with same content are different Rc objects (not interned)
457        if let (Value::String(s1), Value::String(s2)) = (&string1, &string2) {
458            assert_eq!(s1, s2); // Same content
459            assert!(!Rc::ptr_eq(s1, s2)); // Different Rc objects
460        }
461
462        // Atoms with same content share the same object
463        if let (Value::Atom(a1), Value::Atom(a2)) = (&atom1, &atom2) {
464            assert_eq!(a1, a2); // Same content
465            assert!(Rc::ptr_eq(a1, a2)); // Same object
466        }
467    }
468
469    #[test]
470    fn test_pop_boolean() {
471        let mut interp = Interpreter::new();
472
473        // Test successful pop_boolean
474        interp.push(Value::Boolean(true));
475        assert_eq!(interp.pop_boolean().unwrap(), true);
476
477        interp.push(Value::Boolean(false));
478        assert_eq!(interp.pop_boolean().unwrap(), false);
479
480        // Test type error when popping non-boolean
481        interp.push(Value::Number(42.0));
482        assert!(matches!(
483            interp.pop_boolean(),
484            Err(RuntimeError::TypeError(msg)) if msg == "Expected boolean"
485        ));
486
487        // Test stack underflow
488        assert!(matches!(
489            interp.pop_boolean(),
490            Err(RuntimeError::StackUnderflow)
491        ));
492    }
493
494    #[test]
495    fn test_is_null() {
496        let interp = Interpreter::new();
497
498        assert!(interp.is_null(&Value::Null));
499        assert!(!interp.is_null(&Value::Nil));
500        assert!(!interp.is_null(&Value::Boolean(false)));
501        assert!(!interp.is_null(&Value::Number(0.0)));
502    }
503
504    #[test]
505    fn test_is_truthy() {
506        let interp = Interpreter::new();
507
508        // Boolean values
509        assert!(interp.is_truthy(&Value::Boolean(true)));
510        assert!(!interp.is_truthy(&Value::Boolean(false)));
511
512        // Null is falsy, Nil (empty list) is truthy (like JS)
513        assert!(!interp.is_truthy(&Value::Null));
514        assert!(interp.is_truthy(&Value::Nil));
515
516        // Numbers: 0 is falsy, NaN is falsy, everything else is truthy
517        assert!(!interp.is_truthy(&Value::Number(0.0)));
518        assert!(!interp.is_truthy(&Value::Number(f64::NAN))); // NaN is falsy (like JS)
519        assert!(interp.is_truthy(&Value::Number(42.0)));
520        assert!(interp.is_truthy(&Value::Number(-1.0)));
521        assert!(interp.is_truthy(&Value::Number(f64::INFINITY)));
522        assert!(interp.is_truthy(&Value::Number(f64::NEG_INFINITY)));
523
524        // Strings: empty is falsy, non-empty is truthy
525        assert!(!interp.is_truthy(&Value::String("".into())));
526        assert!(interp.is_truthy(&Value::String("hello".into())));
527
528        // Atoms and QuotedAtoms are always truthy
529        assert!(interp.is_truthy(&Value::Atom("hello".into())));
530        assert!(interp.is_truthy(&Value::QuotedAtom("hello".into())));
531
532        // Pairs are always truthy
533        assert!(interp.is_truthy(&Value::Pair(
534            Rc::new(Value::Number(1.0)),
535            Rc::new(Value::Nil)
536        )));
537    }
538}