uni_core/
prelude.rs

1// RUST CONCEPT: Uni Prelude Module
2// This module contains Uni's prelude definitions - the standard words loaded at startup
3// Following the Forth tradition, we define higher-level operations in terms of primitives
4
5use crate::evaluator::execute_string;
6use crate::interpreter::Interpreter;
7use crate::value::RuntimeError;
8
9// RUST CONCEPT: Prelude initialization
10// This function loads all prelude definitions into the interpreter
11pub fn load_prelude(interp: &mut Interpreter) -> Result<(), RuntimeError> {
12    // RUST CONCEPT: Define prelude words using actual Uni code
13    // This is much more natural than building def commands from string pairs
14    // Each line is real Uni code that defines a word
15    // RUST CONCEPT: Multi-line string for multiple definitions
16    // Each definition is actual Uni code that uses def naturally
17    let prelude_code = r#"
18        \\ Stack manipulation words
19        'swap [1 roll] def
20        "( a b -- b a ) Swap top two stack items" doc
21
22        'dup [0 pick] def
23        "( a -- a a ) Duplicate top stack item" doc
24
25        'over [1 pick] def
26        "( a b -- a b a ) Copy second stack item to top" doc
27
28        'rot [2 roll] def
29        "( a b c -- b c a ) Rotate third item to top" doc
30
31        'nip [swap drop] def
32        "( a b -- b ) Remove second stack item" doc
33
34        'tuck [swap over] def
35        "( a b -- b a b ) Copy top below second item" doc
36
37        'nil? [[] =] def
38        "( x -- bool ) Test if value is empty list" doc
39
40        \\ List processing primitives
41        'length [
42            dup nil?
43            [drop 0]
44            [tail length 1 +]
45            if
46        ] def
47        "( list -- n ) Calculate list length recursively" doc
48
49        'null? [null =] def
50        "( x -- bool ) Test if value is null" doc
51
52        'record? [type "record" =] def
53        "( x -- bool ) Test if value is any record type" doc
54
55        \\ Conditional duplication from Forth
56        '?dup [
57            dup truthy? [dup] [] if
58        ] def
59        "( x -- x x | x ) Duplicate if truthy, otherwise leave unchanged" doc
60
61        \\ Variable operations (Forth-style)
62        '1+ [1 +] def
63        "( n -- n+1 ) Increment by 1" doc
64
65        '1- [1 -] def
66        "( n -- n-1 ) Decrement by 1" doc
67
68        '+! [dup @ rot + swap !] def
69        "( n var -- ) Add n to variable" doc
70
71        'on [true swap !] def
72        "( var -- ) Store true to variable" doc
73
74        'off [false swap !] def
75        "( var -- ) Store false to variable" doc
76
77        \\ List iteration
78        'each [
79            >r                      \\ Move fn to return stack: list | fn
80            dup nil?                \\ Check if list is empty: list bool | fn
81            [
82                drop r> drop        \\ Empty list: clean up list and fn
83            ]
84            [
85                dup head            \\ list -> list head | fn
86                r@                  \\ Get fn: list head fn | fn
87                exec                \\ Execute fn: list ... | fn (fn consumes head, may leave results)
88                tail                \\ Get tail: ... tail | fn
89                r> each             \\ Recurse: ... tail fn
90            ]
91            if
92        ] def
93        "( list [fn] -- ) Execute fn on each element of list (fn consumes argument, may leave results)" doc
94
95        \\ Short-circuiting logical operations
96        'and [
97            swap                          \\ Move first quotation to top
98            exec                          \\ Execute first quotation
99            dup                           \\ Always duplicate the result
100            [
101                drop                      \\ Drop the duplicate, keep original
102                exec                      \\ Execute second quotation
103            ]
104            [
105                swap drop                 \\ If falsy, drop second quotation, keep falsy result
106            ]
107            if
108        ] def
109        "( [cond1] [cond2] -- result ) Short-circuit AND: executes cond2 only if cond1 is truthy" doc
110
111        'or [
112            swap                          \\ Move first quotation to top
113            exec                          \\ Execute first quotation
114            dup                           \\ Always duplicate the result
115            [
116                swap drop                 \\ If truthy, drop second quotation, keep result
117            ]
118            [
119                drop                      \\ Drop the duplicate
120                exec                      \\ If falsy, execute second quotation
121            ]
122            if
123        ] def
124        "( [cond1] [cond2] -- result ) Short-circuit OR: executes cond2 only if cond1 is falsy" doc
125
126        \\ Control flow primitives
127        'while [
128            >r >r                         \\ move body and condition to return stack
129            r@ exec                       \\ execute condition (copy from R-stack)
130            [
131                r> r> dup rot swap >r >r  \\ get body and move body and condition back to return stack
132                exec                      \\ execute body
133                r> r> while               \\ recursive call
134            ]
135            [ r> r> drop drop ]
136            if
137        ] def
138        "( [condition] [body] -- ) Loop: executes body while condition returns truthy" doc
139
140        \\ Date/time operations
141        \\ Date record type with calendar components
142        \\ The 'now' primitive (written in Rust) returns instances of this record type
143        ["year" "month" "day" "hour" "minute" "second" "offset"] "date" make-record-type drop
144    "#;
145
146    // RUST CONCEPT: Execute the prelude code directly
147    // This uses the normal execution path - no special handling needed
148    execute_string(prelude_code, interp)?;
149
150    // RUST CONCEPT: Conditional compilation for feature-specific prelude
151    // Complex number constants (only when complex_numbers feature is enabled)
152    #[cfg(feature = "complex_numbers")]
153    {
154        let complex_prelude = r#"
155            \\ Mathematical constants (complex numbers)
156            'i 0+1i def
157            "Imaginary unit constant (0+1i)" doc
158        "#;
159        execute_string(complex_prelude, interp)?;
160    }
161
162    // RUST CONCEPT: Conditional compilation for platform-specific prelude
163    // Hardware convenience wrappers for micro:bit
164    #[cfg(target_os = "none")]
165    {
166        let hardware_prelude = r#"
167            \\ Hardware convenience wrappers (micro:bit only)
168            'button-a? [0 button-read] def
169            "( -- bool ) Read button A state (true = pressed)" doc
170
171            'button-b? [1 button-read] def
172            "( -- bool ) Read button B state (true = pressed)" doc
173        "#;
174        execute_string(hardware_prelude, interp)?;
175    }
176
177    Ok(())
178}
179
180// RUST CONCEPT: Testing the prelude
181#[cfg(test)]
182mod tests {
183    use super::*;
184    use crate::value::Value;
185
186    // RUST CONCEPT: Test helper function
187    fn setup_interpreter_with_prelude() -> Interpreter {
188        // RUST CONCEPT: Manual prelude loading for testing
189        // Interpreter::new() now automatically loads builtins and prelude
190        let mut interp = Interpreter::new();
191        load_prelude(&mut interp).unwrap();
192        interp
193    }
194
195    #[test]
196    fn test_prelude_dup() {
197        let mut interp = setup_interpreter_with_prelude();
198
199        // Test: 42 dup should give us 42 42
200        execute_string("42 dup", &mut interp).unwrap();
201
202        let top = interp.pop().unwrap();
203        let second = interp.pop().unwrap();
204
205        assert!(matches!(top, Value::Int32(42)));
206        assert!(matches!(second, Value::Int32(42)));
207    }
208
209    #[test]
210    fn test_prelude_swap() {
211        let mut interp = setup_interpreter_with_prelude();
212
213        // Test: 1 2 swap should give us 2 1
214        execute_string("1 2 swap", &mut interp).unwrap();
215
216        let top = interp.pop().unwrap();
217        let second = interp.pop().unwrap();
218
219        assert!(matches!(top, Value::Int32(1)));
220        assert!(matches!(second, Value::Int32(2)));
221    }
222
223    #[test]
224    fn test_prelude_over() {
225        let mut interp = setup_interpreter_with_prelude();
226
227        // Test: 1 2 over should give us 1 2 1
228        execute_string("1 2 over", &mut interp).unwrap();
229
230        let top = interp.pop().unwrap();
231        let second = interp.pop().unwrap();
232        let third = interp.pop().unwrap();
233
234        assert!(matches!(top, Value::Int32(1)));
235        assert!(matches!(second, Value::Int32(2)));
236        assert!(matches!(third, Value::Int32(1)));
237    }
238
239    #[test]
240    fn test_prelude_rot() {
241        let mut interp = setup_interpreter_with_prelude();
242
243        // Test: 1 2 3 rot should give us 2 3 1
244        execute_string("1 2 3 rot", &mut interp).unwrap();
245
246        let top = interp.pop().unwrap();
247        let second = interp.pop().unwrap();
248        let third = interp.pop().unwrap();
249
250        assert!(matches!(top, Value::Int32(1)));
251        assert!(matches!(second, Value::Int32(3)));
252        assert!(matches!(third, Value::Int32(2)));
253    }
254
255    #[test]
256    fn test_prelude_nip() {
257        let mut interp = setup_interpreter_with_prelude();
258
259        // Test: 1 2 3 nip should give us 1 3 (removes second item)
260        execute_string("1 2 3 nip", &mut interp).unwrap();
261
262        let top = interp.pop().unwrap();
263        let second = interp.pop().unwrap();
264
265        assert!(matches!(top, Value::Int32(3)));
266        assert!(matches!(second, Value::Int32(1)));
267
268        // Stack should be empty now
269        assert!(interp.pop().is_err());
270    }
271
272    #[test]
273    fn test_prelude_tuck() {
274        let mut interp = setup_interpreter_with_prelude();
275
276        // Test: 5 6 tuck should give us 6 5 6 (insert copy of top below second)
277        execute_string("5 6 tuck", &mut interp).unwrap();
278
279        let top = interp.pop().unwrap();
280        let second = interp.pop().unwrap();
281        let third = interp.pop().unwrap();
282
283        assert!(matches!(top, Value::Int32(6)));
284        assert!(matches!(second, Value::Int32(5)));
285        assert!(matches!(third, Value::Int32(6)));
286
287        // Stack should be empty now
288        assert!(interp.pop().is_err());
289    }
290
291    #[test]
292    fn test_prelude_length() {
293        let mut interp = setup_interpreter_with_prelude();
294
295        // Test: [1 2 3 4 5] length should give us 5
296        execute_string("[1 2 3 4 5] length", &mut interp).unwrap();
297
298        let result = interp.pop().unwrap();
299        assert!(matches!(result, Value::Int32(5)));
300
301        // Test: [] length should give us 0
302        execute_string("[] length", &mut interp).unwrap();
303
304        let result = interp.pop().unwrap();
305        assert!(matches!(result, Value::Int32(0)));
306    }
307
308    #[test]
309    fn test_prelude_null_predicate() {
310        let mut interp = setup_interpreter_with_prelude();
311
312        // Test: null null? should give us true
313        execute_string("null null?", &mut interp).unwrap();
314
315        let result = interp.pop().unwrap();
316        assert!(matches!(result, Value::Boolean(true)));
317
318        // Test: 42 null? should give us false
319        execute_string("42 null?", &mut interp).unwrap();
320
321        let result = interp.pop().unwrap();
322        assert!(matches!(result, Value::Boolean(false)));
323
324        // Test: [] null? should give us false (empty list is not null)
325        execute_string("[] null?", &mut interp).unwrap();
326
327        let result = interp.pop().unwrap();
328        assert!(matches!(result, Value::Boolean(false)));
329    }
330
331    #[test]
332    fn test_prelude_while_loop_counter() {
333        let mut interp = setup_interpreter_with_prelude();
334
335        // Test: while loop that counts from 1 and leaves 5 on the stack
336        // Start with 1, condition checks if counter < 5, body increments counter
337        // Final result should leave 5 on the stack when condition becomes false
338        let code = r#"
339            1
340            [ dup 5 < ]
341            [ 1 + ]
342            while
343        "#;
344
345        execute_string(code, &mut interp).unwrap();
346
347        // Should have 5 on stack (started at 1, incremented while < 5)
348        let result = interp.pop().unwrap();
349        assert!(matches!(result, Value::Int32(5)));
350
351        // Stack should be empty now
352        assert!(interp.pop().is_err());
353    }
354
355    #[test]
356    fn test_prelude_while_sum_accumulator() {
357        let mut interp = setup_interpreter_with_prelude();
358
359        // Test: while loop that sums numbers 1+2+3+4+5 = 15
360        // Stack: [counter sum]
361        let code = r#"
362            1 0
363            [ over 5 <= ]
364            [ over + swap 1 + swap ]
365            while
366            nip
367        "#;
368
369        execute_string(code, &mut interp).unwrap();
370
371        // Should have 15 on stack (sum of 1+2+3+4+5)
372        let result = interp.pop().unwrap();
373        assert!(matches!(result, Value::Int32(15)));
374
375        // Stack should be empty now
376        assert!(interp.pop().is_err());
377    }
378
379    #[test]
380    fn test_prelude_while_empty_body() {
381        let mut interp = setup_interpreter_with_prelude();
382
383        // Test: while loop with false condition - body should never execute
384        let code = r#"
385            42
386            [ 0 ]
387            [ 99 ]
388            while
389        "#;
390
391        execute_string(code, &mut interp).unwrap();
392
393        // Should still have 42 on stack (body never executed)
394        let result = interp.pop().unwrap();
395        assert!(matches!(result, Value::Int32(42)));
396
397        // Stack should be empty now
398        assert!(interp.pop().is_err());
399    }
400
401    #[test]
402    fn test_prelude_question_dup() {
403        let mut interp = setup_interpreter_with_prelude();
404
405        // Test: truthy value should be duplicated
406        execute_string("42 ?dup", &mut interp).unwrap();
407
408        let top = interp.pop().unwrap();
409        let second = interp.pop().unwrap();
410        assert!(matches!(top, Value::Int32(42)));
411        assert!(matches!(second, Value::Int32(42)));
412
413        // Test: falsy value (0) should not be duplicated
414        execute_string("0 ?dup", &mut interp).unwrap();
415
416        let result = interp.pop().unwrap();
417        assert!(matches!(result, Value::Int32(0)));
418        // Should be empty now - no duplication occurred
419        assert!(interp.pop().is_err());
420
421        // Test: false should not be duplicated
422        execute_string("false ?dup", &mut interp).unwrap();
423
424        let result = interp.pop().unwrap();
425        assert!(matches!(result, Value::Boolean(false)));
426        assert!(interp.pop().is_err());
427    }
428
429    #[test]
430    fn test_prelude_and_short_circuit() {
431        let mut interp = setup_interpreter_with_prelude();
432
433        // Test: true and true -> returns second value
434        execute_string("[5] [10] and", &mut interp).unwrap();
435
436        let result = interp.pop().unwrap();
437        assert!(matches!(result, Value::Int32(10)));
438
439        // Test: false and anything -> returns false, doesn't execute second
440        execute_string("[0] [99] and", &mut interp).unwrap();
441
442        let result = interp.pop().unwrap();
443        assert!(matches!(result, Value::Int32(0)));
444
445        // Test: short-circuiting - second quotation should not execute
446        // This pushes a marker, then does false and [marker-remover]
447        // If short-circuiting works, marker should still be there
448        execute_string("999 [0] [drop] and", &mut interp).unwrap();
449
450        let and_result = interp.pop().unwrap();
451        let marker = interp.pop().unwrap();
452        assert!(matches!(and_result, Value::Int32(0)));
453        assert!(matches!(marker, Value::Int32(999))); // Marker should still be there
454    }
455
456    #[test]
457    fn test_prelude_or_short_circuit() {
458        let mut interp = setup_interpreter_with_prelude();
459
460        // Test: false or true -> returns second value
461        execute_string("[0] [42] or", &mut interp).unwrap();
462
463        let result = interp.pop().unwrap();
464        assert!(matches!(result, Value::Int32(42)));
465
466        // Test: true or anything -> returns first value, doesn't execute second
467        execute_string("[5] [99] or", &mut interp).unwrap();
468
469        let result = interp.pop().unwrap();
470        assert!(matches!(result, Value::Int32(5)));
471
472        // Test: short-circuiting - second quotation should not execute
473        execute_string("888 [7] [drop] or", &mut interp).unwrap();
474
475        let or_result = interp.pop().unwrap();
476        let marker = interp.pop().unwrap();
477        assert!(matches!(or_result, Value::Int32(7)));
478        assert!(matches!(marker, Value::Int32(888))); // Marker should still be there
479    }
480
481    #[test]
482    fn test_prelude_and_or_chaining() {
483        let mut interp = setup_interpreter_with_prelude();
484
485        // Test: chaining and operations - all true
486        execute_string("[1] [2] and [3] and", &mut interp).unwrap();
487
488        let result = interp.pop().unwrap();
489        assert!(matches!(result, Value::Int32(3)));
490
491        // Test: chaining or operations - first true
492        execute_string("[1] [2] or [3] or", &mut interp).unwrap();
493
494        let result = interp.pop().unwrap();
495        assert!(matches!(result, Value::Int32(1)));
496
497        // Test: mixed and/or
498        execute_string("[0] [5] or [10] and", &mut interp).unwrap();
499
500        let result = interp.pop().unwrap();
501        assert!(matches!(result, Value::Int32(10)));
502    }
503
504    #[test]
505    #[cfg(feature = "complex_numbers")]
506    fn test_prelude_imaginary_constant() {
507        use num_bigint::BigInt;
508        let mut interp = setup_interpreter_with_prelude();
509
510        // Test: 'i' should be defined as 0+1i (GaussianInt)
511        execute_string("i", &mut interp).unwrap();
512
513        let result = interp.pop().unwrap();
514        assert!(matches!(result, Value::GaussianInt(ref re, ref im)
515            if re == &BigInt::from(0) && im == &BigInt::from(1)));
516
517        // Test: using 'i' in arithmetic: i + i = 0+2i
518        execute_string("i i +", &mut interp).unwrap();
519
520        let result = interp.pop().unwrap();
521        assert!(matches!(result, Value::GaussianInt(ref re, ref im)
522            if re == &BigInt::from(0) && im == &BigInt::from(2)));
523    }
524
525    #[test]
526    fn test_prelude_each_basic() {
527        let mut interp = setup_interpreter_with_prelude();
528
529        // Test: [1 2 3] [drop] each should consume all elements
530        execute_string("[10 20 30] [drop] each", &mut interp).unwrap();
531
532        // Stack should be empty - each consumes the list and function consumes elements
533        assert!(interp.pop().is_err());
534    }
535
536    #[test]
537    fn test_prelude_each_empty_list() {
538        let mut interp = setup_interpreter_with_prelude();
539
540        // Test: empty list should not execute function
541        execute_string("42 [] [drop] each", &mut interp).unwrap();
542
543        // The 42 should still be on stack (function never executed)
544        let result = interp.pop().unwrap();
545        assert!(matches!(result, Value::Int32(42)));
546
547        // Stack should be empty now
548        assert!(interp.pop().is_err());
549    }
550
551    #[test]
552    fn test_prelude_each_with_print() {
553        let mut interp = setup_interpreter_with_prelude();
554
555        // Test: [1 2 3] [.] each should print 1, 2, 3 (side effects only)
556        // Since [.] consumes its argument and returns nothing, stack should be clean
557        execute_string("[1 2 3] [.] each", &mut interp).unwrap();
558
559        // Stack should be empty - pure side effects
560        assert!(interp.pop().is_err());
561    }
562}