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
141    // RUST CONCEPT: Execute the prelude code directly
142    // This uses the normal execution path - no special handling needed
143    execute_string(prelude_code, interp)?;
144
145    // RUST CONCEPT: Conditional compilation for feature-specific prelude
146    // Complex number constants (only when complex_numbers feature is enabled)
147    #[cfg(feature = "complex_numbers")]
148    {
149        let complex_prelude = r#"
150            \\ Mathematical constants (complex numbers)
151            'i 0+1i def
152            "Imaginary unit constant (0+1i)" doc
153        "#;
154        execute_string(complex_prelude, interp)?;
155    }
156
157    // RUST CONCEPT: Conditional compilation for platform-specific prelude
158    // Hardware convenience wrappers for micro:bit
159    #[cfg(target_os = "none")]
160    {
161        let hardware_prelude = r#"
162            \\ Hardware convenience wrappers (micro:bit only)
163            'button-a? [0 button-read] def
164            "( -- bool ) Read button A state (true = pressed)" doc
165
166            'button-b? [1 button-read] def
167            "( -- bool ) Read button B state (true = pressed)" doc
168        "#;
169        execute_string(hardware_prelude, interp)?;
170    }
171
172    Ok(())
173}
174
175// RUST CONCEPT: Testing the prelude
176#[cfg(test)]
177mod tests {
178    use super::*;
179    use crate::value::Value;
180
181    // RUST CONCEPT: Test helper function
182    fn setup_interpreter_with_prelude() -> Interpreter {
183        // RUST CONCEPT: Manual prelude loading for testing
184        // Interpreter::new() now automatically loads builtins and prelude
185        let mut interp = Interpreter::new();
186        load_prelude(&mut interp).unwrap();
187        interp
188    }
189
190    #[test]
191    fn test_prelude_dup() {
192        let mut interp = setup_interpreter_with_prelude();
193
194        // Test: 42 dup should give us 42 42
195        execute_string("42 dup", &mut interp).unwrap();
196
197        let top = interp.pop().unwrap();
198        let second = interp.pop().unwrap();
199
200        assert!(matches!(top, Value::Int32(42)));
201        assert!(matches!(second, Value::Int32(42)));
202    }
203
204    #[test]
205    fn test_prelude_swap() {
206        let mut interp = setup_interpreter_with_prelude();
207
208        // Test: 1 2 swap should give us 2 1
209        execute_string("1 2 swap", &mut interp).unwrap();
210
211        let top = interp.pop().unwrap();
212        let second = interp.pop().unwrap();
213
214        assert!(matches!(top, Value::Int32(1)));
215        assert!(matches!(second, Value::Int32(2)));
216    }
217
218    #[test]
219    fn test_prelude_over() {
220        let mut interp = setup_interpreter_with_prelude();
221
222        // Test: 1 2 over should give us 1 2 1
223        execute_string("1 2 over", &mut interp).unwrap();
224
225        let top = interp.pop().unwrap();
226        let second = interp.pop().unwrap();
227        let third = interp.pop().unwrap();
228
229        assert!(matches!(top, Value::Int32(1)));
230        assert!(matches!(second, Value::Int32(2)));
231        assert!(matches!(third, Value::Int32(1)));
232    }
233
234    #[test]
235    fn test_prelude_rot() {
236        let mut interp = setup_interpreter_with_prelude();
237
238        // Test: 1 2 3 rot should give us 2 3 1
239        execute_string("1 2 3 rot", &mut interp).unwrap();
240
241        let top = interp.pop().unwrap();
242        let second = interp.pop().unwrap();
243        let third = interp.pop().unwrap();
244
245        assert!(matches!(top, Value::Int32(1)));
246        assert!(matches!(second, Value::Int32(3)));
247        assert!(matches!(third, Value::Int32(2)));
248    }
249
250    #[test]
251    fn test_prelude_nip() {
252        let mut interp = setup_interpreter_with_prelude();
253
254        // Test: 1 2 3 nip should give us 1 3 (removes second item)
255        execute_string("1 2 3 nip", &mut interp).unwrap();
256
257        let top = interp.pop().unwrap();
258        let second = interp.pop().unwrap();
259
260        assert!(matches!(top, Value::Int32(3)));
261        assert!(matches!(second, Value::Int32(1)));
262
263        // Stack should be empty now
264        assert!(interp.pop().is_err());
265    }
266
267    #[test]
268    fn test_prelude_tuck() {
269        let mut interp = setup_interpreter_with_prelude();
270
271        // Test: 5 6 tuck should give us 6 5 6 (insert copy of top below second)
272        execute_string("5 6 tuck", &mut interp).unwrap();
273
274        let top = interp.pop().unwrap();
275        let second = interp.pop().unwrap();
276        let third = interp.pop().unwrap();
277
278        assert!(matches!(top, Value::Int32(6)));
279        assert!(matches!(second, Value::Int32(5)));
280        assert!(matches!(third, Value::Int32(6)));
281
282        // Stack should be empty now
283        assert!(interp.pop().is_err());
284    }
285
286    #[test]
287    fn test_prelude_length() {
288        let mut interp = setup_interpreter_with_prelude();
289
290        // Test: [1 2 3 4 5] length should give us 5
291        execute_string("[1 2 3 4 5] length", &mut interp).unwrap();
292
293        let result = interp.pop().unwrap();
294        assert!(matches!(result, Value::Int32(5)));
295
296        // Test: [] length should give us 0
297        execute_string("[] length", &mut interp).unwrap();
298
299        let result = interp.pop().unwrap();
300        assert!(matches!(result, Value::Int32(0)));
301    }
302
303    #[test]
304    fn test_prelude_null_predicate() {
305        let mut interp = setup_interpreter_with_prelude();
306
307        // Test: null null? should give us true
308        execute_string("null null?", &mut interp).unwrap();
309
310        let result = interp.pop().unwrap();
311        assert!(matches!(result, Value::Boolean(true)));
312
313        // Test: 42 null? should give us false
314        execute_string("42 null?", &mut interp).unwrap();
315
316        let result = interp.pop().unwrap();
317        assert!(matches!(result, Value::Boolean(false)));
318
319        // Test: [] null? should give us false (empty list is not null)
320        execute_string("[] null?", &mut interp).unwrap();
321
322        let result = interp.pop().unwrap();
323        assert!(matches!(result, Value::Boolean(false)));
324    }
325
326    #[test]
327    fn test_prelude_while_loop_counter() {
328        let mut interp = setup_interpreter_with_prelude();
329
330        // Test: while loop that counts from 1 and leaves 5 on the stack
331        // Start with 1, condition checks if counter < 5, body increments counter
332        // Final result should leave 5 on the stack when condition becomes false
333        let code = r#"
334            1
335            [ dup 5 < ]
336            [ 1 + ]
337            while
338        "#;
339
340        execute_string(code, &mut interp).unwrap();
341
342        // Should have 5 on stack (started at 1, incremented while < 5)
343        let result = interp.pop().unwrap();
344        assert!(matches!(result, Value::Int32(5)));
345
346        // Stack should be empty now
347        assert!(interp.pop().is_err());
348    }
349
350    #[test]
351    fn test_prelude_while_sum_accumulator() {
352        let mut interp = setup_interpreter_with_prelude();
353
354        // Test: while loop that sums numbers 1+2+3+4+5 = 15
355        // Stack: [counter sum]
356        let code = r#"
357            1 0
358            [ over 5 <= ]
359            [ over + swap 1 + swap ]
360            while
361            nip
362        "#;
363
364        execute_string(code, &mut interp).unwrap();
365
366        // Should have 15 on stack (sum of 1+2+3+4+5)
367        let result = interp.pop().unwrap();
368        assert!(matches!(result, Value::Int32(15)));
369
370        // Stack should be empty now
371        assert!(interp.pop().is_err());
372    }
373
374    #[test]
375    fn test_prelude_while_empty_body() {
376        let mut interp = setup_interpreter_with_prelude();
377
378        // Test: while loop with false condition - body should never execute
379        let code = r#"
380            42
381            [ 0 ]
382            [ 99 ]
383            while
384        "#;
385
386        execute_string(code, &mut interp).unwrap();
387
388        // Should still have 42 on stack (body never executed)
389        let result = interp.pop().unwrap();
390        assert!(matches!(result, Value::Int32(42)));
391
392        // Stack should be empty now
393        assert!(interp.pop().is_err());
394    }
395
396    #[test]
397    fn test_prelude_question_dup() {
398        let mut interp = setup_interpreter_with_prelude();
399
400        // Test: truthy value should be duplicated
401        execute_string("42 ?dup", &mut interp).unwrap();
402
403        let top = interp.pop().unwrap();
404        let second = interp.pop().unwrap();
405        assert!(matches!(top, Value::Int32(42)));
406        assert!(matches!(second, Value::Int32(42)));
407
408        // Test: falsy value (0) should not be duplicated
409        execute_string("0 ?dup", &mut interp).unwrap();
410
411        let result = interp.pop().unwrap();
412        assert!(matches!(result, Value::Int32(0)));
413        // Should be empty now - no duplication occurred
414        assert!(interp.pop().is_err());
415
416        // Test: false should not be duplicated
417        execute_string("false ?dup", &mut interp).unwrap();
418
419        let result = interp.pop().unwrap();
420        assert!(matches!(result, Value::Boolean(false)));
421        assert!(interp.pop().is_err());
422    }
423
424    #[test]
425    fn test_prelude_and_short_circuit() {
426        let mut interp = setup_interpreter_with_prelude();
427
428        // Test: true and true -> returns second value
429        execute_string("[5] [10] and", &mut interp).unwrap();
430
431        let result = interp.pop().unwrap();
432        assert!(matches!(result, Value::Int32(10)));
433
434        // Test: false and anything -> returns false, doesn't execute second
435        execute_string("[0] [99] and", &mut interp).unwrap();
436
437        let result = interp.pop().unwrap();
438        assert!(matches!(result, Value::Int32(0)));
439
440        // Test: short-circuiting - second quotation should not execute
441        // This pushes a marker, then does false and [marker-remover]
442        // If short-circuiting works, marker should still be there
443        execute_string("999 [0] [drop] and", &mut interp).unwrap();
444
445        let and_result = interp.pop().unwrap();
446        let marker = interp.pop().unwrap();
447        assert!(matches!(and_result, Value::Int32(0)));
448        assert!(matches!(marker, Value::Int32(999))); // Marker should still be there
449    }
450
451    #[test]
452    fn test_prelude_or_short_circuit() {
453        let mut interp = setup_interpreter_with_prelude();
454
455        // Test: false or true -> returns second value
456        execute_string("[0] [42] or", &mut interp).unwrap();
457
458        let result = interp.pop().unwrap();
459        assert!(matches!(result, Value::Int32(42)));
460
461        // Test: true or anything -> returns first value, doesn't execute second
462        execute_string("[5] [99] or", &mut interp).unwrap();
463
464        let result = interp.pop().unwrap();
465        assert!(matches!(result, Value::Int32(5)));
466
467        // Test: short-circuiting - second quotation should not execute
468        execute_string("888 [7] [drop] or", &mut interp).unwrap();
469
470        let or_result = interp.pop().unwrap();
471        let marker = interp.pop().unwrap();
472        assert!(matches!(or_result, Value::Int32(7)));
473        assert!(matches!(marker, Value::Int32(888))); // Marker should still be there
474    }
475
476    #[test]
477    fn test_prelude_and_or_chaining() {
478        let mut interp = setup_interpreter_with_prelude();
479
480        // Test: chaining and operations - all true
481        execute_string("[1] [2] and [3] and", &mut interp).unwrap();
482
483        let result = interp.pop().unwrap();
484        assert!(matches!(result, Value::Int32(3)));
485
486        // Test: chaining or operations - first true
487        execute_string("[1] [2] or [3] or", &mut interp).unwrap();
488
489        let result = interp.pop().unwrap();
490        assert!(matches!(result, Value::Int32(1)));
491
492        // Test: mixed and/or
493        execute_string("[0] [5] or [10] and", &mut interp).unwrap();
494
495        let result = interp.pop().unwrap();
496        assert!(matches!(result, Value::Int32(10)));
497    }
498
499    #[test]
500    #[cfg(feature = "complex_numbers")]
501    fn test_prelude_imaginary_constant() {
502        use num_bigint::BigInt;
503        let mut interp = setup_interpreter_with_prelude();
504
505        // Test: 'i' should be defined as 0+1i (GaussianInt)
506        execute_string("i", &mut interp).unwrap();
507
508        let result = interp.pop().unwrap();
509        assert!(matches!(result, Value::GaussianInt(ref re, ref im)
510            if re == &BigInt::from(0) && im == &BigInt::from(1)));
511
512        // Test: using 'i' in arithmetic: i + i = 0+2i
513        execute_string("i i +", &mut interp).unwrap();
514
515        let result = interp.pop().unwrap();
516        assert!(matches!(result, Value::GaussianInt(ref re, ref im)
517            if re == &BigInt::from(0) && im == &BigInt::from(2)));
518    }
519
520    #[test]
521    fn test_prelude_each_basic() {
522        let mut interp = setup_interpreter_with_prelude();
523
524        // Test: [1 2 3] [drop] each should consume all elements
525        execute_string("[10 20 30] [drop] each", &mut interp).unwrap();
526
527        // Stack should be empty - each consumes the list and function consumes elements
528        assert!(interp.pop().is_err());
529    }
530
531    #[test]
532    fn test_prelude_each_empty_list() {
533        let mut interp = setup_interpreter_with_prelude();
534
535        // Test: empty list should not execute function
536        execute_string("42 [] [drop] each", &mut interp).unwrap();
537
538        // The 42 should still be on stack (function never executed)
539        let result = interp.pop().unwrap();
540        assert!(matches!(result, Value::Int32(42)));
541
542        // Stack should be empty now
543        assert!(interp.pop().is_err());
544    }
545
546    #[test]
547    fn test_prelude_each_with_print() {
548        let mut interp = setup_interpreter_with_prelude();
549
550        // Test: [1 2 3] [.] each should print 1, 2, 3 (side effects only)
551        // Since [.] consumes its argument and returns nothing, stack should be clean
552        execute_string("[1 2 3] [.] each", &mut interp).unwrap();
553
554        // Stack should be empty - pure side effects
555        assert!(interp.pop().is_err());
556    }
557}