diff --git a/Cargo.lock b/Cargo.lock index 6722a0f..820fca2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,6 +244,14 @@ dependencies = [ name = "dvm-lib" version = "0.1.0" +[[package]] +name = "e2e-tests" +version = "0.1.0" +dependencies = [ + "dmc-lib", + "dvm-lib", +] + [[package]] name = "encoding_rs" version = "0.8.35" diff --git a/Cargo.toml b/Cargo.toml index 4a95eea..0a8f58b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,4 +25,4 @@ cst-test-generator = { path = "cst-test-generator" } [workspace] resolver = "3" -members = ["ast-generator", "cst-test-generator", "dm", "dm-std-lib", "dmc-lib", "dvm-lib"] +members = ["ast-generator", "cst-test-generator", "dm", "dm-std-lib", "dmc-lib", "dvm-lib", "e2e-tests"] diff --git a/dm/src/main.rs b/dm/src/main.rs index ec87e68..2bcb942 100644 --- a/dm/src/main.rs +++ b/dm/src/main.rs @@ -105,7 +105,7 @@ fn main() { &mut registers, &mut call_stack, "main", - vec![], + &vec![], ); if let Some(value) = result { println!("{}", value); diff --git a/dvm-lib/src/vm/mod.rs b/dvm-lib/src/vm/mod.rs index 0677686..0091539 100644 --- a/dvm-lib/src/vm/mod.rs +++ b/dvm-lib/src/vm/mod.rs @@ -218,7 +218,7 @@ pub fn call<'a>( registers: &mut Vec, call_stack: &mut CallStack<'a>, function_name: &str, - arguments: Vec, + arguments: &[Value], ) -> Option { // check if DVM_DEBUG is enabled let debug = std::env::var("DVM_DEBUG") @@ -234,10 +234,13 @@ pub fn call<'a>( call_stack.push(CallFrame::new(function)); // put each arg on the stack - for argument in &arguments { + for argument in arguments { call_stack.top_mut().stack_mut().push(argument.clone()); } + // set fp for this function + call_stack.top_mut().set_fp(arguments.len()); + // ensure enough stack space call_stack.top_mut().stack_mut().resize( arguments.len() + (function.stack_size() as usize), @@ -314,6 +317,12 @@ pub fn call<'a>( let mut args = call_stack.top().stack()[call_stack.top().stack().len() - arg_count..].to_vec(); + // save registers + for register in registers.iter_mut() { + let value = std::mem::take(register); + call_stack.top_mut().stack_mut().push(value); + } + // increment caller frame ip call_stack.top_mut().increment_ip(); @@ -482,9 +491,17 @@ pub fn call<'a>( let mut callee = call_stack.pop(); // pop this frame let maybe_caller = call_stack.maybe_top_mut(); if let Some(caller) = maybe_caller { + // restore registers + for i in (0..registers.len()).rev() { + registers[i] = caller.stack_mut().pop().unwrap(); + } + + // pop args for _ in 0..callee.function().parameter_count() { caller.stack_mut().pop(); } + + // push return value if some if let Some(return_value) = callee.take_return_value() { // n.b. callee caller.stack_mut().push(return_value); diff --git a/dvm-lib/src/vm/value.rs b/dvm-lib/src/vm/value.rs index 8d8c83c..60f0281 100644 --- a/dvm-lib/src/vm/value.rs +++ b/dvm-lib/src/vm/value.rs @@ -1,7 +1,7 @@ use std::fmt::{Display, Formatter}; use std::rc::Rc; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Value { Int(i32), String(Rc), diff --git a/e2e-tests/Cargo.toml b/e2e-tests/Cargo.toml new file mode 100644 index 0000000..679fef5 --- /dev/null +++ b/e2e-tests/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "e2e-tests" +version = "0.1.0" +edition = "2024" + +[dependencies] +dmc-lib = { path = "../dmc-lib" } +dvm-lib = { path = "../dvm-lib" } \ No newline at end of file diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs new file mode 100644 index 0000000..de304db --- /dev/null +++ b/e2e-tests/src/lib.rs @@ -0,0 +1,128 @@ +#[cfg(test)] +mod e2e_tests { + use dmc_lib::constants_table::ConstantsTable; + use dmc_lib::diagnostic::Diagnostic; + use dmc_lib::parser::parse_compilation_unit; + use dmc_lib::symbol_table::SymbolTable; + use dvm_lib::vm::constant::{Constant, StringConstant}; + use dvm_lib::vm::function::Function; + use dvm_lib::vm::value::Value; + use dvm_lib::vm::{CallStack, DvmContext, call}; + use std::rc::Rc; + + const REGISTER_COUNT: usize = 8; + + fn report_diagnostics(diagnostics: &[Diagnostic]) -> ! { + eprintln!( + "{}", + diagnostics + .iter() + .map(|d| format!("{:?}", d)) + .collect::>() + .join("\n") + ); + panic!("There were diagnostics."); + } + + fn assert_result(input: &str, function_name: &str, arguments: &[Value], expected_value: Value) { + let parse_result = parse_compilation_unit(input); + let mut compilation_unit = match parse_result { + Ok(compilation_unit) => compilation_unit, + Err(diagnostics) => { + report_diagnostics(&diagnostics); + } + }; + + let mut symbol_table = SymbolTable::new(); + + let gather_names_diagnostics = compilation_unit.gather_declared_names(&mut symbol_table); + if !gather_names_diagnostics.is_empty() { + report_diagnostics(&gather_names_diagnostics); + } + + let name_usages_diagnostics = compilation_unit.check_name_usages(&symbol_table); + if !name_usages_diagnostics.is_empty() { + report_diagnostics(&name_usages_diagnostics); + } + + let type_check_diagnostics = compilation_unit.type_check(&symbol_table); + if !type_check_diagnostics.is_empty() { + report_diagnostics(&type_check_diagnostics); + } + + let mut ir_functions = compilation_unit.to_ir(&symbol_table); + + let mut functions: Vec = vec![]; + let mut constants_table = ConstantsTable::new(); + + for ir_function in &mut ir_functions { + let (_, stack_size) = ir_function.assign_registers(REGISTER_COUNT); + let function = ir_function.assemble(stack_size, &mut constants_table); + functions.push(function); + } + + let mut dvm_context = DvmContext::new(); + + for function in functions { + dvm_context + .functions_mut() + .insert(function.name_owned(), function); + } + + for (name, content) in &constants_table.string_constants() { + dvm_context.constants_mut().insert( + name.clone(), + Constant::String(StringConstant::new(name.clone(), content.clone())), + ); + } + + let mut registers: Vec = vec![Value::Null; REGISTER_COUNT]; + let mut call_stack = CallStack::new(); + + let result = call( + &dvm_context, + &mut registers, + &mut call_stack, + function_name, + &arguments, + ); + match result { + None => panic!("Call returned no value"), + Some(result_value) => { + assert_eq!(result_value, expected_value); + } + } + } + + #[test] + fn add_1_2() { + assert_result( + " + fn add(a: Int, b: Int) -> Int + a + b + end + ", + "add", + &vec![Value::Int(1), Value::Int(2)], + Value::Int(3), + ); + } + + #[test] + fn bunch_of_adding() { + assert_result( + " + fn add(a: Int, b: Int) -> Int + a + b + end + + fn greetAndAdd(a: Int, b: Int) -> String + \"Hello. \" + a + \" plus \" + b + \" is \" + add(a, b) + end + ", + "greetAndAdd", + &vec![Value::Int(1), Value::Int(2)], + Value::String(Rc::from("Hello. 1 plus 2 is 3")), + ); + } +} diff --git a/examples/add_2.dm b/examples/add_2.dm new file mode 100644 index 0000000..2dc8b4b --- /dev/null +++ b/examples/add_2.dm @@ -0,0 +1,13 @@ +extern fn println(message: Any) -> Void + +fn add(a: Int, b: Int) -> Int + a + b +end + +fn main() -> String + let a = 9 + let b = 7 + let c = add(a, b) + let msg = "Hello. " + a + " plus " + b + " is " + c + msg +end