scheme4r 0.2.3

Scheme interpreter for rust
Documentation
use scheme4r::{eval, Value};

#[test]
fn and_and_or_short_circuit_correctly() {
    let value = eval("(and #t 1 2 3)").unwrap();
    assert!(matches!(value, Value::Number(3)));

    let value = eval("(or #f #f 9)").unwrap();
    assert!(matches!(value, Value::Number(9)));

    let value = eval(
        "\
        (define touched 0)
        (or #t (set! touched 1))
        touched
        ",
    )
    .unwrap();
    assert!(matches!(value, Value::Number(0)));
}

#[test]
fn let_variants_work_for_parallel_sequential_and_recursive_bindings() {
    let value = eval("(let ((x 2) (y 3)) (+ x y))").unwrap();
    assert!(matches!(value, Value::Number(5)));

    let value = eval("(let* ((x 2) (y (+ x 3))) (* x y))").unwrap();
    assert!(matches!(value, Value::Number(10)));

    let value = eval(
        "\
        (letrec ((even?
                  (lambda (n)
                    (if (= n 0) #t (odd? (- n 1)))))
                 (odd?
                  (lambda (n)
                    (if (= n 0) #f (even? (- n 1))))))
          (even? 8))
        ",
    )
    .unwrap();
    assert!(matches!(value, Value::Boolean(true)));
}

#[test]
fn named_let_supports_iteration() {
    let value = eval(
        "\
        (let loop ((n 5) (acc 1))
          (if (= n 0)
              acc
              (loop (- n 1) (* acc n))))
        ",
    )
    .unwrap();

    assert!(matches!(value, Value::Number(120)));
}

#[test]
fn cond_supports_plain_else_and_arrow_clauses() {
    let value = eval(
        "\
        (cond
          ((> 1 2) 0)
          ((+ 1 2) => (lambda (x) (+ x 10)))
          (else 99))
        ",
    )
    .unwrap();
    assert!(matches!(value, Value::Number(13)));

    let value = eval(
        "\
        (cond
          ((> 1 2) 0)
          (else 99))
        ",
    )
    .unwrap();
    assert!(matches!(value, Value::Number(99)));
}

#[test]
fn case_and_do_forms_work() {
    let value = eval(
        "\
        (case (* 2 3)
          ((1 2 3) 'small)
          ((6 7) => (lambda (x) (+ x 10)))
          (else 'other))
        ",
    )
    .unwrap();
    assert!(matches!(value, Value::Number(16)));

    let value = eval(
        "\
        (do ((i 0 (+ i 1))
             (acc 0 (+ acc i)))
            ((= i 5) acc))
        ",
    )
    .unwrap();
    assert!(matches!(value, Value::Number(10)));
}

#[test]
fn internal_definitions_work_inside_bodies() {
    let value = eval(
        "\
        ((lambda ()
           (define even?
             (lambda (n)
               (if (= n 0) #t (odd? (- n 1)))))
           (define odd?
             (lambda (n)
               (if (= n 0) #f (even? (- n 1)))))
           (even? 8)))
        ",
    )
    .unwrap();
    assert!(matches!(value, Value::Boolean(true)));

    let value = eval(
        "\
        (let ()
          (define x 10)
          (define y (+ x 5))
          (+ x y))
        ",
    )
    .unwrap();
    assert!(matches!(value, Value::Number(25)));
}

#[test]
fn list_library_functions_work() {
    let value = eval("(length '(1 2 3 4))").unwrap();
    assert!(matches!(value, Value::Number(4)));

    let value = eval("(append '(1 2) '(3 4))").unwrap();
    assert_eq!(format!("{value}"), "(1 2 3 4)");

    let value = eval("(reverse '(1 2 3))").unwrap();
    assert_eq!(format!("{value}"), "(3 2 1)");

    let value = eval("(list-ref '(10 20 30) 1)").unwrap();
    assert!(matches!(value, Value::Number(20)));

    let value = eval("(list-tail '(10 20 30) 2)").unwrap();
    assert_eq!(format!("{value}"), "(30)");
}

#[test]
fn apply_and_string_conversions_work() {
    let value = eval("(apply + '(1 2 3 4))").unwrap();
    assert!(matches!(value, Value::Number(10)));

    let value = eval("(apply list 1 2 '(3 4))").unwrap();
    assert_eq!(format!("{value}"), "(1 2 3 4)");

    let value = eval("(string-append \"hel\" \"lo\")").unwrap();
    assert_eq!(format!("{value}"), "\"hello\"");

    let value = eval("(string-length \"hello\")").unwrap();
    assert!(matches!(value, Value::Number(5)));

    let value = eval("(string=? (symbol->string 'abc) \"abc\")").unwrap();
    assert!(matches!(value, Value::Boolean(true)));

    let value = eval("(string->symbol \"xyz\")").unwrap();
    assert_eq!(format!("{value}"), "xyz");

    let value = eval("(string->number \"42\")").unwrap();
    assert!(matches!(value, Value::Number(42)));
}