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