diff --git a/fine/TODO b/fine/TODO new file mode 100644 index 00000000..33d3eccf --- /dev/null +++ b/fine/TODO @@ -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` that does the boxing, and `new` should just make the standard one \ No newline at end of file diff --git a/fine/build.rs b/fine/build.rs index 1b3c7b1e..9938438a 100644 --- a/fine/build.rs +++ b/fine/build.rs @@ -107,7 +107,7 @@ fn generate_test_for_file(path: PathBuf) -> String { } else if let Some(line) = line.strip_prefix("@eval:") { let expected = line.trim(); 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:") { let expected = line.trim(); @@ -139,8 +139,8 @@ fn generate_test_for_file(path: PathBuf) -> String { #disabled fn #name() { let source : std::rc::Rc = #contents.into(); - let mut runtime = crate::test_runtime(#display_path, source.clone()); - let (_errors, _module) = runtime.load_module("__test__").unwrap(); + let mut program = crate::test_runtime(#display_path, source.clone()); + let (_errors, _module) = program.load_module("__test__").unwrap(); #(#assertions)* } diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index 0086b35c..09a095f8 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -3,7 +3,9 @@ use std::rc::Rc; use crate::{ 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, }; @@ -62,6 +64,8 @@ pub enum Instruction { StoreSlot(usize), StringAdd, NewList(usize), + + ModulePrefix(ModuleId), } pub enum Export { @@ -69,23 +73,31 @@ pub enum Export { Global(usize), } -pub struct Module { +pub struct CompiledModule { + pub id: ModuleId, pub functions: Vec>, // Functions pub globals: usize, // The number of global variables pub exports: HashMap, // Exports by name pub init: usize, // The index of the initialization function + pub deps: Vec, // Modules I depend on } -impl Module { - pub fn new() -> Self { - Module { +impl CompiledModule { + pub fn new(id: ModuleId) -> Self { + CompiledModule { + id, functions: Vec::new(), globals: 0, exports: HashMap::new(), init: 0, + deps: Vec::new(), } } + pub fn init_function(&self) -> &Rc { + &self.functions[self.init] + } + pub fn functions(&self) -> &[Rc] { &self.functions } @@ -142,21 +154,12 @@ impl std::fmt::Debug for Function { } } -#[derive(Eq, PartialEq, Hash, Clone)] -struct FunctionKey { - tree: TreeRef, -} - struct Compiler<'a> { source: &'a str, semantics: &'a Semantics, syntax: &'a SyntaxTree, - function_bindings: HashMap, - pending_functions: Vec<(FunctionKey, usize, Function)>, - temp_functions: Vec>>, - - module: Module, + module: CompiledModule, function: Function, } @@ -249,18 +252,35 @@ macro_rules! ice { }} } -// macro_rules! inst_panic { -// ($($t:tt)+) => {{ -// // eprintln!($($t)*); -// Instruction::Panic -// }}; -// } +type CR = Result<(), &'static str>; +const OK: CR = CR::Ok(()); -// macro_rules! ice { -// ($compiler:expr, $tr:expr, $($t:tt)*) => {{}}; -// } +fn function_from_function_decl( + source: &str, + syntax: &SyntaxTree, + tree: &Tree, +) -> Result { + // 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 { + 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 { + 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 { let source = semantics.source(); let syntax_tree = semantics.tree(); @@ -269,35 +289,48 @@ pub fn compile(semantics: &Semantics) -> Rc { semantics: &semantics, syntax: &syntax_tree, - function_bindings: HashMap::new(), - pending_functions: Vec::new(), - temp_functions: Vec::new(), - - module: Module::new(), + module: CompiledModule::new(semantics.mid()), function: Function::new("<< module >>", 0), }; + let mut functions = vec![None; semantics.function_count() + 1]; if let Some(t) = semantics.tree().root() { - compiler.temp_functions.push(None); file(&mut compiler, t); - compiler.temp_functions[0] = Some(Rc::new(compiler.function)); - compiler.module.init = 0; + let index = functions.len() - 1; + functions[index] = Some(Rc::new(compiler.function)); + compiler.module.init = index; } - while let Some((fk, idx, func)) = compiler.pending_functions.pop() { - if idx >= compiler.temp_functions.len() { - compiler.temp_functions.resize(idx + 1, None); + for t in semantics.tree().trees() { + if let Some(function_index) = semantics.get_function_index(t) { + 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; - for f in compiler.temp_functions { + for f in functions { module.functions.push(f.unwrap()); } + module.deps.append(&mut semantics.import_ids()); Rc::new(module) } @@ -317,9 +350,6 @@ fn file(c: &mut Compiler, t: TreeRef) { c.push(Instruction::Return); } -type CR = Result<(), &'static str>; -const OK: CR = CR::Ok(()); - fn compile_expression(c: &mut Compiler, t: TreeRef) { let tree = &c.syntax[t]; 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 { - // TODO: Handle load of non-local value. - let index = declaration.index; let instruction = match declaration.location { Location::Local => { @@ -648,7 +676,12 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat Instruction::LoadArgument(index) } Location::Module => { - compiler_assert!(c, t, index < c.module.globals); + 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); + } Instruction::LoadModule(index) } Location::Slot => { @@ -657,32 +690,9 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat } Location::Function => { - // TODO: Assert declaration is local - let Origin::Source(ft) = declaration.origin else { - 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 - ) - } - } - } - }; + if declaration.module != c.semantics.mid() { + c.push(Instruction::ModulePrefix(declaration.module)); + } 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. Location::Class => return OK, - // fix later - Location::Import => ice!(c, t, "import compile not supported"), + // Imports are handled with an instruction prefix. + Location::Import => return OK, }; 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")?; match declaration.location { Location::Class => { - let Origin::Source(classdecl) = declaration.origin else { - 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)); + c.push(Instruction::LoadFunction(declaration.index)); } _ => 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 ident = tree.nth_token(2).ok_or("no ident")?.as_str(&c.source); - let environment = match &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 environment = c.semantics.member_environment(t, &typ); let declaration = environment.bind(ident).ok_or("cannot bind")?; // 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::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::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::LetStatement => compile_let_statement(c, t, tree, gen_value), TreeKind::ReturnStatement => compile_return_statement(c, tree), 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), }; @@ -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 { compile_expression(c, tree.nth_tree(0).ok_or("no expr")?); if !gen_value { @@ -1161,7 +1148,9 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b Instruction::StoreLocal(index) } 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; } Instruction::StoreModule(index) @@ -1176,32 +1165,7 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b OK } -fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, 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)); - } - +fn compile_function_declaration(c: &mut Compiler, gen_value: bool) -> CR { if gen_value { c.push(Instruction::PushNothing); } @@ -1209,26 +1173,7 @@ fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_v OK } -fn compile_class_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, 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)); - } - +fn compile_class_declaration(c: &mut Compiler, gen_value: bool) -> CR { if gen_value { c.push(Instruction::PushNothing); } diff --git a/fine/src/lib.rs b/fine/src/lib.rs index 2a4fc39d..b914ca6e 100644 --- a/fine/src/lib.rs +++ b/fine/src/lib.rs @@ -1,181 +1,51 @@ -use std::{collections::HashMap, fs, path::PathBuf, rc::Rc}; +use std::{path::PathBuf, rc::Rc}; -use compiler::compile; -use parser::parse; -use semantics::{check, Error, ImportRecord, ModuleId, Semantics}; +use compiler::compile_module; +use program::{Module, Program, StandardModuleLoader}; use vm::{eval, Context}; pub mod compiler; pub mod parser; +pub mod program; pub mod semantics; pub mod tokens; pub mod vm; -pub enum ModuleSource { - SourceText(String), -} +fn load_module( + program: &Program, + module: &Rc, + 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)] -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; -} - -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 { - 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, -} - -impl Module { - pub fn id(&self) -> ModuleId { - self.id - } - - pub fn semantics(&self) -> Rc { - self.semantics.clone() - } -} - -pub struct Runtime { - next_module_id: u64, - modules: HashMap>, - loader: Box, -} - -impl Runtime { - pub fn new(loader: Box) -> Self { - Runtime { - next_module_id: 0, - modules: HashMap::new(), - loader, - } - } - - pub fn load_module( - &mut self, - name: &str, - ) -> Result<(Vec>, Rc), 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 = 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 dep in module.deps.iter() { + if let Some(dep_mod) = program.get_module(dep) { + load_module(program, dep_mod, context)?; } } - 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)) + eval(context, module.id, module.init, &[])?; } + + Ok(()) +} + +pub fn compile_program(program: &Program, context: &mut Context) -> Result<(), vm::VMError> { + for module in program.modules() { + load_module(program, module, context)?; + } + + Ok(()) } 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, Err(_) => { 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 { for e in errors { eprintln!("{file}: {}:{}: {}", e.start.0, e.start.1, e.message); @@ -191,16 +61,10 @@ pub fn process_file(file: &str) { return; } - // shrug - let semantics = module.semantics(); - let module = compile(&semantics); - let main_function = module.functions[module.init].clone(); - - let mut context = Context::new(module.clone()); - match eval(&mut context, main_function, vec![]) { - Ok(v) => { - println!("{:?}", v); - } + // This is weird, why run the init function as main? Maybe just run main? + let mut context = Context::new(); + match compile_program(&program, &mut context) { + Ok(_) => {} Err(e) => { eprintln!("{:?}", e); } diff --git a/fine/src/program.rs b/fine/src/program.rs new file mode 100644 index 00000000..48d0a913 --- /dev/null +++ b/fine/src/program.rs @@ -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; +} + +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 { + 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, +} + +impl Module { + pub fn id(&self) -> ModuleId { + self.id + } + + pub fn semantics(&self) -> Rc { + self.semantics.clone() + } +} + +struct PendingModule { + mid: ModuleId, + imports: Vec<(String, String)>, // (raw, normalized) + semantics: Rc, +} + +pub struct Program { + next_module_id: u64, + modules: HashMap>, + modules_by_id: HashMap>, + loader: Box, +} + +impl Program { + pub fn new(loader: Box) -> Self { + Program { + next_module_id: 0, + modules: HashMap::new(), + modules_by_id: HashMap::new(), + loader, + } + } + + pub fn modules(&self) -> impl Iterator> { + self.modules.values() + } + + pub fn get_module(&self, id: &ModuleId) -> Option<&Rc> { + self.modules_by_id.get(id) + } + + pub fn load_module( + &mut self, + name: &str, + ) -> Result<(Vec>, Rc), 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 = 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)) + } +} diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index d65b85c6..976fd202 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -116,6 +116,12 @@ impl From for ModuleId { } } +impl fmt::Display for ModuleId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "#{}", self.0) + } +} + #[derive(Clone, Debug)] pub struct ImportRecord { pub name: String, @@ -344,9 +350,9 @@ pub enum Location { Local, // A local in an frame Slot, // A slot in an object 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) - Class, // A class in a module (index unrelated) + Class, // A class in a module Import, // An import in a module (index unrelated) } @@ -683,6 +689,9 @@ pub struct Semantics { // This is what is used for binding. logical_parents: Vec>, + function_count: usize, + function_indices: Vec>, + // TODO: State should be externalized instead of this refcell nonsense. errors: RefCell>>, types: RefCell>>, @@ -706,6 +715,19 @@ impl Semantics { 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 { mid, file, @@ -714,6 +736,8 @@ impl Semantics { lines, import_map: OnceCell::new(), logical_parents, + function_count, + function_indices, errors: RefCell::new(vec![]), types: 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"); } + pub fn import_ids(&self) -> Vec { + // 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> { // TODO: ACTUALLY THIS IS WRONG, WE NEED THE GLOBAL MAP HERE, NOT THE LOCAL ONE. let import_map = self.import_map.get()?; @@ -797,6 +827,10 @@ impl Semantics { } } + pub fn mid(&self) -> ModuleId { + self.mid + } + fn report_error_span(&self, start_pos: usize, end_pos: usize, error: T) -> Rc where T: ToString, @@ -892,6 +926,27 @@ impl Semantics { Environment::error(error) } + pub fn function_count(&self) -> usize { + self.function_count + } + + pub fn get_function_index(&self, t: TreeRef) -> Option { + 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 { { // 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(), Declaration { location: Location::Function, - index: 0, + index: self.function_index_of(*t), module: self.mid, origin: Origin::Source(*t), exported: false, @@ -1007,7 +1062,7 @@ impl Semantics { let declaration = Declaration { location: Location::Function, - index: 0, + index: self.function_index_of(t), module: self.mid, origin: Origin::Source(t), exported, @@ -1024,7 +1079,7 @@ impl Semantics { let declaration = Declaration { location: Location::Class, - index: 0, + index: self.function_index_of(t), module: self.mid, origin: Origin::Source(t), exported, @@ -1393,7 +1448,7 @@ impl Semantics { (&*method.name).into(), Declaration { location: Location::Function, - index: 0, + index: self.function_index_of(method.declaration), module: self.mid, origin: Origin::Source(method.declaration), exported: false, diff --git a/fine/src/vm.rs b/fine/src/vm.rs index 032a8c7e..d176cba1 100644 --- a/fine/src/vm.rs +++ b/fine/src/vm.rs @@ -1,11 +1,13 @@ +use core::fmt; use std::cell::{Cell, RefCell}; +use std::collections::HashMap; use std::rc::Rc; 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, }; -use crate::semantics::Type; +use crate::semantics::{ModuleId, Type}; use thiserror::Error; #[derive(Error, Debug)] @@ -44,6 +46,13 @@ pub enum VMErrorCode { SlotOutOfRange(usize, Rc, usize), #[error("internal error: the extern function with ID {0} was not registered")] 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)] @@ -80,6 +89,21 @@ pub struct ListIterator { index: Cell, } +#[derive(Clone)] +pub enum FuncValue { + Function(Rc, Rc), + 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)] pub enum StackValue { Nothing, @@ -87,8 +111,7 @@ pub enum StackValue { Float(f64), Int(i64), String(Rc), - Function(Rc), - ExternFunction(usize), + Function(Box), Object(Rc), List(Rc>), ListIterator(Rc), @@ -131,13 +154,9 @@ impl StackValue { } } -enum FuncValue { - Function(Rc), - ExternFunction(usize), -} - #[derive(Debug)] pub struct Frame { + module: Rc, func: Rc, args: Vec, locals: Vec, @@ -146,11 +165,12 @@ pub struct Frame { } impl Frame { - fn from_function(func: Rc, args: Vec) -> Self { + fn from_function(module: Rc, func: Rc, args: Vec) -> Self { let mut locals = Vec::new(); locals.resize(func.locals(), StackValue::Nothing); Frame { + module, func, args, locals, @@ -159,8 +179,12 @@ impl Frame { } } - pub fn func(&self) -> Rc { - self.func.clone() + pub fn module(&self) -> &Rc { + &self.module + } + + pub fn func(&self) -> &Rc { + &self.func } pub fn args(&self) -> &[StackValue] { @@ -230,12 +254,12 @@ impl Frame { self.push_value(StackValue::String(v)) } - fn push_function(&mut self, v: Rc) { - self.push_value(StackValue::Function(v)); + fn push_function(&mut self, m: Rc, v: Rc) { + self.push_value(StackValue::Function(Box::new(FuncValue::Function(m, v)))); } 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) { @@ -292,55 +316,86 @@ impl Frame { fn pop_function(&mut self) -> Result { match self.pop_value()? { - StackValue::Function(f) => Ok(FuncValue::Function(f)), - StackValue::ExternFunction(i) => Ok(FuncValue::ExternFunction(i)), + StackValue::Function(f) => Ok(*f), v => Err(VMErrorCode::StackExpectedFunction(v)), } } } -pub struct Context { - module: Rc, - globals: Vec, +pub struct RuntimeModule { + code: Rc, + globals: RefCell>, } -impl Context { - pub fn new(module: Rc) -> Context { - let mut globals = Vec::new(); - globals.resize(module.globals, StackValue::Nothing); - Context { module, globals } +impl fmt::Debug for RuntimeModule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "#{}", self.code.id) } +} - 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(()) +impl RuntimeModule { + fn new(code: Rc) -> RuntimeModule { + let mut globals = Vec::new(); + globals.resize(code.globals, StackValue::Nothing); + RuntimeModule { + code, + globals: RefCell::new(globals), + } } fn get_global(&self, i: usize) -> Result { self.globals + .borrow() .get(i) .map(|v| v.clone()) .ok_or_else(|| VMErrorCode::GlobalOutOfRange(i)) // TODO: Test } - fn set_global(&mut self, i: usize, v: StackValue) -> Result<()> { - if i >= self.globals.len() { + fn set_global(&self, i: usize, v: StackValue) -> Result<()> { + let mut globals = self.globals.borrow_mut(); + if i >= globals.len() { Err(VMErrorCode::GlobalOutOfRange(i)) // TODO: Test } else { - self.globals[i] = v; + globals[i] = v; Ok(()) } } fn get_function(&self, i: usize) -> Result> { - let functions = self.module.functions(); + let functions = self.code.functions(); functions .get(i) .map(|v| v.clone()) .ok_or_else(|| VMErrorCode::FunctionOutOfRange(i)) // TODO: Test } +} + +pub struct Context { + modules: HashMap>, + module_prefix: Option, +} + +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> { + self.modules.get(&module).map(|rm| &rm.code) + } + + pub fn set_module(&mut self, module: Rc) { + 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 { match index { @@ -393,6 +448,8 @@ fn eval_one( f: &mut Frame, stack: &mut Vec, ) -> Result { + let module_prefix = c.module_prefix.take(); + match instruction { Instruction::Panic(index) => { let v = f @@ -457,7 +514,15 @@ fn eval_one( f.push_value(v); } 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); } Instruction::PushFalse => { @@ -486,7 +551,15 @@ fn eval_one( } Instruction::StoreModule(i) => { 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) => { let o = f.pop_object()?; @@ -494,8 +567,16 @@ fn eval_one( o.values.borrow_mut()[i] = v; } Instruction::LoadFunction(i) => { - let v = c.get_function(i)?; - f.push_function(v); + 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_function(i)?; + f.push_function(module.clone(), v); } Instruction::LoadExternFunction(i) => { f.push_extern_function(i); @@ -507,8 +588,8 @@ fn eval_one( args.push(f.pop_value()?); } match function { - FuncValue::Function(func) => { - let mut frame = Frame::from_function(func, args); + FuncValue::Function(module, func) => { + let mut frame = Frame::from_function(module, func, args); std::mem::swap(&mut frame, f); frame.pc = *index; stack.push(frame); @@ -533,8 +614,8 @@ fn eval_one( let x = f.pop_string()?; let y = f.pop_string()?; - let mut new_string = x.to_string(); - new_string.push_str(&y); + let mut new_string = y.to_string(); + new_string.push_str(&x); f.push_string(new_string.into()); } @@ -624,18 +705,22 @@ fn eval_one( } f.push_list(Rc::new(v)); } + Instruction::ModulePrefix(mid) => { + c.module_prefix = Some(mid); + } } Ok(Flow::Continue) } -pub fn eval( +fn eval_core( c: &mut Context, + module: Rc, function: Rc, args: Vec, ) -> std::result::Result { 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; loop { @@ -680,18 +765,61 @@ pub fn eval( } } +pub fn eval( + c: &mut Context, + module: ModuleId, + function: usize, + args: &[StackValue], +) -> std::result::Result { + 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( c: &mut Context, + module: ModuleId, name: &str, args: &[StackValue], ) -> std::result::Result { - let export = match c.module.exports.get(name) { - Some(Export::Function(id)) => id, - Some(_) => todo!(), - None => todo!(), + let Some(module) = c.modules.get(&module) else { + return Err(VMError { + code: VMErrorCode::ModuleNotFound(module), + stack: Box::new([]), + }); }; - let function = c.module.functions[*export].clone(); + let export = match module.code.exports.get(name) { + Some(Export::Function(id)) => id, + 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(c, function, args) + eval_core(c, module.clone(), function, args) } diff --git a/fine/tests/example_tests.rs b/fine/tests/example_tests.rs index fd829613..eede64c1 100644 --- a/fine/tests/example_tests.rs +++ b/fine/tests/example_tests.rs @@ -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) -> Runtime { - Runtime::new(TestLoader::new(_source_path.into(), source)) +fn test_runtime(_source_path: &str, source: Rc) -> Program { + Program::new(TestLoader::new(_source_path.into(), source)) } -fn assert_type_at(module: Rc, pos: usize, expected: &str, _source_path: &str) { +fn assert_type_at(module: Rc, 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, pos: usize, expected: &str, _source_ } fn assert_type_error_at( - module: Rc, + module: Rc, errors: &[Rc], 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, expected: &str, source_path: &str) { +fn assert_compiles_to(module: Rc, 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, expected: &str, source_path: &st } } -fn assert_no_errors(module: Rc, errors: &[Rc]) { +fn assert_no_errors(module: Rc, errors: &[Rc]) { let semantics = module.semantics(); let expected_errors: &[Rc] = &[]; @@ -299,14 +302,39 @@ fn assert_no_errors(module: Rc, errors: &[Rc]) { ); } -fn assert_eval_ok(module: Rc, expected: &str) { +fn dump_runtime_error(module: &Rc, 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, 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, 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, errors: &[Rc], expected_errors: Vec<&str>) { +fn assert_errors(module: Rc, errors: &[Rc], expected_errors: Vec<&str>) { let semantics = module.semantics(); let errors: Vec = errors.iter().map(|e| format!("{}", e)).collect(); @@ -355,7 +363,7 @@ fn assert_errors(module: Rc, errors: &[Rc], expected_errors ); } -fn assert_check_error(module: Rc, errors: &[Rc], expected: &str) { +fn assert_check_error(module: Rc, errors: &[Rc], expected: &str) { let semantics = module.semantics(); semantic_assert!( diff --git a/fine/tests/expression/argument.fine b/fine/tests/expression/argument.fine index a432d24e..9b7453fe 100644 --- a/fine/tests/expression/argument.fine +++ b/fine/tests/expression/argument.fine @@ -64,11 +64,6 @@ fun test() -> f64 { // | RightBrace:'"}"' // | // @compiles-to: -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | function foo (1 args, 0 locals): // | strings (0): // | code (4): @@ -80,7 +75,12 @@ fun test() -> f64 { // | strings (0): // | code (4): // | 0: PushFloat(1.0) -// | 1: LoadFunction(1) +// | 1: LoadFunction(0) // | 2: Call(1) // | 3: Return +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | diff --git a/fine/tests/expression/arithmetic.fine b/fine/tests/expression/arithmetic.fine index 03639511..0a57ae32 100644 --- a/fine/tests/expression/arithmetic.fine +++ b/fine/tests/expression/arithmetic.fine @@ -40,11 +40,6 @@ fun test() -> f64 { // | RightBrace:'"}"' // | // @compiles-to: -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | function test (0 args, 0 locals): // | strings (0): // | code (10): @@ -58,4 +53,9 @@ fun test() -> f64 { // | 7: FloatMultiply // | 8: FloatAdd // | 9: Return +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | diff --git a/fine/tests/expression/assignment.fine b/fine/tests/expression/assignment.fine index 56497b75..4f822871 100644 --- a/fine/tests/expression/assignment.fine +++ b/fine/tests/expression/assignment.fine @@ -8,11 +8,6 @@ fun test() -> f64 { // @no-errors // @compiles-to: -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | function test (0 args, 3 locals): // | strings (0): // | code (14): @@ -30,5 +25,10 @@ fun test() -> f64 { // | 11: Discard // | 12: LoadLocal(0) // | 13: Return +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | // @eval: Float(2.0) diff --git a/fine/tests/expression/block.fine b/fine/tests/expression/block.fine index c6785fa2..867b2275 100644 --- a/fine/tests/expression/block.fine +++ b/fine/tests/expression/block.fine @@ -4,12 +4,12 @@ fun test() { // @no-errors // @compiles-to: -// | function << module >> (0 args, 0 locals): +// | function test (0 args, 0 locals): // | strings (0): // | code (2): // | 0: PushNothing // | 1: Return -// | function test (0 args, 0 locals): +// | function << module >> (0 args, 0 locals): // | strings (0): // | code (2): // | 0: PushNothing diff --git a/fine/tests/expression/boolean.fine b/fine/tests/expression/boolean.fine index 5ae7741b..01615b02 100644 --- a/fine/tests/expression/boolean.fine +++ b/fine/tests/expression/boolean.fine @@ -4,11 +4,6 @@ fun test() -> bool { // @no-errors // @compiles-to: -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | function test (0 args, 0 locals): // | strings (0): // | code (15): @@ -27,6 +22,11 @@ fun test() -> bool { // | 12: PushTrue // | 13: BoolNot // | 14: Return +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | // @eval: Bool(false) // @type: 15 bool diff --git a/fine/tests/expression/class.fine b/fine/tests/expression/class.fine index 0b04c979..3bf8e056 100644 --- a/fine/tests/expression/class.fine +++ b/fine/tests/expression/class.fine @@ -30,11 +30,26 @@ fun test() -> f64 { // @no-errors // @eval: Float(597.0) // @compiles-to: -// | function << module >> (0 args, 0 locals): +// | function something_static (0 args, 0 locals): // | strings (0): // | code (2): -// | 0: PushNothing +// | 0: PushFloat(12.0) // | 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): // | strings (1): // | 0: "Point" @@ -60,13 +75,13 @@ fun test() -> f64 { // | code (27): // | 0: PushFloat(99.0) // | 1: PushFloat(999.0) -// | 2: LoadFunction(1) +// | 2: LoadFunction(2) // | 3: Call(2) // | 4: PushFloat(23.0) // | 5: PushFloat(7.0) -// | 6: LoadFunction(1) +// | 6: LoadFunction(2) // | 7: Call(2) -// | 8: LoadFunction(2) +// | 8: LoadFunction(3) // | 9: Call(2) // | 10: StoreLocal(0) // | 11: LoadLocal(0) @@ -76,33 +91,18 @@ fun test() -> f64 { // | 15: LoadSlot(0) // | 16: LoadSlot(0) // | 17: LoadLocal(1) -// | 18: LoadFunction(4) +// | 18: LoadFunction(1) // | 19: Call(1) // | 20: FloatAdd -// | 21: LoadFunction(5) +// | 21: LoadFunction(0) // | 22: Call(0) // | 23: FloatAdd // | 24: StoreLocal(2) // | 25: LoadLocal(2) // | 26: 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 something_static (0 args, 0 locals): +// | function << module >> (0 args, 0 locals): // | strings (0): // | code (2): -// | 0: PushFloat(12.0) +// | 0: PushNothing // | 1: Return // | diff --git a/fine/tests/expression/conditional.fine b/fine/tests/expression/conditional.fine index cad9fa10..24c0d09c 100644 --- a/fine/tests/expression/conditional.fine +++ b/fine/tests/expression/conditional.fine @@ -58,11 +58,6 @@ fun test() -> f64 { // | RightBrace:'"}"' // // @compiles-to: -// | function << module >> (0 args, 0 locals): -// | strings (0): -// | code (2): -// | 0: PushNothing -// | 1: Return // | function test (0 args, 0 locals): // | strings (1): // | 0: "discarded" @@ -75,5 +70,10 @@ fun test() -> f64 { // | 5: Jump(7) // | 6: PushFloat(45.0) // | 7: Return +// | function << module >> (0 args, 0 locals): +// | strings (0): +// | code (2): +// | 0: PushNothing +// | 1: Return // | // @eval: Float(23.0) diff --git a/fine/tests/expression/variable.fine b/fine/tests/expression/variable.fine index e7335ac8..34e39dc6 100644 --- a/fine/tests/expression/variable.fine +++ b/fine/tests/expression/variable.fine @@ -64,6 +64,13 @@ fun test() -> f64 { // | RightBrace:'"}"' // | // @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): // | strings (0): // | code (12): @@ -79,11 +86,4 @@ fun test() -> f64 { // | 9: Discard // | 10: PushNothing // | 11: Return -// | function test (0 args, 0 locals): -// | strings (0): -// | code (4): -// | 0: LoadModule(0) -// | 1: LoadModule(1) -// | 2: FloatAdd -// | 3: Return // | diff --git a/fine/tests/modules/import.fine b/fine/tests/modules/import.fine index da98ad17..9b9ec5c0 100644 --- a/fine/tests/modules/import.fine +++ b/fine/tests/modules/import.fine @@ -1,8 +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: String("hello world") \ No newline at end of file