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    // Write text to the output without a newline
252    pub fn write_str(&mut self, text: &str) -> Result<(), ()> {
253        if let Some(output) = &mut self.output {
254            output.write(text.as_bytes())?;
255            output.flush()?;
256        }
257        Ok(())
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    // Test-only helper methods
266    impl Interpreter {
267        fn pop_string(&mut self) -> Result<Rc<str>, RuntimeError> {
268            let value = self.pop()?;
269            match value {
270                Value::String(s) => Ok(s),
271                _ => Err(RuntimeError::TypeError("Expected string".to_string())),
272            }
273        }
274
275        fn pop_boolean(&mut self) -> Result<bool, RuntimeError> {
276            let value = self.pop()?;
277            match value {
278                Value::Boolean(b) => Ok(b),
279                _ => Err(RuntimeError::TypeError("Expected boolean".to_string())),
280            }
281        }
282    }
283
284    #[test]
285    fn test_atom_interning() {
286        let mut interp = Interpreter::new();
287
288        let atom1 = interp.intern_atom("hello");
289        let atom2 = interp.intern_atom("hello");
290
291        assert!(Rc::ptr_eq(&atom1, &atom2));
292    }
293
294    #[test]
295    fn test_stack_operations() {
296        let mut interp = Interpreter::new();
297
298        interp.push(Value::Number(42.0));
299        let popped = interp.pop().unwrap();
300
301        match popped {
302            Value::Number(n) => assert_eq!(n, 42.0),
303            _ => panic!("Expected number"),
304        }
305
306        assert!(interp.pop().is_err());
307    }
308
309    #[test]
310    fn test_list_construction() {
311        let interp = Interpreter::new();
312
313        let empty = interp.make_list(vec![]);
314        match empty {
315            Value::Nil => (),
316            _ => panic!("Expected Nil for empty list"),
317        }
318
319        let single = interp.make_list(vec![Value::Number(42.0)]);
320        match single {
321            Value::Pair(car, cdr) => match (car.as_ref(), cdr.as_ref()) {
322                (Value::Number(n), Value::Nil) => assert_eq!(*n, 42.0),
323                _ => panic!("Expected Pair(42, Nil)"),
324            },
325            _ => panic!("Expected Pair for single element list"),
326        }
327
328        // Test multi-element list
329        let multi = interp.make_list(vec![
330            Value::Number(1.0),
331            Value::Number(2.0),
332            Value::Number(3.0),
333        ]);
334        match multi {
335            Value::Pair(car1, cdr1) => match (car1.as_ref(), cdr1.as_ref()) {
336                (Value::Number(n1), Value::Pair(car2, cdr2)) => {
337                    assert_eq!(*n1, 1.0);
338                    match (car2.as_ref(), cdr2.as_ref()) {
339                        (Value::Number(n2), Value::Pair(car3, cdr3)) => {
340                            assert_eq!(*n2, 2.0);
341                            match (car3.as_ref(), cdr3.as_ref()) {
342                                (Value::Number(n3), Value::Nil) => assert_eq!(*n3, 3.0),
343                                _ => panic!("Expected third element to be 3.0 followed by Nil"),
344                            }
345                        }
346                        _ => panic!("Expected second element to be 2.0"),
347                    }
348                }
349                _ => panic!("Expected first element to be 1.0"),
350            },
351            _ => panic!("Expected Pair for multi-element list"),
352        }
353    }
354
355    #[test]
356    fn test_pop_number_success() {
357        let mut interp = Interpreter::new();
358        interp.push(Value::Number(42.0));
359        assert_eq!(interp.pop_number().unwrap(), 42.0);
360    }
361
362    #[test]
363    fn test_pop_number_type_error() {
364        let mut interp = Interpreter::new();
365        interp.push(Value::Nil);
366        assert!(matches!(
367            interp.pop_number(),
368            Err(RuntimeError::TypeError(msg)) if msg == "Expected number"
369        ));
370    }
371
372    #[test]
373    fn test_pop_number_underflow() {
374        let mut interp = Interpreter::new();
375        assert!(matches!(
376            interp.pop_number(),
377            Err(RuntimeError::StackUnderflow)
378        ));
379    }
380
381    #[test]
382    fn test_dictionary_operations() {
383        let mut interp = Interpreter::new();
384
385        // Test inserting and retrieving from dictionary
386        let key = interp.intern_atom("test");
387        let entry = DictEntry {
388            value: Value::Number(99.0),
389            is_executable: false, // Constants are not executable
390            doc: None,
391        };
392        interp.dictionary.insert(key.clone(), entry);
393
394        match interp.dictionary.get(&key) {
395            Some(dict_entry) => {
396                match &dict_entry.value {
397                    Value::Number(n) => assert_eq!(*n, 99.0),
398                    _ => panic!("Expected to find Number(99.0) in dictionary entry"),
399                }
400                assert!(!dict_entry.is_executable);
401            }
402            _ => panic!("Expected to find dictionary entry"),
403        }
404
405        // Test that non-existent keys return None
406        let missing = interp.intern_atom("missing");
407        assert!(interp.dictionary.get(&missing).is_none());
408    }
409
410    #[test]
411    fn test_atom_interning_different_atoms() {
412        let mut interp = Interpreter::new();
413
414        let atom1 = interp.intern_atom("hello");
415        let atom2 = interp.intern_atom("world");
416        let atom3 = interp.intern_atom("hello");
417
418        // Same text should return same Rc
419        assert!(Rc::ptr_eq(&atom1, &atom3));
420
421        // Different text should return different Rc
422        assert!(!Rc::ptr_eq(&atom1, &atom2));
423
424        // Verify the actual content
425        assert_eq!(&*atom1, "hello");
426        assert_eq!(&*atom2, "world");
427    }
428
429    #[test]
430    fn test_pop_string() {
431        let mut interp = Interpreter::new();
432
433        // Test successful pop_string
434        let string_val: Rc<str> = "hello world".into();
435        interp.push(Value::String(string_val));
436        let s = interp.pop_string().unwrap();
437        assert_eq!(&*s, "hello world");
438
439        // Test type error when popping non-string
440        interp.push(Value::Number(42.0));
441        assert!(matches!(
442            interp.pop_string(),
443            Err(RuntimeError::TypeError(msg)) if msg == "Expected string"
444        ));
445
446        // Test stack underflow
447        assert!(matches!(
448            interp.pop_string(),
449            Err(RuntimeError::StackUnderflow)
450        ));
451    }
452
453    #[test]
454    fn test_string_vs_atom_distinction() {
455        let mut interp = Interpreter::new();
456
457        // Strings are not interned - each is separate
458        let string1 = Value::String("hello".into());
459        let string2 = Value::String("hello".into());
460
461        // Atoms are interned - same text gives same Rc
462        let atom1 = Value::Atom(interp.intern_atom("hello"));
463        let atom2 = Value::Atom(interp.intern_atom("hello"));
464
465        // Strings with same content are different Rc objects (not interned)
466        if let (Value::String(s1), Value::String(s2)) = (&string1, &string2) {
467            assert_eq!(s1, s2); // Same content
468            assert!(!Rc::ptr_eq(s1, s2)); // Different Rc objects
469        }
470
471        // Atoms with same content share the same object
472        if let (Value::Atom(a1), Value::Atom(a2)) = (&atom1, &atom2) {
473            assert_eq!(a1, a2); // Same content
474            assert!(Rc::ptr_eq(a1, a2)); // Same object
475        }
476    }
477
478    #[test]
479    fn test_pop_boolean() {
480        let mut interp = Interpreter::new();
481
482        // Test successful pop_boolean
483        interp.push(Value::Boolean(true));
484        assert_eq!(interp.pop_boolean().unwrap(), true);
485
486        interp.push(Value::Boolean(false));
487        assert_eq!(interp.pop_boolean().unwrap(), false);
488
489        // Test type error when popping non-boolean
490        interp.push(Value::Number(42.0));
491        assert!(matches!(
492            interp.pop_boolean(),
493            Err(RuntimeError::TypeError(msg)) if msg == "Expected boolean"
494        ));
495
496        // Test stack underflow
497        assert!(matches!(
498            interp.pop_boolean(),
499            Err(RuntimeError::StackUnderflow)
500        ));
501    }
502
503    #[test]
504    fn test_is_null() {
505        let interp = Interpreter::new();
506
507        assert!(interp.is_null(&Value::Null));
508        assert!(!interp.is_null(&Value::Nil));
509        assert!(!interp.is_null(&Value::Boolean(false)));
510        assert!(!interp.is_null(&Value::Number(0.0)));
511    }
512
513    #[test]
514    fn test_is_truthy() {
515        let interp = Interpreter::new();
516
517        // Boolean values
518        assert!(interp.is_truthy(&Value::Boolean(true)));
519        assert!(!interp.is_truthy(&Value::Boolean(false)));
520
521        // Null is falsy, Nil (empty list) is truthy (like JS)
522        assert!(!interp.is_truthy(&Value::Null));
523        assert!(interp.is_truthy(&Value::Nil));
524
525        // Numbers: 0 is falsy, NaN is falsy, everything else is truthy
526        assert!(!interp.is_truthy(&Value::Number(0.0)));
527        assert!(!interp.is_truthy(&Value::Number(f64::NAN))); // NaN is falsy (like JS)
528        assert!(interp.is_truthy(&Value::Number(42.0)));
529        assert!(interp.is_truthy(&Value::Number(-1.0)));
530        assert!(interp.is_truthy(&Value::Number(f64::INFINITY)));
531        assert!(interp.is_truthy(&Value::Number(f64::NEG_INFINITY)));
532
533        // Strings: empty is falsy, non-empty is truthy
534        assert!(!interp.is_truthy(&Value::String("".into())));
535        assert!(interp.is_truthy(&Value::String("hello".into())));
536
537        // Atoms and QuotedAtoms are always truthy
538        assert!(interp.is_truthy(&Value::Atom("hello".into())));
539        assert!(interp.is_truthy(&Value::QuotedAtom("hello".into())));
540
541        // Pairs are always truthy
542        assert!(interp.is_truthy(&Value::Pair(
543            Rc::new(Value::Number(1.0)),
544            Rc::new(Value::Nil)
545        )));
546    }
547}