uni_core/
evaluator.rs

1// This module implements the AST-walking evaluator for Uni
2//
3// UNI EXECUTION MODEL (detailed):
4// 1. Numbers, strings, lists: Push themselves onto the stack (they are data)
5// 2. Atoms: Look up in dictionary and execute the definition
6// 3. Quoted atoms: Already parsed as (quote atom), quote builtin handles them
7// 4. Lists are data by default, use 'exec' builtin to execute them
8//
9// RUST LEARNING NOTES:
10// - This demonstrates recursive function calls and pattern matching
11// - We use Result<(), RuntimeError> to handle execution errors
12// - Mutable references (&mut) allow us to modify the interpreter state
13// - The ? operator propagates errors up the call stack automatically
14
15use crate::interpreter::Interpreter;
16use crate::value::{RuntimeError, Value};
17use crate::compat::{Rc, Vec, ToString};
18
19// RUST CONCEPT: Continuation-based evaluation for tail-call optimization
20// Instead of using recursion, we use an explicit continuation stack
21// This enables proper tail-call optimization and prevents stack overflow
22#[derive(Debug, Clone)]
23enum Continuation {
24    // Execute a single value (push data or execute atom)
25    Value(Value),
26
27    // Execute a list of values sequentially with index tracking
28    // When index == items.len()-1, we can do tail-call optimization
29    List {
30        items: Vec<Value>,
31        index: usize,
32    },
33
34    // Execute an if statement (condition already evaluated)
35    If {
36        condition_result: bool,
37        true_branch: Value,
38        false_branch: Value,
39    },
40
41    // Execute an exec'd expression
42    Exec(Value),
43
44    // Execute a defined word's body
45    Definition(Value),
46}
47
48// RUST CONCEPT: Continuation-based execution loop
49// This replaces recursion with an explicit continuation stack for tail-call optimization
50pub fn execute_with_continuations(
51    initial_value: &Value,
52    interp: &mut Interpreter,
53) -> Result<(), RuntimeError> {
54    let mut continuation_stack: Vec<Continuation> = Vec::new();
55    continuation_stack.push(Continuation::Value(initial_value.clone()));
56
57    while let Some(continuation) = continuation_stack.pop() {
58        match continuation {
59            Continuation::Value(value) => {
60                execute_value_direct(&value, interp, &mut continuation_stack)?;
61            }
62
63            Continuation::List { items, index } => {
64                if index >= items.len() {
65                    continue; // Empty list or finished
66                }
67
68                let item = &items[index];
69                let is_tail_call = index == items.len() - 1;
70
71                if is_tail_call {
72                    // TAIL-CALL OPTIMIZATION: Don't push continuation for last item
73                    // This reuses the current "stack frame" enabling proper TCO
74                    continuation_stack.push(Continuation::Value(item.clone()));
75                } else {
76                    // Push continuation for next item, then execute current
77                    continuation_stack.push(Continuation::List {
78                        items: items.clone(),
79                        index: index + 1,
80                    });
81                    continuation_stack.push(Continuation::Value(item.clone()));
82                }
83            }
84
85            Continuation::If {
86                condition_result,
87                true_branch,
88                false_branch,
89            } => {
90                let branch = if condition_result {
91                    true_branch
92                } else {
93                    false_branch
94                };
95                // TAIL-CALL OPTIMIZATION: Execute branch directly without adding continuation
96                match &branch {
97                    Value::Pair(_, _) | Value::Nil => {
98                        let items = list_to_vec(&branch)?;
99                        continuation_stack.push(Continuation::List { items, index: 0 });
100                    }
101                    _ => {
102                        continuation_stack.push(Continuation::Value(branch));
103                    }
104                }
105            }
106
107            Continuation::Exec(value) => {
108                // Convert list to continuation or execute single value directly
109                match &value {
110                    Value::Pair(_, _) => {
111                        let items = list_to_vec(&value)?;
112                        continuation_stack.push(Continuation::List { items, index: 0 });
113                    }
114                    Value::Nil => {
115                        // Empty list - do nothing
116                    }
117                    _ => {
118                        // Single value - execute directly (tail-call optimized)
119                        continuation_stack.push(Continuation::Value(value));
120                    }
121                }
122            }
123
124            Continuation::Definition(definition) => {
125                match &definition {
126                    Value::Pair(_, _) | Value::Nil => {
127                        // Execute list as code (tail-call optimized)
128                        let items = list_to_vec(&definition)?;
129                        continuation_stack.push(Continuation::List { items, index: 0 });
130                    }
131                    _ => {
132                        // Execute single value directly (tail-call optimized)
133                        continuation_stack.push(Continuation::Value(definition));
134                    }
135                }
136            }
137        }
138    }
139
140    Ok(())
141}
142
143// RUST CONCEPT: Helper function to execute a value directly and manage continuations
144// This is where atoms are looked up and special forms are handled
145fn execute_value_direct(
146    value: &Value,
147    interp: &mut Interpreter,
148    continuation_stack: &mut Vec<Continuation>,
149) -> Result<(), RuntimeError> {
150    match value {
151        // RUST CONCEPT: Simple values just push themselves onto the stack
152        Value::Number(n) => {
153            interp.push(Value::Number(*n));
154            Ok(())
155        }
156        // RUST CONCEPT: New number types also push themselves
157        Value::Int32(i) => {
158            interp.push(Value::Int32(*i));
159            Ok(())
160        }
161        Value::Integer(i) => {
162            interp.push(Value::Integer(i.clone()));
163            Ok(())
164        }
165        Value::Rational(r) => {
166            interp.push(Value::Rational(r.clone()));
167            Ok(())
168        }
169        #[cfg(feature = "complex_numbers")]
170        Value::Complex(c) => {
171            interp.push(Value::Complex(*c));
172            Ok(())
173        }
174        #[cfg(feature = "complex_numbers")]
175        Value::GaussianInt(re, im) => {
176            interp.push(Value::GaussianInt(re.clone(), im.clone()));
177            Ok(())
178        }
179        Value::String(s) => {
180            interp.push(Value::String(s.clone()));
181            Ok(())
182        }
183        Value::Boolean(b) => {
184            interp.push(Value::Boolean(*b));
185            Ok(())
186        }
187        Value::Null => {
188            interp.push(Value::Null);
189            Ok(())
190        }
191        Value::Array(array) => {
192            interp.push(Value::Array(array.clone()));
193            Ok(())
194        }
195        Value::Pair(_, _) | Value::Nil => {
196            interp.push(value.clone());
197            Ok(())
198        }
199        Value::QuotedAtom(atom_name) => {
200            interp.push(Value::Atom(atom_name.clone()));
201            Ok(())
202        }
203        Value::Builtin(func) => func(interp),
204        Value::Atom(atom_name) => {
205            execute_atom_with_continuations(atom_name, interp, continuation_stack)
206        }
207        // RUST CONCEPT: Records and record types push themselves
208        Value::Record { .. } | Value::RecordType { .. } => {
209            interp.push(value.clone());
210            Ok(())
211        }
212        // RUST CONCEPT: DateTime and Duration push themselves
213        #[cfg(feature = "datetime")]
214        Value::DateTime(_) | Value::Duration(_) => {
215            interp.push(value.clone());
216            Ok(())
217        }
218        // RUST CONCEPT: I16 buffers push themselves (they are data)
219        Value::I16Buffer(_) => {
220            interp.push(value.clone());
221            Ok(())
222        }
223    }
224}
225
226// RUST CONCEPT: Convert list structure to vector for sequential processing
227fn list_to_vec(list: &Value) -> Result<Vec<Value>, RuntimeError> {
228    let mut current = list.clone();
229    let mut items = Vec::new();
230
231    loop {
232        match current {
233            Value::Pair(car, cdr) => {
234                items.push((*car).clone());
235                current = (*cdr).clone();
236            }
237            Value::Nil => break,
238            _ => {
239                // Single value (improper list) - just return it as single item
240                items.push(current);
241                break;
242            }
243        }
244    }
245
246    Ok(items)
247}
248
249// RUST CONCEPT: Atom execution with continuation support
250fn execute_atom_with_continuations(
251    atom_name: &Rc<str>,
252    interp: &mut Interpreter,
253    continuation_stack: &mut Vec<Continuation>,
254) -> Result<(), RuntimeError> {
255    // RUST CONCEPT: Special handling for exec, if, and quit
256    if &**atom_name == "exec" {
257        let value = interp.pop()?;
258        continuation_stack.push(Continuation::Exec(value));
259        return Ok(());
260    }
261
262    if &**atom_name == "if" {
263        let false_branch = interp.pop()?;
264        let true_branch = interp.pop()?;
265        let condition = interp.pop()?;
266
267        let condition_result = interp.is_truthy(&condition);
268        continuation_stack.push(Continuation::If {
269            condition_result,
270            true_branch,
271            false_branch,
272        });
273        return Ok(());
274    }
275
276    if &**atom_name == "quit" {
277        // Return special error to signal clean exit
278        return Err(RuntimeError::QuitRequested);
279    }
280
281    // RUST CONCEPT: Dictionary lookup with continuation support
282    match interp.dictionary.get(atom_name) {
283        Some(entry) => {
284            let entry_copy = entry.clone();
285
286            if entry_copy.is_executable {
287                // Push definition execution continuation
288                continuation_stack.push(Continuation::Definition(entry_copy.value));
289            } else {
290                // Non-executable entry - just push as constant
291                interp.push(entry_copy.value);
292            }
293            Ok(())
294        }
295        None => Err(RuntimeError::UndefinedWord((**atom_name).to_string())),
296    }
297}
298
299// RUST CONCEPT: Public functions that other modules can use
300// This is the main entry point for executing Uni values
301// Now uses continuation-based execution for tail-call optimization
302pub fn execute(value: &Value, interp: &mut Interpreter) -> Result<(), RuntimeError> {
303    execute_with_continuations(value, interp)
304}
305
306// NOTE: Old execute_atom function removed - now using execute_atom_with_continuations
307
308// RUST CONCEPT: Top-level execution function
309// This parses and executes a string of Uni code
310pub fn execute_string(code: &str, interp: &mut Interpreter) -> Result<(), RuntimeError> {
311    // RUST CONCEPT: Module imports and error conversion
312    // We import parse from our parser module
313    use crate::parser::parse;
314
315    // RUST CONCEPT: Error propagation with ?
316    // parse() returns Result<Vec<Value>, ParseError>
317    // The ? converts ParseError to RuntimeError using our From implementation
318    let values = parse(code, interp)?;
319
320    // RUST CONCEPT: Iterators and error handling in loops
321    // We execute each top-level value in sequence
322    // If any execution fails, the ? will return that error immediately
323    for value in values {
324        execute(&value, interp)?;
325    }
326
327    Ok(())
328}
329
330// RUST CONCEPT: Conditional compilation for tests
331#[cfg(test)]
332mod tests {
333    use super::*;
334    // use crate::builtins::register_builtins;  // No longer needed
335
336    // RUST CONCEPT: Test helper function
337    // DRY principle - Don't Repeat Yourself
338    fn setup_interpreter() -> Interpreter {
339        // RUST CONCEPT: Automatic initialization
340        // Interpreter::new() now automatically loads builtins and prelude
341        Interpreter::new()
342    }
343
344    #[test]
345    fn test_execute_numbers() {
346        let mut interp = setup_interpreter();
347
348        // RUST CONCEPT: Testing basic functionality
349        let number = Value::Number(42.0);
350        execute(&number, &mut interp).unwrap();
351
352        // RUST CONCEPT: Verifying state changes
353        let result = interp.pop().unwrap();
354        assert!(matches!(result, Value::Number(n) if n == 42.0));
355    }
356
357    #[test]
358    fn test_execute_strings() {
359        let mut interp = setup_interpreter();
360
361        let string_val: Rc<str> = "hello world".into();
362        let string = Value::String(string_val);
363        execute(&string, &mut interp).unwrap();
364
365        let result = interp.pop().unwrap();
366        assert!(matches!(result, Value::String(s) if s.as_ref() == "hello world"));
367    }
368
369    #[test]
370    fn test_execute_lists_as_data() {
371        let mut interp = setup_interpreter();
372
373        // RUST CONCEPT: Testing that lists don't execute, just push themselves
374        let plus_atom = interp.intern_atom("+");
375        let list = interp.make_list(vec![
376            Value::Number(1.0),
377            Value::Number(2.0),
378            Value::Atom(plus_atom),
379        ]);
380
381        execute(&list, &mut interp).unwrap();
382
383        // Should have pushed the list as data, not executed it
384        let result = interp.pop().unwrap();
385        assert!(matches!(result, Value::Pair(_, _)));
386
387        // Stack should be empty (list didn't execute and push 3)
388        assert!(interp.pop().is_err());
389    }
390
391    #[test]
392    fn test_execute_builtin_functions() {
393        let mut interp = setup_interpreter();
394
395        // RUST CONCEPT: Testing builtin execution
396        // Set up stack for addition: 3 + 5
397        interp.push(Value::Number(3.0));
398        interp.push(Value::Number(5.0));
399
400        // Get the + builtin from dictionary and execute it
401        let plus_atom = interp.intern_atom("+");
402        execute(&Value::Atom(plus_atom), &mut interp).unwrap();
403
404        // Should have popped 3 and 5, pushed 8
405        let result = interp.pop().unwrap();
406        assert!(matches!(result, Value::Number(n) if n == 8.0));
407    }
408
409    #[test]
410    fn test_execute_undefined_atom() {
411        let mut interp = setup_interpreter();
412
413        // RUST CONCEPT: Testing error cases
414        let undefined_atom = interp.intern_atom("nonexistent");
415        let result = execute(&Value::Atom(undefined_atom), &mut interp);
416
417        assert!(result.is_err());
418        assert!(
419            matches!(result.unwrap_err(), RuntimeError::UndefinedWord(s) if s == "nonexistent")
420        );
421    }
422
423    #[test]
424    fn test_execute_string_simple() {
425        let mut interp = setup_interpreter();
426
427        // RUST CONCEPT: Testing integration between parser and evaluator
428        execute_string("42 3.14", &mut interp).unwrap();
429
430        // Should have two values on stack
431        let result1 = interp.pop().unwrap();
432        let result2 = interp.pop().unwrap();
433
434        assert!(matches!(result1, Value::Number(n) if n == 3.14));
435        assert!(matches!(result2, Value::Int32(42))); // Small integers use Int32
436    }
437
438    #[test]
439    fn test_execute_string_with_builtin() {
440        let mut interp = setup_interpreter();
441
442        // RUST CONCEPT: Testing complete execution flow
443        execute_string("5 3 +", &mut interp).unwrap();
444
445        // Should have executed: push 5, push 3, execute +
446        let result = interp.pop().unwrap();
447        assert!(matches!(result, Value::Int32(8))); // Small integers use Int32
448
449        // Stack should be empty
450        assert!(interp.pop().is_err());
451    }
452
453    // RUST CONCEPT: Tail-call optimization tests
454    // These tests demonstrate that the continuation-based evaluator
455    // provides proper tail-call optimization for recursive functions
456
457    #[test]
458    fn test_tail_recursive_factorial() {
459        let mut interp = setup_interpreter();
460
461        // Define simple tail-recursive countdown
462        execute_string(
463            "'count-down [dup 1 <= [drop 999] [1 - count-down] if] def",
464            &mut interp,
465        )
466        .unwrap();
467
468        // Test with small value
469        execute_string("5 count-down", &mut interp).unwrap();
470        let result = interp.pop().unwrap();
471        assert!(matches!(result, Value::Int32(999))); // Small integers use Int32
472    }
473
474    #[test]
475    fn test_deep_tail_recursion() {
476        let mut interp = setup_interpreter();
477
478        // Define a tail-recursive countdown that would overflow regular recursion
479        execute_string(
480            "'countdown [dup 0 <= [drop 42] [1 - countdown] if] def",
481            &mut interp,
482        )
483        .unwrap();
484
485        // Test with moderately deep recursion (this would cause stack overflow without TCO)
486        execute_string("1000 countdown", &mut interp).unwrap();
487        let result = interp.pop().unwrap();
488        assert!(matches!(result, Value::Int32(42))); // Small integers use Int32
489    }
490
491    #[test]
492    fn test_mutually_tail_recursive_functions() {
493        let mut interp = setup_interpreter();
494
495        // Define mutually recursive even/odd functions using available primitives
496        execute_string("'even [dup 0 = [drop true] [1 - odd] if] def", &mut interp).unwrap();
497        execute_string("'odd [dup 0 = [drop false] [1 - even] if] def", &mut interp).unwrap();
498
499        // Test even/odd with moderate numbers
500        execute_string("10 even", &mut interp).unwrap();
501        let result = interp.pop().unwrap();
502        assert!(matches!(result, Value::Boolean(true)));
503
504        execute_string("11 odd", &mut interp).unwrap();
505        let result = interp.pop().unwrap();
506        assert!(matches!(result, Value::Boolean(true)));
507    }
508
509    #[test]
510    fn test_tail_call_in_if_branches() {
511        let mut interp = setup_interpreter();
512
513        // Test that both branches of if are tail-call optimized
514        execute_string(
515            "'branch-test [dup 10 > [drop 99] [1 + branch-test] if] def",
516            &mut interp,
517        )
518        .unwrap();
519
520        execute_string("5 branch-test", &mut interp).unwrap();
521        let result = interp.pop().unwrap();
522        assert!(matches!(result, Value::Int32(99))); // Small integers use Int32
523    }
524
525    #[test]
526    fn test_execute_string_with_list() {
527        let mut interp = setup_interpreter();
528
529        // RUST CONCEPT: Testing that lists remain as data
530        execute_string("[1 2 +] 42", &mut interp).unwrap();
531
532        // Should have list and number on stack
533        let number = interp.pop().unwrap();
534        let list = interp.pop().unwrap();
535
536        assert!(matches!(number, Value::Int32(42))); // Small integers use Int32
537        assert!(matches!(list, Value::Pair(_, _)));
538    }
539
540    #[test]
541    fn test_execute_quoted_atoms() {
542        let mut interp = setup_interpreter();
543
544        // RUST CONCEPT: Testing quote behavior
545        // 'hello should parse as (quote hello) and quote should be a builtin that
546        // pushes the atom without executing it
547
548        // First we need to add a quote builtin for this test to work
549        // For now, let's test that quoted structure gets created correctly
550        let hello_atom = interp.intern_atom("hello");
551        let quote_atom = interp.intern_atom("quote");
552
553        let quoted_hello = Value::Pair(
554            Rc::new(Value::Atom(quote_atom)),
555            Rc::new(Value::Pair(
556                Rc::new(Value::Atom(hello_atom)),
557                Rc::new(Value::Nil),
558            )),
559        );
560
561        // This should push the quoted structure as data (since it's a list)
562        execute(&quoted_hello, &mut interp).unwrap();
563
564        let result = interp.pop().unwrap();
565        assert!(matches!(result, Value::Pair(_, _)));
566    }
567
568    #[test]
569    fn test_execute_string_parse_errors() {
570        let mut interp = setup_interpreter();
571
572        // RUST CONCEPT: Testing error propagation from parser
573        let result = execute_string("[1 2", &mut interp); // Unclosed bracket
574        assert!(result.is_err());
575
576        let result = execute_string("'[1 2]", &mut interp); // Invalid quote
577        assert!(result.is_err());
578    }
579}