use crate::compiler::Compiler;
use crate::compiler_test::test_constants;
use crate::vm::VM;
use object::Object;
use parser::parse;
use std::io::{self, Write};
pub struct VmTestCase<'a> {
pub(crate) input: &'a str,
pub(crate) expected: Object,
}
pub fn run_vm_tests(tests: Vec<VmTestCase>) {
for t in tests {
let program = parse(t.input).unwrap();
let mut compiler = Compiler::new();
let bytecodes = compiler.compile(&program).unwrap();
let s = bytecodes.instructions.string();
println!("ins {} for input {}", bytecodes.instructions.string(), t.input);
let mut vm = VM::new(bytecodes);
vm.run();
let got = vm.last_popped_stack_elm().unwrap();
let expected_argument = t.expected;
test_constants(&vec![expected_argument], &vec![got]);
}
}
#[cfg(test)]
mod tests {
use object::Object;
use std::collections::HashMap;
use std::rc::Rc;
use crate::vm_test::{run_vm_tests, VmTestCase};
#[test]
fn test_integer_arithmetic() {
let tests: Vec<VmTestCase> = vec![
VmTestCase {
input: "1",
expected: Object::Integer(1),
},
VmTestCase {
input: "2",
expected: Object::Integer(2),
},
VmTestCase {
input: "1 + 2",
expected: Object::Integer(3),
},
VmTestCase {
input: "4 / 2",
expected: Object::Integer(2),
},
VmTestCase {
input: "50 / 2 * 2 + 10 - 5",
expected: Object::Integer(55),
},
VmTestCase {
input: "5 * (2 + 10)",
expected: Object::Integer(60),
},
VmTestCase {
input: "5 + 5 + 5 + 5 - 10",
expected: Object::Integer(10),
},
VmTestCase {
input: "2 * 2 * 2 * 2 * 2",
expected: Object::Integer(32),
},
VmTestCase {
input: "5 * 2 + 10",
expected: Object::Integer(20),
},
VmTestCase {
input: "5 + 2 * 10",
expected: Object::Integer(25),
},
VmTestCase {
input: "5 * (2 + 10)",
expected: Object::Integer(60),
},
VmTestCase {
input: "-5",
expected: Object::Integer(-5),
},
VmTestCase {
input: "-10",
expected: Object::Integer(-10),
},
VmTestCase {
input: "-50 + 100 + -50",
expected: Object::Integer(0),
},
VmTestCase {
input: "(5 + 10 * 2 + 15 / 3) * 2 + -10",
expected: Object::Integer(50),
},
];
run_vm_tests(tests);
}
#[test]
fn test_boolean_expressions() {
let tests: Vec<VmTestCase> = vec![
VmTestCase {
input: "true",
expected: Object::Boolean(true),
},
VmTestCase {
input: "false",
expected: Object::Boolean(false),
},
VmTestCase {
input: "true",
expected: Object::Boolean(true),
},
VmTestCase {
input: "false",
expected: Object::Boolean(false),
},
VmTestCase {
input: "1 < 2",
expected: Object::Boolean(true),
},
VmTestCase {
input: "1 > 2",
expected: Object::Boolean(false),
},
VmTestCase {
input: "1 < 1",
expected: Object::Boolean(false),
},
VmTestCase {
input: "1 > 1",
expected: Object::Boolean(false),
},
VmTestCase {
input: "1 == 1",
expected: Object::Boolean(true),
},
VmTestCase {
input: "1 != 1",
expected: Object::Boolean(false),
},
VmTestCase {
input: "1 == 2",
expected: Object::Boolean(false),
},
VmTestCase {
input: "1 != 2",
expected: Object::Boolean(true),
},
VmTestCase {
input: "true == true",
expected: Object::Boolean(true),
},
VmTestCase {
input: "false == false",
expected: Object::Boolean(true),
},
VmTestCase {
input: "true == false",
expected: Object::Boolean(false),
},
VmTestCase {
input: "true != false",
expected: Object::Boolean(true),
},
VmTestCase {
input: "false != true",
expected: Object::Boolean(true),
},
VmTestCase {
input: "(1 < 2) == true",
expected: Object::Boolean(true),
},
VmTestCase {
input: "(1 < 2) == false",
expected: Object::Boolean(false),
},
VmTestCase {
input: "(1 > 2) == true",
expected: Object::Boolean(false),
},
VmTestCase {
input: "(1 > 2) == false",
expected: Object::Boolean(true),
},
VmTestCase {
input: "!true",
expected: Object::Boolean(false),
},
VmTestCase {
input: "!false",
expected: Object::Boolean(true),
},
VmTestCase {
input: "!5",
expected: Object::Boolean(false),
},
VmTestCase {
input: "!!true",
expected: Object::Boolean(true),
},
VmTestCase {
input: "!!false",
expected: Object::Boolean(false),
},
VmTestCase {
input: "!!5",
expected: Object::Boolean(true),
},
];
run_vm_tests(tests);
}
#[test]
fn test_conditionals() {
let tests = vec![
VmTestCase {
input: "if (true) { 10 }",
expected: Object::Integer(10),
},
VmTestCase {
input: "if (true) { 10 } else { 20 }",
expected: Object::Integer(10),
},
VmTestCase {
input: "if (false) { 10 } else { 20 }",
expected: Object::Integer(20),
},
VmTestCase {
input: "if (1) { 10 }",
expected: Object::Integer(10),
},
VmTestCase {
input: "if (1 < 2) { 10 }",
expected: Object::Integer(10),
},
VmTestCase {
input: "if (1 < 2) { 10 } else { 20 }",
expected: Object::Integer(10),
},
VmTestCase {
input: "if (1 > 2) { 10 } else { 20 }",
expected: Object::Integer(20),
},
VmTestCase {
input: "if (1 > 2) { 10 }",
expected: Object::Null,
},
VmTestCase {
input: "if (false) { 10 }",
expected: Object::Null,
},
VmTestCase {
input: "if ((if (false) { 10 })) { 10 } else { 20 }",
expected: Object::Integer(20),
},
];
run_vm_tests(tests);
}
#[test]
fn test_global_let_statements() {
let tests = vec![
VmTestCase {
input: "let one = 1; one",
expected: Object::Integer(1),
},
VmTestCase {
input: "let one = 1; let two = 2; one + two",
expected: Object::Integer(3),
},
VmTestCase {
input: "let one = 1; let two = one + one; one + two",
expected: Object::Integer(3),
},
];
run_vm_tests(tests);
}
#[test]
fn test_strings() {
let tests = vec![
VmTestCase {
input: "\"monkey\"",
expected: Object::String("monkey".to_string()),
},
VmTestCase {
input: "\"mon\" + \"key\"",
expected: Object::String("monkey".to_string()),
},
VmTestCase {
input: "\"mon\" + \"key\" + \"banana\"",
expected: Object::String("monkeybanana".to_string()),
},
];
run_vm_tests(tests);
}
#[test]
fn test_arrays() {
fn map_vec_to_object(vec: Vec<i64>) -> Object {
let array = vec
.iter()
.map(|i| Rc::new(Object::Integer(*i)))
.collect::<Vec<Rc<Object>>>();
return Object::Array(array);
}
let tests = vec![
VmTestCase {
input: "[]",
expected: map_vec_to_object(vec![]),
},
VmTestCase {
input: "[1, 2, 3]",
expected: map_vec_to_object(vec![1, 2, 3]),
},
VmTestCase {
input: "[1 + 2, 3 * 4, 5 + 6]",
expected: map_vec_to_object(vec![3, 12, 11]),
},
];
run_vm_tests(tests);
}
#[test]
fn test_hash() {
fn map_vec_to_object(vec: Vec<(i64, i64)>) -> Object {
let hash = vec.iter().fold(HashMap::new(), |mut acc, (k, v)| {
acc.insert(Rc::new(Object::Integer(*k)), Rc::new(Object::Integer(*v)));
acc
});
return Object::Hash(hash);
}
let tests = vec![
VmTestCase {
input: "{}",
expected: Object::Hash(HashMap::new()),
},
VmTestCase {
input: "{1: 2, 2: 3}",
expected: map_vec_to_object(vec![(1, 2), (2, 3)]),
},
VmTestCase {
input: "{1 + 1: 2 * 2, 3 + 3: 4 * 4}",
expected: map_vec_to_object(vec![(2, 4), (6, 16)]),
},
];
run_vm_tests(tests);
}
#[test]
fn test_index() {
let tests = vec![
VmTestCase {
input: "[1, 2, 3][1]",
expected: Object::Integer(2),
},
VmTestCase {
input: "[1, 2, 3][0 + 2]",
expected: Object::Integer(3),
},
VmTestCase {
input: "[1, 2, 3][0]",
expected: Object::Integer(1),
},
VmTestCase {
input: "[[1, 1, 1]][0][0]",
expected: Object::Integer(1),
},
VmTestCase {
input: "[][0]",
expected: Object::Null,
},
VmTestCase {
input: "[1, 2, 3][99]",
expected: Object::Null,
},
VmTestCase {
input: "[1][-1]",
expected: Object::Null,
},
VmTestCase {
input: "{1: 1, 2: 2}[1]",
expected: Object::Integer(1),
},
VmTestCase {
input: "{1: 1, 2: 2}[2]",
expected: Object::Integer(2),
},
VmTestCase {
input: "{1: 1}[0]",
expected: Object::Null,
},
VmTestCase {
input: "{}[0]",
expected: Object::Null,
},
];
run_vm_tests(tests);
}
}