use std::io::{self, Write};
use crate::compiler::Compiler;
use crate::compiler_test::test_constants;
use crate::vm::VM;
use object::Object;
use parser::parse;
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);
}
}