[fine] Multi-module compilation

It's a little bit complicated, loading a module is a two-step dance
but here's how it's done. Probably some surface-area refactoring needs
to happen so that we do the right thing.
This commit is contained in:
John Doty 2024-03-30 16:33:27 -07:00
parent ab477cd783
commit a3d4c24f11
8 changed files with 506 additions and 274 deletions

View file

@ -1,7 +1,10 @@
use fine::compiler::{compile, Function, Module};
use fine::compile_program;
use fine::compiler::{compile_module, CompiledModule, Function};
use fine::program::{
Module, ModuleLoadError, ModuleLoader, ModuleSource, Program, StandardModuleLoader,
};
use fine::semantics::{Error, Type};
use fine::vm::{eval_export_fn, Context};
use fine::{ModuleLoadError, ModuleLoader, ModuleSource, Runtime, StandardModuleLoader};
use fine::vm::{eval_export_fn, Context, VMError};
use pretty_assertions::assert_eq;
use std::fmt::Write as _;
@ -168,11 +171,11 @@ impl ModuleLoader for TestLoader {
}
}
fn test_runtime(_source_path: &str, source: Rc<str>) -> Runtime {
Runtime::new(TestLoader::new(_source_path.into(), source))
fn test_runtime(_source_path: &str, source: Rc<str>) -> Program {
Program::new(TestLoader::new(_source_path.into(), source))
}
fn assert_type_at(module: Rc<fine::Module>, pos: usize, expected: &str, _source_path: &str) {
fn assert_type_at(module: Rc<Module>, pos: usize, expected: &str, _source_path: &str) {
let semantics = module.semantics();
let tree = semantics.tree();
@ -197,7 +200,7 @@ fn assert_type_at(module: Rc<fine::Module>, pos: usize, expected: &str, _source_
}
fn assert_type_error_at(
module: Rc<fine::Module>,
module: Rc<Module>,
errors: &[Rc<Error>],
pos: usize,
expected: &str,
@ -256,7 +259,7 @@ fn dump_function(out: &mut String, function: &Function) -> std::fmt::Result {
Ok(())
}
fn dump_module(out: &mut String, module: &Module) -> std::fmt::Result {
fn dump_module(out: &mut String, module: &CompiledModule) -> std::fmt::Result {
for function in module.functions() {
dump_function(out, function)?;
}
@ -264,9 +267,9 @@ fn dump_module(out: &mut String, module: &Module) -> std::fmt::Result {
Ok(())
}
fn assert_compiles_to(module: Rc<fine::Module>, expected: &str, source_path: &str) {
fn assert_compiles_to(module: Rc<Module>, expected: &str, source_path: &str) {
let semantics = module.semantics();
let module = compile(&semantics);
let module = compile_module(&semantics);
let mut actual = String::new();
dump_module(&mut actual, &module).expect("no dumping?");
@ -286,7 +289,7 @@ fn assert_compiles_to(module: Rc<fine::Module>, expected: &str, source_path: &st
}
}
fn assert_no_errors(module: Rc<fine::Module>, errors: &[Rc<Error>]) {
fn assert_no_errors(module: Rc<Module>, errors: &[Rc<Error>]) {
let semantics = module.semantics();
let expected_errors: &[Rc<Error>] = &[];
@ -299,14 +302,39 @@ fn assert_no_errors(module: Rc<fine::Module>, errors: &[Rc<Error>]) {
);
}
fn assert_eval_ok(module: Rc<fine::Module>, expected: &str) {
fn dump_runtime_error(module: &Rc<Module>, context: &Context, e: VMError) -> ! {
let semantics = module.semantics();
let module = compile(&semantics);
semantics.dump_compiler_state(None);
let mut context = Context::new(module.clone());
context.init().expect("Unable to initialize module");
if let Some(module) = context.get_module(module.id()) {
let mut actual = String::new();
let _ = dump_module(&mut actual, &module);
match eval_export_fn(&mut context, "test", &[]) {
eprintln!("{actual}");
}
eprintln!("Backtrace:");
for frame in e.stack.iter() {
let func = frame.func();
eprint!(" {} (", func.name());
for arg in frame.args().iter() {
eprint!("{:?},", arg);
}
eprintln!(") @ {}", frame.pc());
}
eprintln!();
panic!("error occurred while running: {:?}", e.code);
}
fn assert_eval_ok(program: &Program, module: Rc<Module>, expected: &str) {
let semantics = module.semantics();
let mut context = Context::new();
if let Err(e) = compile_program(&program, &mut context) {
dump_runtime_error(&module, &context, e);
};
match eval_export_fn(&mut context, module.id(), "test", &[]) {
Ok(v) => {
let actual = format!("{:?}", v);
semantic_assert_eq!(
@ -317,31 +345,11 @@ fn assert_eval_ok(module: Rc<fine::Module>, expected: &str) {
"wrong return from test function"
);
}
Err(e) => {
semantics.dump_compiler_state(None);
let mut actual = String::new();
let _ = dump_module(&mut actual, &module);
eprintln!("{actual}");
eprintln!("Backtrace:");
for frame in e.stack.iter() {
let func = frame.func();
eprint!(" {} (", func.name());
for arg in frame.args().iter() {
eprint!("{:?},", arg);
}
eprintln!(") @ {}", frame.pc());
}
eprintln!();
panic!("error occurred while running: {:?}", e.code);
}
Err(e) => dump_runtime_error(&module, &context, e),
}
}
fn assert_errors(module: Rc<fine::Module>, errors: &[Rc<Error>], expected_errors: Vec<&str>) {
fn assert_errors(module: Rc<Module>, errors: &[Rc<Error>], expected_errors: Vec<&str>) {
let semantics = module.semantics();
let errors: Vec<String> = errors.iter().map(|e| format!("{}", e)).collect();
@ -355,7 +363,7 @@ fn assert_errors(module: Rc<fine::Module>, errors: &[Rc<Error>], expected_errors
);
}
fn assert_check_error(module: Rc<fine::Module>, errors: &[Rc<Error>], expected: &str) {
fn assert_check_error(module: Rc<Module>, errors: &[Rc<Error>], expected: &str) {
let semantics = module.semantics();
semantic_assert!(

View file

@ -1,9 +1,14 @@
import "./foo.fine" as foo;
// NOTE: This is right here because a known miscompilation will cause us to
// call this function instead of the actual target.
fun wrong_function() -> string {
"VERY WRONG"
}
fun test() -> string {
foo.hello() + " world"
}
// TODO: Obviously run the code duh
// @no-errors
/// @eval: asdf
// @eval: String("hello world")