Compare commits

..

2 commits

Author SHA1 Message Date
a3d4c24f11 [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.
2024-03-30 16:33:27 -07:00
ab477cd783 [fine] Simplify function compilation
This is simpler because we don't "discover" functions to compile as we
go, we just compile all the ones we can find, and functions have
pre-defined exports. This is good and useful to us as we can now refer
to functions in different modules by known indices, but it *does* make
me wonder what we're going to do for compiling generic specializations.
The previous technique was better for that sort of thing.

This is all predicated on the idea that I want to have
partially-compiled modules, which I can't really say why I want it. If
I'm happy to just compile things cross module in the same kind of
space then it's much easier to go back to the function key way of
working.
2024-03-29 19:12:18 -07:00
17 changed files with 692 additions and 484 deletions

6
fine/TODO Normal file
View file

@ -0,0 +1,6 @@
- The Export enum is stupid I think, for runtime modules. Why do we even have them? We should just put all the names in, like Declaration {} but for runtime.
- Module IDs must be globally resolvable from within a given semantics object
- When adding PANIC instructions, push a diagnostic that I find if I can find one instead of a hard-coded string.
- runtime should have `new` with 0 args and `with_loader<T : ModuleLoader>` that does the boxing, and `new` should just make the standard one

View file

@ -107,7 +107,7 @@ fn generate_test_for_file(path: PathBuf) -> String {
} else if let Some(line) = line.strip_prefix("@eval:") { } else if let Some(line) = line.strip_prefix("@eval:") {
let expected = line.trim(); let expected = line.trim();
assertions.push(quote! { assertions.push(quote! {
crate::assert_eval_ok(_module.clone(), #expected); crate::assert_eval_ok(&program, _module.clone(), #expected);
}); });
} else if let Some(line) = line.strip_prefix("@check-error:") { } else if let Some(line) = line.strip_prefix("@check-error:") {
let expected = line.trim(); let expected = line.trim();
@ -139,8 +139,8 @@ fn generate_test_for_file(path: PathBuf) -> String {
#disabled #disabled
fn #name() { fn #name() {
let source : std::rc::Rc<str> = #contents.into(); let source : std::rc::Rc<str> = #contents.into();
let mut runtime = crate::test_runtime(#display_path, source.clone()); let mut program = crate::test_runtime(#display_path, source.clone());
let (_errors, _module) = runtime.load_module("__test__").unwrap(); let (_errors, _module) = program.load_module("__test__").unwrap();
#(#assertions)* #(#assertions)*
} }

View file

@ -3,7 +3,9 @@ use std::rc::Rc;
use crate::{ use crate::{
parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef}, parser::{Child, SyntaxTree, Tree, TreeKind, TreeRef},
semantics::{string_constant_to_string, Declaration, Location, Origin, Semantics, Type}, semantics::{
string_constant_to_string, Declaration, Location, ModuleId, Origin, Semantics, Type,
},
tokens::TokenKind, tokens::TokenKind,
}; };
@ -62,6 +64,8 @@ pub enum Instruction {
StoreSlot(usize), StoreSlot(usize),
StringAdd, StringAdd,
NewList(usize), NewList(usize),
ModulePrefix(ModuleId),
} }
pub enum Export { pub enum Export {
@ -69,23 +73,31 @@ pub enum Export {
Global(usize), Global(usize),
} }
pub struct Module { pub struct CompiledModule {
pub id: ModuleId,
pub functions: Vec<Rc<Function>>, // Functions pub functions: Vec<Rc<Function>>, // Functions
pub globals: usize, // The number of global variables pub globals: usize, // The number of global variables
pub exports: HashMap<String, Export>, // Exports by name pub exports: HashMap<String, Export>, // Exports by name
pub init: usize, // The index of the initialization function pub init: usize, // The index of the initialization function
pub deps: Vec<ModuleId>, // Modules I depend on
} }
impl Module { impl CompiledModule {
pub fn new() -> Self { pub fn new(id: ModuleId) -> Self {
Module { CompiledModule {
id,
functions: Vec::new(), functions: Vec::new(),
globals: 0, globals: 0,
exports: HashMap::new(), exports: HashMap::new(),
init: 0, init: 0,
deps: Vec::new(),
} }
} }
pub fn init_function(&self) -> &Rc<Function> {
&self.functions[self.init]
}
pub fn functions(&self) -> &[Rc<Function>] { pub fn functions(&self) -> &[Rc<Function>] {
&self.functions &self.functions
} }
@ -142,21 +154,12 @@ impl std::fmt::Debug for Function {
} }
} }
#[derive(Eq, PartialEq, Hash, Clone)]
struct FunctionKey {
tree: TreeRef,
}
struct Compiler<'a> { struct Compiler<'a> {
source: &'a str, source: &'a str,
semantics: &'a Semantics, semantics: &'a Semantics,
syntax: &'a SyntaxTree, syntax: &'a SyntaxTree,
function_bindings: HashMap<FunctionKey, usize>, module: CompiledModule,
pending_functions: Vec<(FunctionKey, usize, Function)>,
temp_functions: Vec<Option<Rc<Function>>>,
module: Module,
function: Function, function: Function,
} }
@ -249,18 +252,35 @@ macro_rules! ice {
}} }}
} }
// macro_rules! inst_panic { type CR = Result<(), &'static str>;
// ($($t:tt)+) => {{ const OK: CR = CR::Ok(());
// // eprintln!($($t)*);
// Instruction::Panic
// }};
// }
// macro_rules! ice { fn function_from_function_decl(
// ($compiler:expr, $tr:expr, $($t:tt)*) => {{}}; source: &str,
// } syntax: &SyntaxTree,
tree: &Tree,
) -> Result<Function, &'static str> {
// TODO: If this is a method the name should be different.
let name = tree.nth_token(1).ok_or("no id")?.as_str(source);
pub fn compile(semantics: &Semantics) -> Rc<Module> { let param_list = tree
.child_tree_of_kind(syntax, TreeKind::ParamList)
.ok_or("no paramlist")?;
let param_count = param_list.children.len() - 2;
Ok(Function::new(name, param_count))
}
fn function_from_class_decl(source: &str, tree: &Tree) -> Result<Function, &'static str> {
let name = tree.nth_token(1).ok_or("no name")?.as_str(source);
// TODO: I think this is incorrect!
let field_count = tree.children.len() - 2;
Ok(Function::new(name, field_count))
}
pub fn compile_module(semantics: &Semantics) -> Rc<CompiledModule> {
let source = semantics.source(); let source = semantics.source();
let syntax_tree = semantics.tree(); let syntax_tree = semantics.tree();
@ -269,35 +289,48 @@ pub fn compile(semantics: &Semantics) -> Rc<Module> {
semantics: &semantics, semantics: &semantics,
syntax: &syntax_tree, syntax: &syntax_tree,
function_bindings: HashMap::new(), module: CompiledModule::new(semantics.mid()),
pending_functions: Vec::new(),
temp_functions: Vec::new(),
module: Module::new(),
function: Function::new("<< module >>", 0), function: Function::new("<< module >>", 0),
}; };
let mut functions = vec![None; semantics.function_count() + 1];
if let Some(t) = semantics.tree().root() { if let Some(t) = semantics.tree().root() {
compiler.temp_functions.push(None);
file(&mut compiler, t); file(&mut compiler, t);
compiler.temp_functions[0] = Some(Rc::new(compiler.function)); let index = functions.len() - 1;
compiler.module.init = 0; functions[index] = Some(Rc::new(compiler.function));
compiler.module.init = index;
} }
while let Some((fk, idx, func)) = compiler.pending_functions.pop() { for t in semantics.tree().trees() {
if idx >= compiler.temp_functions.len() { if let Some(function_index) = semantics.get_function_index(t) {
compiler.temp_functions.resize(idx + 1, None); let tree = &semantics.tree()[t];
let function = match tree.kind {
TreeKind::FunctionDecl => function_from_function_decl(&source, &syntax_tree, tree),
TreeKind::ClassDecl => function_from_class_decl(&source, tree),
_ => Err("don't know how to make a function of this"),
};
if let Ok(function) = function {
compiler.function = function;
let _ = compile_function(&mut compiler, t);
let function = Rc::new(compiler.function);
compiler
.module
.exports
.insert(function.name.clone(), Export::Function(function_index));
functions[function_index] = Some(function);
}
} }
compiler.function = func;
let _ = compile_function(&mut compiler, fk.tree);
compiler.temp_functions[idx] = Some(Rc::new(compiler.function));
} }
let mut module = compiler.module; let mut module = compiler.module;
for f in compiler.temp_functions { for f in functions {
module.functions.push(f.unwrap()); module.functions.push(f.unwrap());
} }
module.deps.append(&mut semantics.import_ids());
Rc::new(module) Rc::new(module)
} }
@ -317,9 +350,6 @@ fn file(c: &mut Compiler, t: TreeRef) {
c.push(Instruction::Return); c.push(Instruction::Return);
} }
type CR = Result<(), &'static str>;
const OK: CR = CR::Ok(());
fn compile_expression(c: &mut Compiler, t: TreeRef) { fn compile_expression(c: &mut Compiler, t: TreeRef) {
let tree = &c.syntax[t]; let tree = &c.syntax[t];
let cr = match tree.kind { let cr = match tree.kind {
@ -633,8 +663,6 @@ fn compile_identifier_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> C
} }
fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declaration) -> CR { fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declaration) -> CR {
// TODO: Handle load of non-local value.
let index = declaration.index; let index = declaration.index;
let instruction = match declaration.location { let instruction = match declaration.location {
Location::Local => { Location::Local => {
@ -648,7 +676,12 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
Instruction::LoadArgument(index) Instruction::LoadArgument(index)
} }
Location::Module => { Location::Module => {
if declaration.module != c.semantics.mid() {
// TODO: Assert here too?
c.push(Instruction::ModulePrefix(declaration.module));
} else {
compiler_assert!(c, t, index < c.module.globals); compiler_assert!(c, t, index < c.module.globals);
}
Instruction::LoadModule(index) Instruction::LoadModule(index)
} }
Location::Slot => { Location::Slot => {
@ -657,32 +690,9 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
} }
Location::Function => { Location::Function => {
// TODO: Assert declaration is local if declaration.module != c.semantics.mid() {
let Origin::Source(ft) = declaration.origin else { c.push(Instruction::ModulePrefix(declaration.module));
ice!(c, t, "Function location but external origin?");
};
let key = FunctionKey { tree: ft };
let index = match c.function_bindings.get(&key) {
Some(index) => *index,
None => {
let tree = &c.syntax[ft];
compiler_assert_eq!(c, t, tree.kind, TreeKind::FunctionDecl);
compile_function_declaration(c, ft, tree, false)?;
match c.function_bindings.get(&key) {
Some(index) => *index,
None => {
ice!(
c,
t,
"did not compile the function with key {:?}!",
declaration
)
} }
}
}
};
Instruction::LoadFunction(index) Instruction::LoadFunction(index)
} }
@ -691,8 +701,8 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat
// Must be a static don't worry about it. // Must be a static don't worry about it.
Location::Class => return OK, Location::Class => return OK,
// fix later // Imports are handled with an instruction prefix.
Location::Import => ice!(c, t, "import compile not supported"), Location::Import => return OK,
}; };
c.push(instruction); c.push(instruction);
@ -988,24 +998,7 @@ fn compile_new_object_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> C
let declaration = environment.bind(identifier).ok_or("cannot bind type")?; let declaration = environment.bind(identifier).ok_or("cannot bind type")?;
match declaration.location { match declaration.location {
Location::Class => { Location::Class => {
let Origin::Source(classdecl) = declaration.origin else { c.push(Instruction::LoadFunction(declaration.index));
ice!(c, t, "this class declaration has no source?");
};
let key = FunctionKey { tree: classdecl };
let index = match c.function_bindings.get(&key) {
Some(index) => *index,
None => {
let tree = &c.syntax[classdecl];
compiler_assert_eq!(c, t, tree.kind, TreeKind::ClassDecl);
compile_class_declaration(c, t, tree, false)?;
*c.function_bindings
.get(&key)
.expect("did not compile the class constructor!")
}
};
c.push(Instruction::LoadFunction(index));
} }
_ => return Err("unsupported type for construction"), _ => return Err("unsupported type for construction"),
} }
@ -1039,20 +1032,7 @@ fn compile_member_access(c: &mut Compiler, t: TreeRef, tree: &Tree) -> CR {
let typ = c.semantics.type_of(lhs); let typ = c.semantics.type_of(lhs);
let ident = tree.nth_token(2).ok_or("no ident")?.as_str(&c.source); let ident = tree.nth_token(2).ok_or("no ident")?.as_str(&c.source);
let environment = match &typ { let environment = c.semantics.member_environment(t, &typ);
Type::Object(mid, ct, _) => {
let class = c.semantics.class_of(*mid, *ct);
class.env.clone()
}
Type::Class(mid, ct, _) => {
let class = c.semantics.class_of(*mid, *ct);
class.static_env.clone()
}
_ => {
c.push_panic("cannot get environment of {typ}");
return Err("cannot get environment");
}
};
let declaration = environment.bind(ident).ok_or("cannot bind")?; let declaration = environment.bind(ident).ok_or("cannot bind")?;
// NOTE: If this is a method call we still don't have to do anything // NOTE: If this is a method call we still don't have to do anything
@ -1092,14 +1072,16 @@ fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) {
TreeKind::Import => compile_import_statement(c, gen_value), TreeKind::Import => compile_import_statement(c, gen_value),
TreeKind::Block => compile_block_statement(c, t, gen_value), TreeKind::Block => compile_block_statement(c, t, gen_value),
TreeKind::ClassDecl => compile_class_declaration(c, t, tree, gen_value), TreeKind::ClassDecl => compile_class_declaration(c, gen_value),
TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value), TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value),
TreeKind::ForStatement => compile_for_statement(c, tree, gen_value), TreeKind::ForStatement => compile_for_statement(c, tree, gen_value),
TreeKind::FunctionDecl => compile_function_declaration(c, t, tree, gen_value), TreeKind::FunctionDecl => compile_function_declaration(c, gen_value),
TreeKind::IfStatement => compile_if_statement(c, tree, gen_value), TreeKind::IfStatement => compile_if_statement(c, tree, gen_value),
TreeKind::LetStatement => compile_let_statement(c, t, tree, gen_value), TreeKind::LetStatement => compile_let_statement(c, t, tree, gen_value),
TreeKind::ReturnStatement => compile_return_statement(c, tree), TreeKind::ReturnStatement => compile_return_statement(c, tree),
TreeKind::WhileStatement => compile_while_statement(c, tree, gen_value), TreeKind::WhileStatement => compile_while_statement(c, tree, gen_value),
TreeKind::Export => compile_export_statement(c, tree, gen_value),
TreeKind::ExportList => OK,
_ => ice!(c, t, "unsupported statement tree kind {:?}", tree.kind), _ => ice!(c, t, "unsupported statement tree kind {:?}", tree.kind),
}; };
@ -1112,6 +1094,11 @@ fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) {
} }
} }
fn compile_export_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR {
compile_statement(c, tree.nth_tree(1).ok_or("nothing to export")?, gen_value);
OK
}
fn compile_if_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR { fn compile_if_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR {
compile_expression(c, tree.nth_tree(0).ok_or("no expr")?); compile_expression(c, tree.nth_tree(0).ok_or("no expr")?);
if !gen_value { if !gen_value {
@ -1161,7 +1148,9 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b
Instruction::StoreLocal(index) Instruction::StoreLocal(index)
} }
Location::Module => { Location::Module => {
if index >= c.module.globals { if declaration.module != c.semantics.mid() {
c.push(Instruction::ModulePrefix(declaration.module));
} else if index >= c.module.globals {
c.module.globals = index + 1; c.module.globals = index + 1;
} }
Instruction::StoreModule(index) Instruction::StoreModule(index)
@ -1176,32 +1165,7 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b
OK OK
} }
fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR { fn compile_function_declaration(c: &mut Compiler, gen_value: bool) -> CR {
// Only compile a given function once.
//
// TODO: When it's time for generics, this should only actually compile
// if we have no unbound type variables.
let fk = FunctionKey { tree: t };
if !c.function_bindings.contains_key(&fk) {
// TODO: If this is a method the name should be different.
let name = tree.nth_token(1).ok_or("no id")?.as_str(&c.source);
let param_list = tree
.child_tree_of_kind(&c.syntax, TreeKind::ParamList)
.ok_or("no paramlist")?;
let param_count = param_list.children.len() - 2;
let function_index = c.temp_functions.len();
c.temp_functions.push(None);
c.pending_functions
.push((fk.clone(), function_index, Function::new(name, param_count)));
c.function_bindings.insert(fk, function_index);
c.module
.exports
.insert(name.to_string(), Export::Function(function_index));
}
if gen_value { if gen_value {
c.push(Instruction::PushNothing); c.push(Instruction::PushNothing);
} }
@ -1209,26 +1173,7 @@ fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_v
OK OK
} }
fn compile_class_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR { fn compile_class_declaration(c: &mut Compiler, gen_value: bool) -> CR {
// Only compile a given function once.
// Classes get compiled as constructor functions which get called.
let fk = FunctionKey { tree: t };
if !c.function_bindings.contains_key(&fk) {
let name = tree.nth_token(1).ok_or("no name")?.as_str(&c.source);
let field_count = tree.children.len() - 2;
let function_index = c.temp_functions.len();
c.temp_functions.push(None);
c.pending_functions
.push((fk.clone(), function_index, Function::new(name, field_count)));
c.function_bindings.insert(fk, function_index);
c.module
.exports
.insert(name.to_string(), Export::Function(function_index));
}
if gen_value { if gen_value {
c.push(Instruction::PushNothing); c.push(Instruction::PushNothing);
} }

View file

@ -1,181 +1,51 @@
use std::{collections::HashMap, fs, path::PathBuf, rc::Rc}; use std::{path::PathBuf, rc::Rc};
use compiler::compile; use compiler::compile_module;
use parser::parse; use program::{Module, Program, StandardModuleLoader};
use semantics::{check, Error, ImportRecord, ModuleId, Semantics};
use vm::{eval, Context}; use vm::{eval, Context};
pub mod compiler; pub mod compiler;
pub mod parser; pub mod parser;
pub mod program;
pub mod semantics; pub mod semantics;
pub mod tokens; pub mod tokens;
pub mod vm; pub mod vm;
pub enum ModuleSource { fn load_module(
SourceText(String), program: &Program,
} module: &Rc<Module>,
context: &mut Context,
) -> Result<(), vm::VMError> {
if !context.loaded(module.id()) {
let semantics = module.semantics();
let module = compile_module(&semantics);
context.set_module(module.clone());
#[derive(Debug)] for dep in module.deps.iter() {
pub enum ModuleLoadError { if let Some(dep_mod) = program.get_module(dep) {
IO(String, std::io::Error), load_module(program, dep_mod, context)?;
}
pub trait ModuleLoader {
fn normalize_module_name(&self, source: &str, name: String) -> String;
fn load_module(&self, name: &String) -> Result<ModuleSource, ModuleLoadError>;
}
pub struct StandardModuleLoader {
base_path: PathBuf,
}
impl StandardModuleLoader {
pub fn new(base_path: PathBuf) -> Self {
StandardModuleLoader { base_path }
} }
} }
impl ModuleLoader for StandardModuleLoader { eval(context, module.id, module.init, &[])?;
fn normalize_module_name(&self, source: &str, name: String) -> String {
let p = self.base_path.join(source).join(name.clone());
let result = match std::fs::canonicalize(&p) {
Ok(p) => match p.into_os_string().into_string() {
Ok(s) => s,
Err(_e) => name.clone(),
},
Err(_e) => name.clone(),
};
result
} }
fn load_module(&self, name: &String) -> Result<ModuleSource, ModuleLoadError> { Ok(())
match fs::read_to_string(name) {
Ok(c) => Ok(ModuleSource::SourceText(c)),
Err(e) => Err(ModuleLoadError::IO(name.clone(), e)),
}
}
} }
pub struct Module { pub fn compile_program(program: &Program, context: &mut Context) -> Result<(), vm::VMError> {
id: ModuleId, for module in program.modules() {
semantics: Rc<Semantics>, load_module(program, module, context)?;
} }
impl Module { Ok(())
pub fn id(&self) -> ModuleId {
self.id
}
pub fn semantics(&self) -> Rc<Semantics> {
self.semantics.clone()
}
}
pub struct Runtime {
next_module_id: u64,
modules: HashMap<String, Rc<Module>>,
loader: Box<dyn ModuleLoader>,
}
impl Runtime {
pub fn new(loader: Box<dyn ModuleLoader>) -> Self {
Runtime {
next_module_id: 0,
modules: HashMap::new(),
loader,
}
}
pub fn load_module(
&mut self,
name: &str,
) -> Result<(Vec<Rc<Error>>, Rc<Module>), ModuleLoadError> {
let mut init_pending = HashMap::new();
let mut names = Vec::new();
let name = self.loader.normalize_module_name("", name.to_string());
names.push(name.clone());
let mut id_assign = self.next_module_id;
while let Some(name) = names.pop() {
if self.modules.contains_key(&name) || init_pending.contains_key(&name) {
// Either already loaded or pending load.
continue;
}
// TODO: Errors here are bad! Remember, run everything!
match self.loader.load_module(&name)? {
ModuleSource::SourceText(source) => {
let mid = ModuleId::from(id_assign);
id_assign += 1;
let source: Rc<str> = source.into();
let (tree, lines) = parse(&source);
let semantics = Rc::new(Semantics::new(
mid,
name.clone().into(),
source,
tree,
lines,
));
let mut normalized_imports = Vec::new();
for import in semantics.imports() {
let normalized = self.loader.normalize_module_name(&name, import.clone());
names.push(normalized.clone());
normalized_imports.push((import, normalized));
}
init_pending.insert(name, (mid, normalized_imports, semantics));
}
}
}
for (_, (_, imports, semantics)) in init_pending.iter() {
let mut import_table = HashMap::new();
for (import, normalized) in imports.iter() {
// NOTE: We look up the load(ed|ing) module here by normalized name, because that's how
// we track it...
let target = if let Some(module) = self.modules.get(&*normalized) {
ImportRecord {
name: normalized.clone(),
module_id: module.id(),
semantics: Rc::downgrade(&module.semantics),
}
} else {
let (module_id, _, semantics) = init_pending.get(&*normalized).unwrap();
ImportRecord {
name: normalized.clone(),
module_id: *module_id,
semantics: Rc::downgrade(semantics),
}
};
// ...but we set it into the import table here with the name
// that the source code used, for more better binding.
import_table.insert(import.clone(), target);
}
semantics.set_imports(import_table);
}
let mut errors = Vec::new();
for (name, (id, _, semantics)) in init_pending.into_iter() {
check(&semantics);
errors.append(&mut semantics.snapshot_errors());
self.modules.insert(name, Rc::new(Module { id, semantics }));
}
self.next_module_id = id_assign;
let result = self.modules.get(&name).unwrap().clone();
Ok((errors, result))
}
} }
pub fn process_file(file: &str) { pub fn process_file(file: &str) {
let mut runtime = Runtime::new(Box::new(StandardModuleLoader::new(PathBuf::from(".")))); let mut program = Program::new(Box::new(StandardModuleLoader::new(PathBuf::from("."))));
let (errors, module) = match runtime.load_module(file) { // Load the program text
let (errors, _) = match program.load_module(file) {
Ok(r) => r, Ok(r) => r,
Err(_) => { Err(_) => {
eprintln!("Error loading module"); eprintln!("Error loading module");
@ -183,7 +53,7 @@ pub fn process_file(file: &str) {
} }
}; };
// OK now there might be errors. // OK now there might be semantic errors or whatnot.
if errors.len() > 0 { if errors.len() > 0 {
for e in errors { for e in errors {
eprintln!("{file}: {}:{}: {}", e.start.0, e.start.1, e.message); eprintln!("{file}: {}:{}: {}", e.start.0, e.start.1, e.message);
@ -191,16 +61,10 @@ pub fn process_file(file: &str) {
return; return;
} }
// shrug // This is weird, why run the init function as main? Maybe just run main?
let semantics = module.semantics(); let mut context = Context::new();
let module = compile(&semantics); match compile_program(&program, &mut context) {
let main_function = module.functions[module.init].clone(); Ok(_) => {}
let mut context = Context::new(module.clone());
match eval(&mut context, main_function, vec![]) {
Ok(v) => {
println!("{:?}", v);
}
Err(e) => { Err(e) => {
eprintln!("{:?}", e); eprintln!("{:?}", e);
} }

196
fine/src/program.rs Normal file
View file

@ -0,0 +1,196 @@
use std::{collections::HashMap, fs, path::PathBuf, rc::Rc};
use crate::parser::parse;
use crate::semantics::{check, Error, ImportRecord, ModuleId, Semantics};
pub enum ModuleSource {
SourceText(String),
}
#[derive(Debug)]
pub enum ModuleLoadError {
IO(String, std::io::Error),
}
pub trait ModuleLoader {
fn normalize_module_name(&self, source: &str, name: String) -> String;
fn load_module(&self, name: &String) -> Result<ModuleSource, ModuleLoadError>;
}
pub struct StandardModuleLoader {
base_path: PathBuf,
}
impl StandardModuleLoader {
pub fn new(base_path: PathBuf) -> Self {
StandardModuleLoader { base_path }
}
}
impl ModuleLoader for StandardModuleLoader {
fn normalize_module_name(&self, source: &str, name: String) -> String {
let p = self.base_path.join(source).join(name.clone());
let result = match std::fs::canonicalize(&p) {
Ok(p) => match p.into_os_string().into_string() {
Ok(s) => s,
Err(_e) => name.clone(),
},
Err(_e) => name.clone(),
};
result
}
fn load_module(&self, name: &String) -> Result<ModuleSource, ModuleLoadError> {
match fs::read_to_string(name) {
Ok(c) => Ok(ModuleSource::SourceText(c)),
Err(e) => Err(ModuleLoadError::IO(name.clone(), e)),
}
}
}
pub struct Module {
id: ModuleId,
semantics: Rc<Semantics>,
}
impl Module {
pub fn id(&self) -> ModuleId {
self.id
}
pub fn semantics(&self) -> Rc<Semantics> {
self.semantics.clone()
}
}
struct PendingModule {
mid: ModuleId,
imports: Vec<(String, String)>, // (raw, normalized)
semantics: Rc<Semantics>,
}
pub struct Program {
next_module_id: u64,
modules: HashMap<String, Rc<Module>>,
modules_by_id: HashMap<ModuleId, Rc<Module>>,
loader: Box<dyn ModuleLoader>,
}
impl Program {
pub fn new(loader: Box<dyn ModuleLoader>) -> Self {
Program {
next_module_id: 0,
modules: HashMap::new(),
modules_by_id: HashMap::new(),
loader,
}
}
pub fn modules(&self) -> impl Iterator<Item = &Rc<Module>> {
self.modules.values()
}
pub fn get_module(&self, id: &ModuleId) -> Option<&Rc<Module>> {
self.modules_by_id.get(id)
}
pub fn load_module(
&mut self,
name: &str,
) -> Result<(Vec<Rc<Error>>, Rc<Module>), ModuleLoadError> {
let mut init_pending = HashMap::new();
let mut names = Vec::new();
let name = self.loader.normalize_module_name("", name.to_string());
names.push(name.clone());
let mut id_assign = self.next_module_id;
while let Some(name) = names.pop() {
if self.modules.contains_key(&name) || init_pending.contains_key(&name) {
// Either already loaded or pending load.
continue;
}
// TODO: Errors here are bad! Remember, run everything!
match self.loader.load_module(&name)? {
ModuleSource::SourceText(source) => {
let mid = ModuleId::from(id_assign);
id_assign += 1;
let source: Rc<str> = source.into();
let (tree, lines) = parse(&source);
let semantics = Rc::new(Semantics::new(
mid,
name.clone().into(),
source,
tree,
lines,
));
let mut imports = Vec::new();
for import in semantics.imports() {
let normalized = self.loader.normalize_module_name(&name, import.clone());
names.push(normalized.clone());
imports.push((import, normalized));
}
init_pending.insert(
name,
PendingModule {
semantics,
mid,
imports,
},
);
}
}
}
for (_, pending) in init_pending.iter() {
let mut import_table = HashMap::new();
for (import, normalized) in pending.imports.iter() {
// NOTE: We look up the load(ed|ing) module here by normalized name, because that's how
// we track it...
let target = if let Some(module) = self.modules.get(&*normalized) {
ImportRecord {
name: normalized.clone(),
module_id: module.id(),
semantics: Rc::downgrade(&module.semantics),
}
} else {
let other = init_pending.get(&*normalized).unwrap();
ImportRecord {
name: normalized.clone(),
module_id: other.mid,
semantics: Rc::downgrade(&other.semantics),
}
};
// ...but we set it into the import table here with the name
// that the source code used, for more better binding.
import_table.insert(import.clone(), target);
}
// Now tell the semantics object about its import table.
pending.semantics.set_imports(import_table);
}
let mut errors = Vec::new();
for (name, pending) in init_pending.into_iter() {
check(&pending.semantics);
errors.append(&mut pending.semantics.snapshot_errors());
let module = Rc::new(Module {
id: pending.mid,
semantics: pending.semantics,
});
self.modules.insert(name, module.clone());
self.modules_by_id.insert(pending.mid, module);
}
self.next_module_id = id_assign;
let result = self.modules.get(&name).unwrap().clone();
Ok((errors, result))
}
}

View file

@ -116,6 +116,12 @@ impl From<u64> for ModuleId {
} }
} }
impl fmt::Display for ModuleId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{}", self.0)
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ImportRecord { pub struct ImportRecord {
pub name: String, pub name: String,
@ -344,9 +350,9 @@ pub enum Location {
Local, // A local in an frame Local, // A local in an frame
Slot, // A slot in an object Slot, // A slot in an object
Module, // A global in a module Module, // A global in a module
Function, // A function in a module (index unrelated) Function, // A function in a module
ExternalFunction, // An external function (module unrelated) ExternalFunction, // An external function (module unrelated)
Class, // A class in a module (index unrelated) Class, // A class in a module
Import, // An import in a module (index unrelated) Import, // An import in a module (index unrelated)
} }
@ -683,6 +689,9 @@ pub struct Semantics {
// This is what is used for binding. // This is what is used for binding.
logical_parents: Vec<Option<TreeRef>>, logical_parents: Vec<Option<TreeRef>>,
function_count: usize,
function_indices: Vec<Option<usize>>,
// TODO: State should be externalized instead of this refcell nonsense. // TODO: State should be externalized instead of this refcell nonsense.
errors: RefCell<Vec<Rc<Error>>>, errors: RefCell<Vec<Rc<Error>>>,
types: RefCell<Vec<Incremental<Type>>>, types: RefCell<Vec<Incremental<Type>>>,
@ -706,6 +715,19 @@ impl Semantics {
let root_environment = Environment::new(mid, None, Location::Module); let root_environment = Environment::new(mid, None, Location::Module);
let mut function_count = 0;
let mut function_indices = vec![None; tree.len()];
for t in tree.trees() {
let tree = &tree[t];
match tree.kind {
TreeKind::FunctionDecl | TreeKind::ClassDecl => {
function_indices[t.index()] = Some(function_count);
function_count += 1;
}
_ => {}
}
}
let mut semantics = Semantics { let mut semantics = Semantics {
mid, mid,
file, file,
@ -714,6 +736,8 @@ impl Semantics {
lines, lines,
import_map: OnceCell::new(), import_map: OnceCell::new(),
logical_parents, logical_parents,
function_count,
function_indices,
errors: RefCell::new(vec![]), errors: RefCell::new(vec![]),
types: RefCell::new(vec![Incremental::None; tree.len()]), types: RefCell::new(vec![Incremental::None; tree.len()]),
environments: RefCell::new(vec![Incremental::None; tree.len()]), environments: RefCell::new(vec![Incremental::None; tree.len()]),
@ -744,6 +768,12 @@ impl Semantics {
self.import_map.set(imports).expect("imports already set"); self.import_map.set(imports).expect("imports already set");
} }
pub fn import_ids(&self) -> Vec<ModuleId> {
// TODO: Pull from by_name when we go global
let import_map = self.import_map.get().unwrap();
import_map.by_id.keys().map(|id| *id).collect()
}
pub fn import_by_id(&self, mid: ModuleId) -> Option<Rc<Semantics>> { pub fn import_by_id(&self, mid: ModuleId) -> Option<Rc<Semantics>> {
// TODO: ACTUALLY THIS IS WRONG, WE NEED THE GLOBAL MAP HERE, NOT THE LOCAL ONE. // TODO: ACTUALLY THIS IS WRONG, WE NEED THE GLOBAL MAP HERE, NOT THE LOCAL ONE.
let import_map = self.import_map.get()?; let import_map = self.import_map.get()?;
@ -797,6 +827,10 @@ impl Semantics {
} }
} }
pub fn mid(&self) -> ModuleId {
self.mid
}
fn report_error_span<T>(&self, start_pos: usize, end_pos: usize, error: T) -> Rc<Error> fn report_error_span<T>(&self, start_pos: usize, end_pos: usize, error: T) -> Rc<Error>
where where
T: ToString, T: ToString,
@ -892,6 +926,27 @@ impl Semantics {
Environment::error(error) Environment::error(error)
} }
pub fn function_count(&self) -> usize {
self.function_count
}
pub fn get_function_index(&self, t: TreeRef) -> Option<usize> {
let index = t.index();
if index >= self.function_indices.len() {
None
} else {
self.function_indices[t.index()]
}
}
pub fn function_index_of(&self, t: TreeRef) -> usize {
let Some(index) = self.function_indices[t.index()] else {
self.internal_compiler_error(Some(t), "Why didn't I get a function index for this?");
};
index
}
pub fn environment_of(&self, t: TreeRef) -> EnvironmentRef { pub fn environment_of(&self, t: TreeRef) -> EnvironmentRef {
{ {
// I want to make sure that this borrow is dropped after this block. // I want to make sure that this borrow is dropped after this block.
@ -952,7 +1007,7 @@ impl Semantics {
name.as_str(&self.source).into(), name.as_str(&self.source).into(),
Declaration { Declaration {
location: Location::Function, location: Location::Function,
index: 0, index: self.function_index_of(*t),
module: self.mid, module: self.mid,
origin: Origin::Source(*t), origin: Origin::Source(*t),
exported: false, exported: false,
@ -1007,7 +1062,7 @@ impl Semantics {
let declaration = Declaration { let declaration = Declaration {
location: Location::Function, location: Location::Function,
index: 0, index: self.function_index_of(t),
module: self.mid, module: self.mid,
origin: Origin::Source(t), origin: Origin::Source(t),
exported, exported,
@ -1024,7 +1079,7 @@ impl Semantics {
let declaration = Declaration { let declaration = Declaration {
location: Location::Class, location: Location::Class,
index: 0, index: self.function_index_of(t),
module: self.mid, module: self.mid,
origin: Origin::Source(t), origin: Origin::Source(t),
exported, exported,
@ -1393,7 +1448,7 @@ impl Semantics {
(&*method.name).into(), (&*method.name).into(),
Declaration { Declaration {
location: Location::Function, location: Location::Function,
index: 0, index: self.function_index_of(method.declaration),
module: self.mid, module: self.mid,
origin: Origin::Source(method.declaration), origin: Origin::Source(method.declaration),
exported: false, exported: false,

View file

@ -1,11 +1,13 @@
use core::fmt;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use crate::compiler::{ use crate::compiler::{
Export, Function, Instruction, Module, EXTERN_BUILTIN_LIST_GET_ITERATOR, CompiledModule, Export, Function, Instruction, EXTERN_BUILTIN_LIST_GET_ITERATOR,
EXTERN_BUILTIN_LIST_ITERATOR_NEXT, EXTERN_BUILTIN_NOOP, EXTERN_BUILTIN_LIST_ITERATOR_NEXT, EXTERN_BUILTIN_NOOP,
}; };
use crate::semantics::Type; use crate::semantics::{ModuleId, Type};
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -44,6 +46,13 @@ pub enum VMErrorCode {
SlotOutOfRange(usize, Rc<str>, usize), SlotOutOfRange(usize, Rc<str>, usize),
#[error("internal error: the extern function with ID {0} was not registered")] #[error("internal error: the extern function with ID {0} was not registered")]
UnregisteredExternFunction(usize), UnregisteredExternFunction(usize),
#[error("the requested export was not found: {0}")]
ExportNotFound(String),
#[error("the requested export is not a function: {0}")]
ExportNotFunction(String),
#[error("a module with id {0} has not been loaded")]
ModuleNotFound(ModuleId),
} }
#[derive(Debug)] #[derive(Debug)]
@ -80,6 +89,21 @@ pub struct ListIterator {
index: Cell<usize>, index: Cell<usize>,
} }
#[derive(Clone)]
pub enum FuncValue {
Function(Rc<RuntimeModule>, Rc<Function>),
ExternFunction(usize),
}
impl fmt::Debug for FuncValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FuncValue::Function(m, func) => write!(f, "fuction #{:?}:{:?}", m, func),
FuncValue::ExternFunction(i) => write!(f, "external {i}"),
}
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum StackValue { pub enum StackValue {
Nothing, Nothing,
@ -87,8 +111,7 @@ pub enum StackValue {
Float(f64), Float(f64),
Int(i64), Int(i64),
String(Rc<str>), String(Rc<str>),
Function(Rc<Function>), Function(Box<FuncValue>),
ExternFunction(usize),
Object(Rc<Object>), Object(Rc<Object>),
List(Rc<Vec<StackValue>>), List(Rc<Vec<StackValue>>),
ListIterator(Rc<ListIterator>), ListIterator(Rc<ListIterator>),
@ -131,13 +154,9 @@ impl StackValue {
} }
} }
enum FuncValue {
Function(Rc<Function>),
ExternFunction(usize),
}
#[derive(Debug)] #[derive(Debug)]
pub struct Frame { pub struct Frame {
module: Rc<RuntimeModule>,
func: Rc<Function>, func: Rc<Function>,
args: Vec<StackValue>, args: Vec<StackValue>,
locals: Vec<StackValue>, locals: Vec<StackValue>,
@ -146,11 +165,12 @@ pub struct Frame {
} }
impl Frame { impl Frame {
fn from_function(func: Rc<Function>, args: Vec<StackValue>) -> Self { fn from_function(module: Rc<RuntimeModule>, func: Rc<Function>, args: Vec<StackValue>) -> Self {
let mut locals = Vec::new(); let mut locals = Vec::new();
locals.resize(func.locals(), StackValue::Nothing); locals.resize(func.locals(), StackValue::Nothing);
Frame { Frame {
module,
func, func,
args, args,
locals, locals,
@ -159,8 +179,12 @@ impl Frame {
} }
} }
pub fn func(&self) -> Rc<Function> { pub fn module(&self) -> &Rc<RuntimeModule> {
self.func.clone() &self.module
}
pub fn func(&self) -> &Rc<Function> {
&self.func
} }
pub fn args(&self) -> &[StackValue] { pub fn args(&self) -> &[StackValue] {
@ -230,12 +254,12 @@ impl Frame {
self.push_value(StackValue::String(v)) self.push_value(StackValue::String(v))
} }
fn push_function(&mut self, v: Rc<Function>) { fn push_function(&mut self, m: Rc<RuntimeModule>, v: Rc<Function>) {
self.push_value(StackValue::Function(v)); self.push_value(StackValue::Function(Box::new(FuncValue::Function(m, v))));
} }
fn push_extern_function(&mut self, v: usize) { fn push_extern_function(&mut self, v: usize) {
self.push_value(StackValue::ExternFunction(v)); self.push_value(StackValue::Function(Box::new(FuncValue::ExternFunction(v))));
} }
fn push_object(&mut self, v: Rc<Object>) { fn push_object(&mut self, v: Rc<Object>) {
@ -292,55 +316,86 @@ impl Frame {
fn pop_function(&mut self) -> Result<FuncValue> { fn pop_function(&mut self) -> Result<FuncValue> {
match self.pop_value()? { match self.pop_value()? {
StackValue::Function(f) => Ok(FuncValue::Function(f)), StackValue::Function(f) => Ok(*f),
StackValue::ExternFunction(i) => Ok(FuncValue::ExternFunction(i)),
v => Err(VMErrorCode::StackExpectedFunction(v)), v => Err(VMErrorCode::StackExpectedFunction(v)),
} }
} }
} }
pub struct Context { pub struct RuntimeModule {
module: Rc<Module>, code: Rc<CompiledModule>,
globals: Vec<StackValue>, globals: RefCell<Vec<StackValue>>,
} }
impl Context { impl fmt::Debug for RuntimeModule {
pub fn new(module: Rc<Module>) -> Context { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{}", self.code.id)
}
}
impl RuntimeModule {
fn new(code: Rc<CompiledModule>) -> RuntimeModule {
let mut globals = Vec::new(); let mut globals = Vec::new();
globals.resize(module.globals, StackValue::Nothing); globals.resize(code.globals, StackValue::Nothing);
Context { module, globals } RuntimeModule {
code,
globals: RefCell::new(globals),
} }
pub fn init(&mut self) -> std::result::Result<(), VMError> {
let init_index = self.module.init;
let init_fn = self.module.functions[init_index].clone();
eval(self, init_fn, vec![])?;
Ok(())
} }
fn get_global(&self, i: usize) -> Result<StackValue> { fn get_global(&self, i: usize) -> Result<StackValue> {
self.globals self.globals
.borrow()
.get(i) .get(i)
.map(|v| v.clone()) .map(|v| v.clone())
.ok_or_else(|| VMErrorCode::GlobalOutOfRange(i)) // TODO: Test .ok_or_else(|| VMErrorCode::GlobalOutOfRange(i)) // TODO: Test
} }
fn set_global(&mut self, i: usize, v: StackValue) -> Result<()> { fn set_global(&self, i: usize, v: StackValue) -> Result<()> {
if i >= self.globals.len() { let mut globals = self.globals.borrow_mut();
if i >= globals.len() {
Err(VMErrorCode::GlobalOutOfRange(i)) // TODO: Test Err(VMErrorCode::GlobalOutOfRange(i)) // TODO: Test
} else { } else {
self.globals[i] = v; globals[i] = v;
Ok(()) Ok(())
} }
} }
fn get_function(&self, i: usize) -> Result<Rc<Function>> { fn get_function(&self, i: usize) -> Result<Rc<Function>> {
let functions = self.module.functions(); let functions = self.code.functions();
functions functions
.get(i) .get(i)
.map(|v| v.clone()) .map(|v| v.clone())
.ok_or_else(|| VMErrorCode::FunctionOutOfRange(i)) // TODO: Test .ok_or_else(|| VMErrorCode::FunctionOutOfRange(i)) // TODO: Test
} }
}
pub struct Context {
modules: HashMap<ModuleId, Rc<RuntimeModule>>,
module_prefix: Option<ModuleId>,
}
impl Context {
pub fn new() -> Context {
Context {
modules: HashMap::new(),
module_prefix: None,
}
}
pub fn loaded(&self, module: ModuleId) -> bool {
self.modules.contains_key(&module)
}
pub fn get_module(&self, module: ModuleId) -> Option<&Rc<CompiledModule>> {
self.modules.get(&module).map(|rm| &rm.code)
}
pub fn set_module(&mut self, module: Rc<CompiledModule>) {
let id = module.id;
let runtime_module = Rc::new(RuntimeModule::new(module));
self.modules.insert(id, runtime_module.clone());
}
fn call_extern_function(&self, index: usize, args: &[StackValue]) -> Result<StackValue> { fn call_extern_function(&self, index: usize, args: &[StackValue]) -> Result<StackValue> {
match index { match index {
@ -393,6 +448,8 @@ fn eval_one(
f: &mut Frame, f: &mut Frame,
stack: &mut Vec<Frame>, stack: &mut Vec<Frame>,
) -> Result<Flow> { ) -> Result<Flow> {
let module_prefix = c.module_prefix.take();
match instruction { match instruction {
Instruction::Panic(index) => { Instruction::Panic(index) => {
let v = f let v = f
@ -457,7 +514,15 @@ fn eval_one(
f.push_value(v); f.push_value(v);
} }
Instruction::LoadModule(i) => { Instruction::LoadModule(i) => {
let v = c.get_global(i)?; let module = module_prefix
.map(|id| {
c.modules
.get(&id)
.ok_or_else(|| VMErrorCode::ModuleNotFound(id))
})
.unwrap_or(Ok(&f.module))?;
let v = module.get_global(i)?;
f.push_value(v); f.push_value(v);
} }
Instruction::PushFalse => { Instruction::PushFalse => {
@ -486,7 +551,15 @@ fn eval_one(
} }
Instruction::StoreModule(i) => { Instruction::StoreModule(i) => {
let v = f.pop_value()?; let v = f.pop_value()?;
c.set_global(i, v)?; let module = module_prefix
.map(|id| {
c.modules
.get(&id)
.ok_or_else(|| VMErrorCode::ModuleNotFound(id))
})
.unwrap_or(Ok(&f.module))?;
module.set_global(i, v)?;
} }
Instruction::StoreSlot(i) => { Instruction::StoreSlot(i) => {
let o = f.pop_object()?; let o = f.pop_object()?;
@ -494,8 +567,16 @@ fn eval_one(
o.values.borrow_mut()[i] = v; o.values.borrow_mut()[i] = v;
} }
Instruction::LoadFunction(i) => { Instruction::LoadFunction(i) => {
let v = c.get_function(i)?; let module = module_prefix
f.push_function(v); .map(|id| {
c.modules
.get(&id)
.ok_or_else(|| VMErrorCode::ModuleNotFound(id))
})
.unwrap_or(Ok(&f.module))?;
let v = module.get_function(i)?;
f.push_function(module.clone(), v);
} }
Instruction::LoadExternFunction(i) => { Instruction::LoadExternFunction(i) => {
f.push_extern_function(i); f.push_extern_function(i);
@ -507,8 +588,8 @@ fn eval_one(
args.push(f.pop_value()?); args.push(f.pop_value()?);
} }
match function { match function {
FuncValue::Function(func) => { FuncValue::Function(module, func) => {
let mut frame = Frame::from_function(func, args); let mut frame = Frame::from_function(module, func, args);
std::mem::swap(&mut frame, f); std::mem::swap(&mut frame, f);
frame.pc = *index; frame.pc = *index;
stack.push(frame); stack.push(frame);
@ -533,8 +614,8 @@ fn eval_one(
let x = f.pop_string()?; let x = f.pop_string()?;
let y = f.pop_string()?; let y = f.pop_string()?;
let mut new_string = x.to_string(); let mut new_string = y.to_string();
new_string.push_str(&y); new_string.push_str(&x);
f.push_string(new_string.into()); f.push_string(new_string.into());
} }
@ -624,18 +705,22 @@ fn eval_one(
} }
f.push_list(Rc::new(v)); f.push_list(Rc::new(v));
} }
Instruction::ModulePrefix(mid) => {
c.module_prefix = Some(mid);
}
} }
Ok(Flow::Continue) Ok(Flow::Continue)
} }
pub fn eval( fn eval_core(
c: &mut Context, c: &mut Context,
module: Rc<RuntimeModule>,
function: Rc<Function>, function: Rc<Function>,
args: Vec<StackValue>, args: Vec<StackValue>,
) -> std::result::Result<StackValue, VMError> { ) -> std::result::Result<StackValue, VMError> {
let mut stack = Vec::new(); let mut stack = Vec::new();
let mut f = Frame::from_function(function, args); let mut f = Frame::from_function(module, function, args);
let mut index = 0; let mut index = 0;
loop { loop {
@ -680,18 +765,61 @@ pub fn eval(
} }
} }
pub fn eval(
c: &mut Context,
module: ModuleId,
function: usize,
args: &[StackValue],
) -> std::result::Result<StackValue, VMError> {
let Some(module) = c.modules.get(&module) else {
return Err(VMError {
code: VMErrorCode::ModuleNotFound(module),
stack: Box::new([]),
});
};
let Some(function) = module.code.functions.get(function) else {
return Err(VMError {
code: VMErrorCode::FunctionOutOfRange(function),
stack: Box::new([]),
});
};
let function = function.clone();
let args = args.iter().map(|a| a.clone()).collect();
eval_core(c, module.clone(), function, args)
}
pub fn eval_export_fn( pub fn eval_export_fn(
c: &mut Context, c: &mut Context,
module: ModuleId,
name: &str, name: &str,
args: &[StackValue], args: &[StackValue],
) -> std::result::Result<StackValue, VMError> { ) -> std::result::Result<StackValue, VMError> {
let export = match c.module.exports.get(name) { let Some(module) = c.modules.get(&module) else {
Some(Export::Function(id)) => id, return Err(VMError {
Some(_) => todo!(), code: VMErrorCode::ModuleNotFound(module),
None => todo!(), stack: Box::new([]),
});
}; };
let function = c.module.functions[*export].clone(); let export = match module.code.exports.get(name) {
let args = args.iter().map(|a| a.clone()).collect(); Some(Export::Function(id)) => id,
eval(c, function, args) Some(_) => {
return Err(VMError {
code: VMErrorCode::ExportNotFunction(name.to_string()),
stack: Box::new([]),
})
}
None => {
return Err(VMError {
code: VMErrorCode::ExportNotFound(name.to_string()),
stack: Box::new([]),
})
}
};
let function = module.code.functions[*export].clone();
let args = args.iter().map(|a| a.clone()).collect();
eval_core(c, module.clone(), function, args)
} }

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::semantics::{Error, Type};
use fine::vm::{eval_export_fn, Context}; use fine::vm::{eval_export_fn, Context, VMError};
use fine::{ModuleLoadError, ModuleLoader, ModuleSource, Runtime, StandardModuleLoader};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use std::fmt::Write as _; use std::fmt::Write as _;
@ -168,11 +171,11 @@ impl ModuleLoader for TestLoader {
} }
} }
fn test_runtime(_source_path: &str, source: Rc<str>) -> Runtime { fn test_runtime(_source_path: &str, source: Rc<str>) -> Program {
Runtime::new(TestLoader::new(_source_path.into(), source)) 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 semantics = module.semantics();
let tree = semantics.tree(); 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( fn assert_type_error_at(
module: Rc<fine::Module>, module: Rc<Module>,
errors: &[Rc<Error>], errors: &[Rc<Error>],
pos: usize, pos: usize,
expected: &str, expected: &str,
@ -256,7 +259,7 @@ fn dump_function(out: &mut String, function: &Function) -> std::fmt::Result {
Ok(()) 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() { for function in module.functions() {
dump_function(out, function)?; dump_function(out, function)?;
} }
@ -264,9 +267,9 @@ fn dump_module(out: &mut String, module: &Module) -> std::fmt::Result {
Ok(()) 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 semantics = module.semantics();
let module = compile(&semantics); let module = compile_module(&semantics);
let mut actual = String::new(); let mut actual = String::new();
dump_module(&mut actual, &module).expect("no dumping?"); 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 semantics = module.semantics();
let expected_errors: &[Rc<Error>] = &[]; let expected_errors: &[Rc<Error>] = &[];
@ -299,31 +302,16 @@ 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 semantics = module.semantics();
let module = compile(&semantics);
let mut context = Context::new(module.clone());
context.init().expect("Unable to initialize module");
match eval_export_fn(&mut context, "test", &[]) {
Ok(v) => {
let actual = format!("{:?}", v);
semantic_assert_eq!(
&semantics,
None,
expected,
&actual,
"wrong return from test function"
);
}
Err(e) => {
semantics.dump_compiler_state(None); semantics.dump_compiler_state(None);
if let Some(module) = context.get_module(module.id()) {
let mut actual = String::new(); let mut actual = String::new();
let _ = dump_module(&mut actual, &module); let _ = dump_module(&mut actual, &module);
eprintln!("{actual}"); eprintln!("{actual}");
}
eprintln!("Backtrace:"); eprintln!("Backtrace:");
for frame in e.stack.iter() { for frame in e.stack.iter() {
@ -338,10 +326,30 @@ fn assert_eval_ok(module: Rc<fine::Module>, expected: &str) {
panic!("error occurred while running: {:?}", e.code); 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!(
&semantics,
None,
expected,
&actual,
"wrong return from test function"
);
}
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 semantics = module.semantics();
let errors: Vec<String> = errors.iter().map(|e| format!("{}", e)).collect(); 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(); let semantics = module.semantics();
semantic_assert!( semantic_assert!(

View file

@ -64,11 +64,6 @@ fun test() -> f64 {
// | RightBrace:'"}"' // | RightBrace:'"}"'
// | // |
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | function foo (1 args, 0 locals): // | function foo (1 args, 0 locals):
// | strings (0): // | strings (0):
// | code (4): // | code (4):
@ -80,7 +75,12 @@ fun test() -> f64 {
// | strings (0): // | strings (0):
// | code (4): // | code (4):
// | 0: PushFloat(1.0) // | 0: PushFloat(1.0)
// | 1: LoadFunction(1) // | 1: LoadFunction(0)
// | 2: Call(1) // | 2: Call(1)
// | 3: Return // | 3: Return
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | // |

View file

@ -40,11 +40,6 @@ fun test() -> f64 {
// | RightBrace:'"}"' // | RightBrace:'"}"'
// | // |
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | function test (0 args, 0 locals): // | function test (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (10): // | code (10):
@ -58,4 +53,9 @@ fun test() -> f64 {
// | 7: FloatMultiply // | 7: FloatMultiply
// | 8: FloatAdd // | 8: FloatAdd
// | 9: Return // | 9: Return
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | // |

View file

@ -8,11 +8,6 @@ fun test() -> f64 {
// @no-errors // @no-errors
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | function test (0 args, 3 locals): // | function test (0 args, 3 locals):
// | strings (0): // | strings (0):
// | code (14): // | code (14):
@ -30,5 +25,10 @@ fun test() -> f64 {
// | 11: Discard // | 11: Discard
// | 12: LoadLocal(0) // | 12: LoadLocal(0)
// | 13: Return // | 13: Return
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | // |
// @eval: Float(2.0) // @eval: Float(2.0)

View file

@ -4,12 +4,12 @@ fun test() {
// @no-errors // @no-errors
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals): // | function test (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (2): // | code (2):
// | 0: PushNothing // | 0: PushNothing
// | 1: Return // | 1: Return
// | function test (0 args, 0 locals): // | function << module >> (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (2): // | code (2):
// | 0: PushNothing // | 0: PushNothing

View file

@ -4,11 +4,6 @@ fun test() -> bool {
// @no-errors // @no-errors
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | function test (0 args, 0 locals): // | function test (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (15): // | code (15):
@ -27,6 +22,11 @@ fun test() -> bool {
// | 12: PushTrue // | 12: PushTrue
// | 13: BoolNot // | 13: BoolNot
// | 14: Return // | 14: Return
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | // |
// @eval: Bool(false) // @eval: Bool(false)
// @type: 15 bool // @type: 15 bool

View file

@ -30,11 +30,26 @@ fun test() -> f64 {
// @no-errors // @no-errors
// @eval: Float(597.0) // @eval: Float(597.0)
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals): // | function something_static (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (2): // | code (2):
// | 0: PushNothing // | 0: PushFloat(12.0)
// | 1: Return // | 1: Return
// | function square_length (1 args, 0 locals):
// | strings (0):
// | code (12):
// | 0: LoadArgument(0)
// | 1: LoadSlot(0)
// | 2: LoadArgument(0)
// | 3: LoadSlot(0)
// | 4: FloatMultiply
// | 5: LoadArgument(0)
// | 6: LoadSlot(1)
// | 7: LoadArgument(0)
// | 8: LoadSlot(1)
// | 9: FloatMultiply
// | 10: FloatAdd
// | 11: Return
// | function Point (6 args, 0 locals): // | function Point (6 args, 0 locals):
// | strings (1): // | strings (1):
// | 0: "Point" // | 0: "Point"
@ -60,13 +75,13 @@ fun test() -> f64 {
// | code (27): // | code (27):
// | 0: PushFloat(99.0) // | 0: PushFloat(99.0)
// | 1: PushFloat(999.0) // | 1: PushFloat(999.0)
// | 2: LoadFunction(1) // | 2: LoadFunction(2)
// | 3: Call(2) // | 3: Call(2)
// | 4: PushFloat(23.0) // | 4: PushFloat(23.0)
// | 5: PushFloat(7.0) // | 5: PushFloat(7.0)
// | 6: LoadFunction(1) // | 6: LoadFunction(2)
// | 7: Call(2) // | 7: Call(2)
// | 8: LoadFunction(2) // | 8: LoadFunction(3)
// | 9: Call(2) // | 9: Call(2)
// | 10: StoreLocal(0) // | 10: StoreLocal(0)
// | 11: LoadLocal(0) // | 11: LoadLocal(0)
@ -76,33 +91,18 @@ fun test() -> f64 {
// | 15: LoadSlot(0) // | 15: LoadSlot(0)
// | 16: LoadSlot(0) // | 16: LoadSlot(0)
// | 17: LoadLocal(1) // | 17: LoadLocal(1)
// | 18: LoadFunction(4) // | 18: LoadFunction(1)
// | 19: Call(1) // | 19: Call(1)
// | 20: FloatAdd // | 20: FloatAdd
// | 21: LoadFunction(5) // | 21: LoadFunction(0)
// | 22: Call(0) // | 22: Call(0)
// | 23: FloatAdd // | 23: FloatAdd
// | 24: StoreLocal(2) // | 24: StoreLocal(2)
// | 25: LoadLocal(2) // | 25: LoadLocal(2)
// | 26: Return // | 26: Return
// | function square_length (1 args, 0 locals): // | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (12):
// | 0: LoadArgument(0)
// | 1: LoadSlot(0)
// | 2: LoadArgument(0)
// | 3: LoadSlot(0)
// | 4: FloatMultiply
// | 5: LoadArgument(0)
// | 6: LoadSlot(1)
// | 7: LoadArgument(0)
// | 8: LoadSlot(1)
// | 9: FloatMultiply
// | 10: FloatAdd
// | 11: Return
// | function something_static (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (2): // | code (2):
// | 0: PushFloat(12.0) // | 0: PushNothing
// | 1: Return // | 1: Return
// | // |

View file

@ -58,11 +58,6 @@ fun test() -> f64 {
// | RightBrace:'"}"' // | RightBrace:'"}"'
// //
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | function test (0 args, 0 locals): // | function test (0 args, 0 locals):
// | strings (1): // | strings (1):
// | 0: "discarded" // | 0: "discarded"
@ -75,5 +70,10 @@ fun test() -> f64 {
// | 5: Jump(7) // | 5: Jump(7)
// | 6: PushFloat(45.0) // | 6: PushFloat(45.0)
// | 7: Return // | 7: Return
// | function << module >> (0 args, 0 locals):
// | strings (0):
// | code (2):
// | 0: PushNothing
// | 1: Return
// | // |
// @eval: Float(23.0) // @eval: Float(23.0)

View file

@ -64,6 +64,13 @@ fun test() -> f64 {
// | RightBrace:'"}"' // | RightBrace:'"}"'
// | // |
// @compiles-to: // @compiles-to:
// | function test (0 args, 0 locals):
// | strings (0):
// | code (4):
// | 0: LoadModule(0)
// | 1: LoadModule(1)
// | 2: FloatAdd
// | 3: Return
// | function << module >> (0 args, 0 locals): // | function << module >> (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (12): // | code (12):
@ -79,11 +86,4 @@ fun test() -> f64 {
// | 9: Discard // | 9: Discard
// | 10: PushNothing // | 10: PushNothing
// | 11: Return // | 11: Return
// | function test (0 args, 0 locals):
// | strings (0):
// | code (4):
// | 0: LoadModule(0)
// | 1: LoadModule(1)
// | 2: FloatAdd
// | 3: Return
// | // |

View file

@ -1,8 +1,14 @@
import "./foo.fine" as foo; 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 { fun test() -> string {
foo.hello() + " world" foo.hello() + " world"
} }
// TODO: Obviously run the code duh
// @no-errors // @no-errors
// @eval: String("hello world")