Lines
100 %
Functions
Branches
// Skipped under Miri: these tests compile+run wasm via wasmtime, whose
// Cranelift backend refuses to run under Miri.
#![cfg(not(miri))]
use nms::interpreter::Interpreter;
use scripting::nomiscript::{Fraction, Value};
// IF
#[test]
fn test_eval_if_true() {
let mut interp = Interpreter::new(false).unwrap();
assert_eq!(
interp.eval("(if #t 1 2)").unwrap(),
vec![Value::Number(Fraction::from_integer(1))]
);
}
fn test_eval_if_nil() {
interp.eval("(if nil 1 2)").unwrap(),
vec![Value::Number(Fraction::from_integer(2))]
fn test_eval_if_false_is_nil() {
interp.eval("(if #f 1 2)").unwrap(),
fn test_eval_if_zero_is_truthy() {
interp.eval("(if 0 1 2)").unwrap(),
fn test_eval_if_empty_string_is_truthy() {
interp.eval(r#"(if "" 1 2)"#).unwrap(),
fn test_eval_if_no_else_false() {
assert_eq!(interp.eval("(if nil 1)").unwrap(), vec![Value::Nil]);
fn test_eval_if_no_else_true() {
interp.eval("(if #t 1)").unwrap(),
fn test_eval_if_with_expressions() {
interp.eval("(if (= 1 1) (+ 10 20) (+ 30 40))").unwrap(),
vec![Value::Number(Fraction::from_integer(30))]
interp.eval("(if (= 1 2) (+ 10 20) (+ 30 40))").unwrap(),
vec![Value::Number(Fraction::from_integer(70))]
fn test_eval_if_arity_error() {
assert!(interp.eval("(if #t)").is_err());
assert!(interp.eval("(if #t 1 2 3)").is_err());
// COND
fn test_eval_cond_basic() {
interp.eval("(cond (#t 1))").unwrap(),
fn test_eval_cond_second_clause() {
interp.eval("(cond (nil 1) (#t 2))").unwrap(),
fn test_eval_cond_no_match() {
assert_eq!(interp.eval("(cond (nil 1))").unwrap(), vec![Value::Nil]);
fn test_eval_cond_no_clauses() {
assert_eq!(interp.eval("(cond)").unwrap(), vec![Value::Nil]);
fn test_eval_cond_test_only_clause() {
assert_eq!(interp.eval("(cond (#t))").unwrap(), vec![Value::Bool(true)]);
fn test_eval_cond_with_expressions() {
interp.eval("(cond ((= 1 1) (+ 10 20)))").unwrap(),
fn test_eval_cond_third_clause() {
interp.eval("(cond (nil 1) (nil 2) (#t 3))").unwrap(),
vec![Value::Number(Fraction::from_integer(3))]
fn test_eval_cond_multiple_body() {
interp.eval("(cond (#t (+ 1 1) (+ 2 2)))").unwrap(),
vec![Value::Number(Fraction::from_integer(4))]
// DO
fn test_eval_do_countdown() {
interp
.eval(r#"(do ((i 5 (- i 1))) ((= i 0) "done"))"#)
.unwrap(),
vec![Value::String("done".to_string())]
fn test_eval_do_accumulator() {
.eval("(do ((i 0 (+ i 1)) (sum 0 (+ sum i))) ((= i 5) sum))")
vec![Value::Number(Fraction::from_integer(10))]
fn test_eval_do_no_result_forms() {
interp.eval("(do ((i 3 (- i 1))) ((= i 0)))").unwrap(),
vec![Value::Nil]
fn test_eval_do_no_step() {
.eval("(do ((x 10) (i 3 (- i 1))) ((= i 0) x))")
fn test_eval_do_bare_var() {
interp.eval(r#"(do ((x)) ((eql x nil) "nil"))"#).unwrap(),
vec![Value::String("nil".to_string())]
fn test_eval_do_arity_error() {
assert!(interp.eval("(do)").is_err());
assert!(interp.eval("(do ())").is_err());
// DO*
fn test_eval_do_star_sequential_init() {
.eval("(do* ((a 1) (b (+ a 1))) (#t (+ a b)))")
fn test_eval_do_star_sequential_step() {
.eval("(do* ((i 0 (+ i 1)) (j 0 i)) ((= i 3) j))")
// Large-iteration DO loops — exceeds MAX_STATIC_LOOP_ITERS (64), must fall back to runtime path
fn test_eval_do_large_loop_falls_back_to_runtime() {
.eval("(do ((i 0 (+ i 1)) (sum 0 (+ sum i))) ((= i 100) sum))")
vec![Value::Number(Fraction::from_integer(4950))]
fn test_eval_do_runtime_bool_var_keeps_bool_fidelity() {
// A DO loop var initialised from a bool literal must be sized as
// `WasmType::Bool` (mirroring `compile_for_stack`), not I32 — else when the
// runtime-fallback path reads it back as the result, it serializes as
// Number(1) instead of Bool(true). The large loop forces the runtime path;
// `flag` is carried unchanged and returned at exit.
.eval("(do ((i 0 (+ i 1)) (flag #t)) ((= i 100) flag))")
vec![Value::Bool(true)]
fn test_eval_do_accumulator_car_with_user_fn_inside_native() {
// The accumulator pair-element inference may eval a cons car to learn its
// type, but ONLY when the car is pure-native (no user-callable anywhere).
// A user fn nested under a native head (`(+ 1 (dbl i))`) makes the car
// non-pure-native, so inference SKIPS the eval and lets the codegen path
// size the accumulator — it must still compile and run correctly. `dbl` is
// intentionally non-recursive (a recursive runtime-arg defun trips a
// separate, pre-existing inlining-overflow unrelated to this path).
.eval(
"(defun dbl (x) (* x 2)) \
(car (do ((i 0 (+ i 1)) (acc nil (cons (+ 1 (dbl i)) acc))) ((= i 2) acc)))"
)
// i goes 0,1; cons prepends so acc=((+1 (dbl 1)) (+1 (dbl 0)))=(3 1); car=3.
fn test_eval_do_accumulator_car_with_quoted_literal() {
// A side-effect-free quoted-literal car `(car '(5))` is pure (no
// user-callable), so the accumulator pair-element walker still eval-probes
// it for type inference — the purity gate admits `Quote`/`Keyword` as inert.
// The loop accumulates the constant 5; CAR of the result is 5.
"(car (do ((i 0 (+ i 1)) (acc nil (cons (car (quote (5))) acc))) \
((= i 2) acc)))"
vec![Value::Number(Fraction::from_integer(5))]
fn test_eval_do_star_large_loop_falls_back_to_runtime() {
// DO* steps sequentially: acc's step sees the already-incremented i, so sum = 1+2+...+100
.eval("(do* ((i 0 (+ i 1)) (acc 0 (+ acc i))) ((= i 100) acc))")
vec![Value::Number(Fraction::from_integer(5050))]