Add e2e-tests project and add register saving around calls.

This commit is contained in:
Jesse Brault 2026-03-08 16:23:20 -05:00
parent a7d162b2ca
commit ec848b3d36
8 changed files with 179 additions and 5 deletions

8
Cargo.lock generated
View File

@ -244,6 +244,14 @@ dependencies = [
name = "dvm-lib" name = "dvm-lib"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "e2e-tests"
version = "0.1.0"
dependencies = [
"dmc-lib",
"dvm-lib",
]
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.35" version = "0.8.35"

View File

@ -25,4 +25,4 @@ cst-test-generator = { path = "cst-test-generator" }
[workspace] [workspace]
resolver = "3" 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"]

View File

@ -105,7 +105,7 @@ fn main() {
&mut registers, &mut registers,
&mut call_stack, &mut call_stack,
"main", "main",
vec![], &vec![],
); );
if let Some(value) = result { if let Some(value) = result {
println!("{}", value); println!("{}", value);

View File

@ -218,7 +218,7 @@ pub fn call<'a>(
registers: &mut Vec<Value>, registers: &mut Vec<Value>,
call_stack: &mut CallStack<'a>, call_stack: &mut CallStack<'a>,
function_name: &str, function_name: &str,
arguments: Vec<Value>, arguments: &[Value],
) -> Option<Value> { ) -> Option<Value> {
// check if DVM_DEBUG is enabled // check if DVM_DEBUG is enabled
let debug = std::env::var("DVM_DEBUG") let debug = std::env::var("DVM_DEBUG")
@ -234,10 +234,13 @@ pub fn call<'a>(
call_stack.push(CallFrame::new(function)); call_stack.push(CallFrame::new(function));
// put each arg on the stack // put each arg on the stack
for argument in &arguments { for argument in arguments {
call_stack.top_mut().stack_mut().push(argument.clone()); 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 // ensure enough stack space
call_stack.top_mut().stack_mut().resize( call_stack.top_mut().stack_mut().resize(
arguments.len() + (function.stack_size() as usize), arguments.len() + (function.stack_size() as usize),
@ -314,6 +317,12 @@ pub fn call<'a>(
let mut args = let mut args =
call_stack.top().stack()[call_stack.top().stack().len() - arg_count..].to_vec(); 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 // increment caller frame ip
call_stack.top_mut().increment_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 mut callee = call_stack.pop(); // pop this frame
let maybe_caller = call_stack.maybe_top_mut(); let maybe_caller = call_stack.maybe_top_mut();
if let Some(caller) = maybe_caller { 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() { for _ in 0..callee.function().parameter_count() {
caller.stack_mut().pop(); caller.stack_mut().pop();
} }
// push return value if some
if let Some(return_value) = callee.take_return_value() { if let Some(return_value) = callee.take_return_value() {
// n.b. callee // n.b. callee
caller.stack_mut().push(return_value); caller.stack_mut().push(return_value);

View File

@ -1,7 +1,7 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::rc::Rc; use std::rc::Rc;
#[derive(Clone, Debug)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum Value { pub enum Value {
Int(i32), Int(i32),
String(Rc<str>), String(Rc<str>),

8
e2e-tests/Cargo.toml Normal file
View File

@ -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" }

128
e2e-tests/src/lib.rs Normal file
View File

@ -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::<Vec<_>>()
.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<Function> = 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<Value> = 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")),
);
}
}

13
examples/add_2.dm Normal file
View File

@ -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