1
// Skipped under Miri: these tests compile+run wasm via wasmtime, whose
2
// Cranelift backend refuses to run under Miri.
3
#![cfg(not(miri))]
4

            
5
use nms::interpreter::Interpreter;
6
use scripting::nomiscript::{Fraction, Value};
7

            
8
// IF
9

            
10
#[test]
11
1
fn test_eval_if_true() {
12
1
    let mut interp = Interpreter::new(false).unwrap();
13
1
    assert_eq!(
14
1
        interp.eval("(if #t 1 2)").unwrap(),
15
1
        vec![Value::Number(Fraction::from_integer(1))]
16
    );
17
1
}
18

            
19
#[test]
20
1
fn test_eval_if_nil() {
21
1
    let mut interp = Interpreter::new(false).unwrap();
22
1
    assert_eq!(
23
1
        interp.eval("(if nil 1 2)").unwrap(),
24
1
        vec![Value::Number(Fraction::from_integer(2))]
25
    );
26
1
}
27

            
28
#[test]
29
1
fn test_eval_if_false_is_nil() {
30
1
    let mut interp = Interpreter::new(false).unwrap();
31
1
    assert_eq!(
32
1
        interp.eval("(if #f 1 2)").unwrap(),
33
1
        vec![Value::Number(Fraction::from_integer(2))]
34
    );
35
1
}
36

            
37
#[test]
38
1
fn test_eval_if_zero_is_truthy() {
39
1
    let mut interp = Interpreter::new(false).unwrap();
40
1
    assert_eq!(
41
1
        interp.eval("(if 0 1 2)").unwrap(),
42
1
        vec![Value::Number(Fraction::from_integer(1))]
43
    );
44
1
}
45

            
46
#[test]
47
1
fn test_eval_if_empty_string_is_truthy() {
48
1
    let mut interp = Interpreter::new(false).unwrap();
49
1
    assert_eq!(
50
1
        interp.eval(r#"(if "" 1 2)"#).unwrap(),
51
1
        vec![Value::Number(Fraction::from_integer(1))]
52
    );
53
1
}
54

            
55
#[test]
56
1
fn test_eval_if_no_else_false() {
57
1
    let mut interp = Interpreter::new(false).unwrap();
58
1
    assert_eq!(interp.eval("(if nil 1)").unwrap(), vec![Value::Nil]);
59
1
}
60

            
61
#[test]
62
1
fn test_eval_if_no_else_true() {
63
1
    let mut interp = Interpreter::new(false).unwrap();
64
1
    assert_eq!(
65
1
        interp.eval("(if #t 1)").unwrap(),
66
1
        vec![Value::Number(Fraction::from_integer(1))]
67
    );
68
1
}
69

            
70
#[test]
71
1
fn test_eval_if_with_expressions() {
72
1
    let mut interp = Interpreter::new(false).unwrap();
73
1
    assert_eq!(
74
1
        interp.eval("(if (= 1 1) (+ 10 20) (+ 30 40))").unwrap(),
75
1
        vec![Value::Number(Fraction::from_integer(30))]
76
    );
77
1
    assert_eq!(
78
1
        interp.eval("(if (= 1 2) (+ 10 20) (+ 30 40))").unwrap(),
79
1
        vec![Value::Number(Fraction::from_integer(70))]
80
    );
81
1
}
82

            
83
#[test]
84
1
fn test_eval_if_arity_error() {
85
1
    let mut interp = Interpreter::new(false).unwrap();
86
1
    assert!(interp.eval("(if #t)").is_err());
87
1
    assert!(interp.eval("(if #t 1 2 3)").is_err());
88
1
}
89

            
90
// COND
91

            
92
#[test]
93
1
fn test_eval_cond_basic() {
94
1
    let mut interp = Interpreter::new(false).unwrap();
95
1
    assert_eq!(
96
1
        interp.eval("(cond (#t 1))").unwrap(),
97
1
        vec![Value::Number(Fraction::from_integer(1))]
98
    );
99
1
}
100

            
101
#[test]
102
1
fn test_eval_cond_second_clause() {
103
1
    let mut interp = Interpreter::new(false).unwrap();
104
1
    assert_eq!(
105
1
        interp.eval("(cond (nil 1) (#t 2))").unwrap(),
106
1
        vec![Value::Number(Fraction::from_integer(2))]
107
    );
108
1
}
109

            
110
#[test]
111
1
fn test_eval_cond_no_match() {
112
1
    let mut interp = Interpreter::new(false).unwrap();
113
1
    assert_eq!(interp.eval("(cond (nil 1))").unwrap(), vec![Value::Nil]);
114
1
}
115

            
116
#[test]
117
1
fn test_eval_cond_no_clauses() {
118
1
    let mut interp = Interpreter::new(false).unwrap();
119
1
    assert_eq!(interp.eval("(cond)").unwrap(), vec![Value::Nil]);
120
1
}
121

            
122
#[test]
123
1
fn test_eval_cond_test_only_clause() {
124
1
    let mut interp = Interpreter::new(false).unwrap();
125
1
    assert_eq!(interp.eval("(cond (#t))").unwrap(), vec![Value::Bool(true)]);
126
1
}
127

            
128
#[test]
129
1
fn test_eval_cond_with_expressions() {
130
1
    let mut interp = Interpreter::new(false).unwrap();
131
1
    assert_eq!(
132
1
        interp.eval("(cond ((= 1 1) (+ 10 20)))").unwrap(),
133
1
        vec![Value::Number(Fraction::from_integer(30))]
134
    );
135
1
}
136

            
137
#[test]
138
1
fn test_eval_cond_third_clause() {
139
1
    let mut interp = Interpreter::new(false).unwrap();
140
1
    assert_eq!(
141
1
        interp.eval("(cond (nil 1) (nil 2) (#t 3))").unwrap(),
142
1
        vec![Value::Number(Fraction::from_integer(3))]
143
    );
144
1
}
145

            
146
#[test]
147
1
fn test_eval_cond_multiple_body() {
148
1
    let mut interp = Interpreter::new(false).unwrap();
149
1
    assert_eq!(
150
1
        interp.eval("(cond (#t (+ 1 1) (+ 2 2)))").unwrap(),
151
1
        vec![Value::Number(Fraction::from_integer(4))]
152
    );
153
1
}
154

            
155
// DO
156

            
157
#[test]
158
1
fn test_eval_do_countdown() {
159
1
    let mut interp = Interpreter::new(false).unwrap();
160
1
    assert_eq!(
161
1
        interp
162
1
            .eval(r#"(do ((i 5 (- i 1))) ((= i 0) "done"))"#)
163
1
            .unwrap(),
164
1
        vec![Value::String("done".to_string())]
165
    );
166
1
}
167

            
168
#[test]
169
1
fn test_eval_do_accumulator() {
170
1
    let mut interp = Interpreter::new(false).unwrap();
171
1
    assert_eq!(
172
1
        interp
173
1
            .eval("(do ((i 0 (+ i 1)) (sum 0 (+ sum i))) ((= i 5) sum))")
174
1
            .unwrap(),
175
1
        vec![Value::Number(Fraction::from_integer(10))]
176
    );
177
1
}
178

            
179
#[test]
180
1
fn test_eval_do_no_result_forms() {
181
1
    let mut interp = Interpreter::new(false).unwrap();
182
1
    assert_eq!(
183
1
        interp.eval("(do ((i 3 (- i 1))) ((= i 0)))").unwrap(),
184
1
        vec![Value::Nil]
185
    );
186
1
}
187

            
188
#[test]
189
1
fn test_eval_do_no_step() {
190
1
    let mut interp = Interpreter::new(false).unwrap();
191
1
    assert_eq!(
192
1
        interp
193
1
            .eval("(do ((x 10) (i 3 (- i 1))) ((= i 0) x))")
194
1
            .unwrap(),
195
1
        vec![Value::Number(Fraction::from_integer(10))]
196
    );
197
1
}
198

            
199
#[test]
200
1
fn test_eval_do_bare_var() {
201
1
    let mut interp = Interpreter::new(false).unwrap();
202
1
    assert_eq!(
203
1
        interp.eval(r#"(do ((x)) ((eql x nil) "nil"))"#).unwrap(),
204
1
        vec![Value::String("nil".to_string())]
205
    );
206
1
}
207

            
208
#[test]
209
1
fn test_eval_do_arity_error() {
210
1
    let mut interp = Interpreter::new(false).unwrap();
211
1
    assert!(interp.eval("(do)").is_err());
212
1
    assert!(interp.eval("(do ())").is_err());
213
1
}
214

            
215
// DO*
216

            
217
#[test]
218
1
fn test_eval_do_star_sequential_init() {
219
1
    let mut interp = Interpreter::new(false).unwrap();
220
1
    assert_eq!(
221
1
        interp
222
1
            .eval("(do* ((a 1) (b (+ a 1))) (#t (+ a b)))")
223
1
            .unwrap(),
224
1
        vec![Value::Number(Fraction::from_integer(3))]
225
    );
226
1
}
227

            
228
#[test]
229
1
fn test_eval_do_star_sequential_step() {
230
1
    let mut interp = Interpreter::new(false).unwrap();
231
1
    assert_eq!(
232
1
        interp
233
1
            .eval("(do* ((i 0 (+ i 1)) (j 0 i)) ((= i 3) j))")
234
1
            .unwrap(),
235
1
        vec![Value::Number(Fraction::from_integer(3))]
236
    );
237
1
}
238

            
239
// Large-iteration DO loops — exceeds MAX_STATIC_LOOP_ITERS (64), must fall back to runtime path
240

            
241
#[test]
242
1
fn test_eval_do_large_loop_falls_back_to_runtime() {
243
1
    let mut interp = Interpreter::new(false).unwrap();
244
1
    assert_eq!(
245
1
        interp
246
1
            .eval("(do ((i 0 (+ i 1)) (sum 0 (+ sum i))) ((= i 100) sum))")
247
1
            .unwrap(),
248
1
        vec![Value::Number(Fraction::from_integer(4950))]
249
    );
250
1
}
251

            
252
#[test]
253
1
fn test_eval_do_runtime_bool_var_keeps_bool_fidelity() {
254
    // A DO loop var initialised from a bool literal must be sized as
255
    // `WasmType::Bool` (mirroring `compile_for_stack`), not I32 — else when the
256
    // runtime-fallback path reads it back as the result, it serializes as
257
    // Number(1) instead of Bool(true). The large loop forces the runtime path;
258
    // `flag` is carried unchanged and returned at exit.
259
1
    let mut interp = Interpreter::new(false).unwrap();
260
1
    assert_eq!(
261
1
        interp
262
1
            .eval("(do ((i 0 (+ i 1)) (flag #t)) ((= i 100) flag))")
263
1
            .unwrap(),
264
1
        vec![Value::Bool(true)]
265
    );
266
1
}
267

            
268
#[test]
269
1
fn test_eval_do_accumulator_car_with_user_fn_inside_native() {
270
    // The accumulator pair-element inference may eval a cons car to learn its
271
    // type, but ONLY when the car is pure-native (no user-callable anywhere).
272
    // A user fn nested under a native head (`(+ 1 (dbl i))`) makes the car
273
    // non-pure-native, so inference SKIPS the eval and lets the codegen path
274
    // size the accumulator — it must still compile and run correctly. `dbl` is
275
    // intentionally non-recursive (a recursive runtime-arg defun trips a
276
    // separate, pre-existing inlining-overflow unrelated to this path).
277
1
    let mut interp = Interpreter::new(false).unwrap();
278
1
    assert_eq!(
279
1
        interp
280
1
            .eval(
281
1
                "(defun dbl (x) (* x 2)) \
282
1
                 (car (do ((i 0 (+ i 1)) (acc nil (cons (+ 1 (dbl i)) acc))) ((= i 2) acc)))"
283
            )
284
1
            .unwrap(),
285
        // i goes 0,1; cons prepends so acc=((+1 (dbl 1)) (+1 (dbl 0)))=(3 1); car=3.
286
1
        vec![Value::Number(Fraction::from_integer(3))]
287
    );
288
1
}
289

            
290
#[test]
291
1
fn test_eval_do_accumulator_car_with_quoted_literal() {
292
    // A side-effect-free quoted-literal car `(car '(5))` is pure (no
293
    // user-callable), so the accumulator pair-element walker still eval-probes
294
    // it for type inference — the purity gate admits `Quote`/`Keyword` as inert.
295
    // The loop accumulates the constant 5; CAR of the result is 5.
296
1
    let mut interp = Interpreter::new(false).unwrap();
297
1
    assert_eq!(
298
1
        interp
299
1
            .eval(
300
1
                "(car (do ((i 0 (+ i 1)) (acc nil (cons (car (quote (5))) acc))) \
301
1
                 ((= i 2) acc)))"
302
            )
303
1
            .unwrap(),
304
1
        vec![Value::Number(Fraction::from_integer(5))]
305
    );
306
1
}
307

            
308
#[test]
309
1
fn test_eval_do_star_large_loop_falls_back_to_runtime() {
310
1
    let mut interp = Interpreter::new(false).unwrap();
311
    // DO* steps sequentially: acc's step sees the already-incremented i, so sum = 1+2+...+100
312
1
    assert_eq!(
313
1
        interp
314
1
            .eval("(do* ((i 0 (+ i 1)) (acc 0 (+ acc i))) ((= i 100) acc))")
315
1
            .unwrap(),
316
1
        vec![Value::Number(Fraction::from_integer(5050))]
317
    );
318
1
}