oris 0.2.1

An interpreter for Monkey
Documentation
use super::*;

fn test_env() -> Env {
    let mut global = env::Storage::default();

    for (k, v) in value::builtin::all_() {
        global.insert(std::rc::Rc::from(k), (None, Value::Builtin(v)));
    }

    Env::new(global)
}

macro_rules! t {
    ($code:literal, $result:literal) => {
        let mut env = test_env();

        match entry(&mut env, $code.as_bytes()) {
            Ok(Value::Int(result)) => {
                assert_eq!(result, $result);
            }
            Ok(value) => {
                panic!("expect int {}, found {:?}", $result, value);
            }
            Err(err) => {
                panic!("eval failed: {:?}", err);
            }
        }
    };

    ($code:literal, [$($elem:literal),*]) => {
        let mut env = test_env();

        match entry(&mut env, $code.as_bytes()) {
            Ok(Value::Seq(result)) => {
                let result = result.iter().map(|v| match v {
                    Value::Int(v) => Ok(*v),
                    _ => Err(v),
                }).collect::<std::result::Result<Vec<_>,_>>();

                match result {
                    Ok(seq) => assert_eq!(seq, vec![$($elem),*]),
                    Err(value) => {
                        panic!("expect seq of int, found one is not int: {:?}", value);
                    }
                }
            }
            Ok(value) => {
                panic!("expect seq of int, found {:?}", value);
            }
            Err(err) => {
                panic!("eval failed: {:?}", err);
            }
        }
    };

    (error: $code:literal) => {
        let mut env = test_env();

        match entry(&mut env, $code.as_bytes()) {
            result @ Ok(_) | result @ Err(Error::Parse(_)) => panic!("{:?}", result),
            _ => {}
        }
    };

    (unit: $code:literal) => {
        let mut env = test_env();

        match entry(&mut env, $code.as_bytes()) {
            Ok(Value::Unit) => {},
            other => panic!("{:?}", other),
        }
    };

    (str: $code:literal, $result:literal) => {
        let mut env = test_env();

        match entry(&mut env, $code.as_bytes()) {
            Ok(Value::Str(s)) => assert_eq!(&*s, $result),
            Ok(v) => panic!("expect str, found {:?}", v),
            Err(err) => panic!("eval failed: {:?}", err),
        }
    };
}

#[test]
fn arithmetic() {
    t!("1 + 2", 3);

    t!("1 - 2", -1);

    t!("2 * 3", 6);
    t!("2 * 0", 0);

    t!("2 / 3", 0);
    t!("3 / 3", 1);

    t!("1 + 2 * 3", 7);
    t!("1 + (2 * 3)", 7);
    t!("(1 + 2) * 3", 9);
    t!("(2 + 3) / 2", 2);
}

#[test]
fn if_() {
    t!("if 1 < 2 { 3 } else { 4 }", 3);
    t!("if 1 > 2 { 3 } else { 4 }", 4);

    t!("if 1 != 2 { 3 } else { 4 }", 3);
    t!("if 1 == 2 { 3 } else { 4 }", 4);

    t!("if 1 <= 2 { 3 } else { 4 }", 3);
    t!("if 1 >= 2 { 3 } else { 4 }", 4);
}

#[test]
fn closure() {
    t!("let f = fn(x) { x + 1 }; f(1)", 2);
    t!("let f = fn(x) { if x < 0 { 0 } else { x } }; f(1)", 1);
    t!("let f = fn(x) { if x < 0 { 0 } else { x } }; f(-1)", 0);
    t!(
        "
let adder = fn(x) {
    fn(y) { x + y }
};

adder(1)(2)",
        3
    );
    t!(
        "
let filter = fn(seq, predicate) {
    let iter = fn(from, to) {
        if len(from) == 0 {
            to
        } else {
            iter(tail(from), if predicate(head(from)) {
                append(to, head(from))
            } else {
                to
            })
        }
    };

    iter(seq, [])
};

filter([1, 2, 3, 4], fn(x) { x / 2 * 2 == x })
",
        [2, 4]
    );
}

#[test]
fn closure_early_return() {
    t!("let f = fn(x) { if x < 0 { return 0; } x }; f(-1)", 0);
    t!("let f = fn(x) { if x < 0 { return 0; } x }; f(1)", 1);
}

#[test]
fn seq() {
    t!("[1, 2, 3]", [1, 2, 3]);
    t!("[1] + [2, 3]", [1, 2, 3]);
    t!("[1, 2, 3][2]", 3);
}

#[test]
fn map() {
    t!(r#"{2: "two", "three": 3, false: 4}["three"]"#, 3);
    t!(r#"{2: "two", "three": 3, false: 4}[false]"#, 4);
}

#[test]
fn builtin_len() {
    t!("len([])", 0);
    t!("len([1])", 1);
    t!("len([1, 2])", 2);

    t!("len(\"\")", 0);
    t!("len(\"a\")", 1);
    t!("len(\"ab\")", 2);
    t!("len(\"a b\")", 3);

    t!("len({})", 0);
    t!("len({1: 2})", 1);
    t!("len({3: true, false: 4})", 2);

    t!(error: "len(1)");
    t!(error: "len(true)");
    t!(error: "len(len)");
    t!(error: "len(a)");
    t!(error: "len(print())");
    t!(error: "len(fn(){})");
}

#[test]
fn builtin_head() {
    t!(error: "head([])");
    t!("head([1])", 1);
    t!("head([2, 3])", 2);

    t!(error: "head({})");
    t!(error: "head(1)");
    t!(error: "head(false)");
    t!(error: "head(\"\")");
}

#[test]
fn builtin_tail() {
    t!(error: "tail([])");
    t!("tail([1])", []);
    t!("tail([1, 2])", [2]);
    t!("tail([1, 2, 3])", [2, 3]);

    t!(error: "tail({})");
    t!(error: "tail(1)");
    t!(error: "tail(false)");
    t!(error: "tail(\"\")");
}

#[test]
fn builtin_append() {
    t!(error: "append([])");
    t!("append([], 1)", [1]);
    t!("append([1], 2)", [1, 2]);
    t!("append([1], 2, 3)", [1, 2, 3]);

    t!(error: "append({})");
    t!(error: "append(1)");
    t!(error: "append(false)");
    t!(error: "append(\"\")");
}

#[test]
fn builtin_assert_eq() {
    t!(unit: "assert_eq(1, 1)");
    t!(error: "assert_eq(1, 2)");
}

#[test]
fn builtin_type() {
    t!(str: "type(print())", "unit");
    t!(str: "type(1)", "int");
    t!(str: "type(true)", "bool");
    t!(str: "type(false)", "bool");
    t!(str: "type(\"\")", "str");
    t!(str: "type([])", "seq");
    t!(str: "type([1])", "seq");
    t!(str: "type({})", "map");
    t!(str: "type({true: 1})", "map");
    t!(str: "type(type)", "builtin");
    t!(str: "type(fn(){})", "closure");
}