diff --git a/fine/build.rs b/fine/build.rs index 74763b20..1b3c7b1e 100644 --- a/fine/build.rs +++ b/fine/build.rs @@ -139,7 +139,7 @@ fn generate_test_for_file(path: PathBuf) -> String { #disabled fn #name() { let source : std::rc::Rc = #contents.into(); - let mut runtime = crate::test_runtime(source.clone()); + let mut runtime = crate::test_runtime(#display_path, source.clone()); let (_errors, _module) = runtime.load_module("__test__").unwrap(); #(#assertions)* diff --git a/fine/src/compiler.rs b/fine/src/compiler.rs index e20b9042..f71f45ca 100644 --- a/fine/src/compiler.rs +++ b/fine/src/compiler.rs @@ -661,7 +661,8 @@ fn compile_load_declaration(c: &mut Compiler, t: TreeRef, declaration: &Declarat // Must be a static don't worry about it. Declaration::Class { .. } => return OK, - Declaration::Import { .. } => todo!(), + // fix later + Declaration::Import { .. } => ice!(c, t, "import compile not supported"), }; c.push(instruction); @@ -1000,6 +1001,7 @@ fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) { let cr = match tree.kind { TreeKind::Error => None, + 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::ExpressionStatement => compile_expression_statement(c, tree, gen_value), @@ -1288,3 +1290,10 @@ fn compile_for_statement(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR { OK } + +fn compile_import_statement(c: &mut Compiler, gen_value: bool) -> CR { + if gen_value { + c.push(Instruction::PushNothing); + } + OK +} diff --git a/fine/src/lib.rs b/fine/src/lib.rs index dab9bc78..51dfaec4 100644 --- a/fine/src/lib.rs +++ b/fine/src/lib.rs @@ -1,8 +1,8 @@ -use std::{collections::HashMap, fs, rc::Rc}; +use std::{collections::HashMap, fs, path::PathBuf, rc::Rc}; use compiler::compile; use parser::parse; -use semantics::{check, Error, Semantics}; +use semantics::{check, Error, ImportRecord, Semantics}; use vm::{eval, Context}; pub mod compiler; @@ -17,51 +17,69 @@ pub enum ModuleSource { #[derive(Debug)] pub enum ModuleLoadError { - IO(std::io::Error), + IO(String, std::io::Error), } pub trait ModuleLoader { - fn normalize_module_name(&self, name: String) -> String; + fn normalize_module_name(&self, source: &str, name: String) -> String; fn load_module(&self, name: &String) -> Result; } -pub struct StandardModuleLoader {} +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, name: String) -> String { - match std::fs::canonicalize(&name) { + 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(_) => name, + Err(_e) => { + eprintln!("ERROR INTO OS STRING: {}", _e.to_string_lossy()); + name.clone() + } }, - Err(_) => name, - } + Err(_e) => { + eprintln!("ERROR CANONICAL {}: {_e}", p.display()); + name.clone() + } + }; + eprintln!("**** {source} {name} => {result}"); + 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(e)), + Err(e) => Err(ModuleLoadError::IO(name.clone(), e)), } } } pub struct Module { - module: Rc, + id: u64, semantics: Rc, } impl Module { - pub fn semantics(&self) -> Rc { - self.semantics.clone() + pub fn id(&self) -> u64 { + self.id } - pub fn compiled(&self) -> Rc { - self.module.clone() + pub fn semantics(&self) -> Rc { + self.semantics.clone() } } pub struct Runtime { + next_module_id: u64, modules: HashMap>, loader: Box, } @@ -69,6 +87,7 @@ pub struct Runtime { impl Runtime { pub fn new(loader: Box) -> Self { Runtime { + next_module_id: 0, modules: HashMap::new(), loader, } @@ -77,55 +96,72 @@ impl Runtime { 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()); + 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) { + if self.modules.contains_key(&name) || init_pending.contains_key(&name) { + // Either already loaded or pending load. continue; } - if !init_pending.contains_key(&name) { - let loaded = self.loader.load_module(&name)?; - match loaded { - ModuleSource::SourceText(source) => { - let source: Rc = source.into(); - let (tree, lines) = parse(&source); - let semantics = Rc::new(Semantics::new(source, tree, lines)); - let mut normalized = Vec::new(); - for import in semantics.imports() { - let import = self.loader.normalize_module_name(import); - names.push(import.clone()); - normalized.push(import); - } + // TODO: Errors here are bad! Remember, run everything! + match self.loader.load_module(&name)? { + ModuleSource::SourceText(source) => { + let source: Rc = source.into(); + let (tree, lines) = parse(&source); + let semantics = Rc::new(Semantics::new(source, tree, lines)); - init_pending.insert(name, (normalized, semantics)); + 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, (id_assign, normalized_imports, semantics)); + id_assign += 1; } } } - for (_, (imports, semantics)) in init_pending.iter() { + for (_, (_, imports, semantics)) in init_pending.iter() { let mut import_table = HashMap::new(); - for import in imports.iter() { - let target = if let Some(module) = self.modules.get(&*import) { - Rc::downgrade(&module.semantics) + 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 { - Rc::downgrade(&init_pending.get(&*import).unwrap().1) + 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, (_, semantics)) in init_pending.into_iter() { + for (name, (id, _, semantics)) in init_pending.into_iter() { check(&semantics); errors.append(&mut semantics.snapshot_errors()); - let module = compile(&semantics); - self.modules - .insert(name, Rc::new(Module { semantics, module })); + 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)) @@ -133,7 +169,7 @@ impl Runtime { } pub fn process_file(file: &str) { - let mut runtime = Runtime::new(Box::new(StandardModuleLoader {})); + let mut runtime = Runtime::new(Box::new(StandardModuleLoader::new(PathBuf::from(".")))); let (errors, module) = match runtime.load_module(file) { Ok(r) => r, @@ -152,7 +188,8 @@ pub fn process_file(file: &str) { } // shrug - let module = module.module.clone(); + let semantics = module.semantics(); + let module = compile(&semantics); let main_function = module.functions[module.init].clone(); let mut context = Context::new(module.clone()); diff --git a/fine/src/semantics.rs b/fine/src/semantics.rs index d725491b..4ed8009b 100644 --- a/fine/src/semantics.rs +++ b/fine/src/semantics.rs @@ -104,6 +104,13 @@ impl std::ops::Deref for ClassRef { } } +#[derive(Clone, Debug)] +pub struct ImportRecord { + pub name: String, + pub module_id: u64, + pub semantics: Weak, +} + #[derive(Clone)] pub enum Type { // Signals a type error. If you receive this then you know that an error @@ -156,7 +163,7 @@ pub enum Type { Alternate(Box<[Type]>), // A module of some kind. What module? - Module(Rc), + Module(ImportRecord), } impl Type { @@ -251,7 +258,7 @@ impl fmt::Display for Type { } Ok(()) } - Module(name) => write!(f, "module {}", name), + Module(name) => write!(f, "module {}", name.name), } } } @@ -583,7 +590,11 @@ fn set_logical_parents( // Process escapes and convert a string constant in source to a runtime String value. pub fn string_constant_to_string(s: &str) -> String { let mut result = String::new(); - let mut input = s.chars(); + if s.len() <= 2 { + return result; + } + + let mut input = s[1..s.len() - 1].chars(); while let Some(ch) = input.next() { if ch == '\\' { if let Some(ch) = input.next() { @@ -615,7 +626,7 @@ pub struct Semantics { syntax_tree: Rc, lines: Rc, - import_map: OnceCell>>, + import_map: OnceCell>, // Instead of physical parents, this is the set of *logical* parents. // This is what is used for binding. @@ -661,7 +672,7 @@ impl Semantics { semantics } - pub fn set_imports(&self, imports: HashMap>) { + pub fn set_imports(&self, imports: HashMap) { self.import_map.set(imports).expect("imports already set"); } @@ -678,7 +689,22 @@ impl Semantics { } pub fn imports(&self) -> Vec { - vec![] + self.syntax_tree + .root() + .map(|file| { + self.syntax_tree[file] + .children_of_kind(&self.syntax_tree, TreeKind::Import) + .filter_map(|import| { + let tok = self.syntax_tree[import].nth_token(1)?; + if tok.kind != TokenKind::String { + None + } else { + Some(string_constant_to_string(tok.as_str(&self.source))) + } + }) + .collect() + }) + .unwrap_or(Vec::new()) } pub fn snapshot_errors(&self) -> Vec { @@ -2213,7 +2239,20 @@ impl Semantics { if tok.kind != TokenKind::String { return Some(Type::Error); // Already reported as syntax error } - Some(Type::Module(tok.as_str(&self.source).into())) + + // do we bind it here? it's not normalized.... + let Some(import_map) = self.import_map.get() else { + self.internal_compiler_error(None, "import map not initialized"); + }; + + let name = tok.as_str(&self.source); + match import_map.get(name) { + Some(import) => Some(Type::Module(import.clone())), + None => { + self.report_error_tree(tree, format!("unable to resolve module import {name}")); + Some(Type::Error) + } + } } // TODO: Really want to TEST THIS also uh can we generate bytecode for functions and call it?? @@ -2488,7 +2527,7 @@ pub fn check(s: &Semantics) { TreeKind::WhileStatement => check_while_statement(s, tree), TreeKind::Import => { - // TODO: Check Import Statement + let _ = s.type_of(t); } } } diff --git a/fine/tests/example_tests.rs b/fine/tests/example_tests.rs index 91656686..88f2f790 100644 --- a/fine/tests/example_tests.rs +++ b/fine/tests/example_tests.rs @@ -1,10 +1,11 @@ -use fine::compiler::{Function, Module}; +use fine::compiler::{compile, Function, Module}; use fine::semantics::{Error, Type}; use fine::vm::{eval_export_fn, Context}; use fine::{ModuleLoadError, ModuleLoader, ModuleSource, Runtime, StandardModuleLoader}; use pretty_assertions::assert_eq; use std::fmt::Write as _; +use std::path::PathBuf; use std::rc::Rc; fn rebase_section(source_path: &str, section: &str, value: &str) { @@ -135,20 +136,26 @@ struct TestLoader { } impl TestLoader { - fn new(source: Rc) -> Box { + fn new(base_path: PathBuf, source: Rc) -> Box { + let base_path = base_path + .parent() + .map(|p| p.to_owned()) + .unwrap_or(base_path); + Box::new(TestLoader { source, - base: StandardModuleLoader {}, + base: StandardModuleLoader::new(base_path), }) } } impl ModuleLoader for TestLoader { - fn normalize_module_name(&self, name: String) -> String { + fn normalize_module_name(&self, base: &str, name: String) -> String { if name == "__test__" { name } else { - self.base.normalize_module_name(name) + let base = if base == "__test__" { "" } else { base }; + self.base.normalize_module_name(base, name) } } @@ -161,8 +168,8 @@ impl ModuleLoader for TestLoader { } } -fn test_runtime(source: Rc) -> Runtime { - Runtime::new(TestLoader::new(source)) +fn test_runtime(_source_path: &str, source: Rc) -> Runtime { + Runtime::new(TestLoader::new(_source_path.into(), source)) } fn assert_type_at(module: Rc, pos: usize, expected: &str, _source_path: &str) { @@ -237,7 +244,7 @@ fn dump_function(out: &mut String, function: &Function) -> std::fmt::Result { let strings = function.strings(); writeln!(out, " strings ({}):", strings.len())?; for (i, s) in strings.iter().enumerate() { - writeln!(out, " {}: {}", i, s)?; // TODO: ESCAPE + writeln!(out, " {}: \"{}\"", i, s)?; // TODO: ESCAPE } let code = function.instructions(); @@ -259,7 +266,7 @@ fn dump_module(out: &mut String, module: &Module) -> std::fmt::Result { fn assert_compiles_to(module: Rc, expected: &str, source_path: &str) { let semantics = module.semantics(); - let module = module.compiled(); + let module = compile(&semantics); let mut actual = String::new(); dump_module(&mut actual, &module).expect("no dumping?"); @@ -294,7 +301,7 @@ fn assert_no_errors(module: Rc, errors: &[Error]) { fn assert_eval_ok(module: Rc, expected: &str) { let semantics = module.semantics(); - let module = module.compiled(); + let module = compile(&semantics); let mut context = Context::new(module.clone()); context.init().expect("Unable to initialize module"); diff --git a/fine/tests/expression/class.fine b/fine/tests/expression/class.fine index 01989feb..0b04c979 100644 --- a/fine/tests/expression/class.fine +++ b/fine/tests/expression/class.fine @@ -37,7 +37,7 @@ fun test() -> f64 { // | 1: Return // | function Point (6 args, 0 locals): // | strings (1): -// | 0: Point +// | 0: "Point" // | code (6): // | 0: LoadArgument(1) // | 1: LoadArgument(0) @@ -47,7 +47,7 @@ fun test() -> f64 { // | 5: Return // | function Line (4 args, 0 locals): // | strings (1): -// | 0: Line +// | 0: "Line" // | code (6): // | 0: LoadArgument(1) // | 1: LoadArgument(0) diff --git a/fine/tests/modules/foo.fine b/fine/tests/modules/foo.fine index c7193c90..475086c2 100644 --- a/fine/tests/modules/foo.fine +++ b/fine/tests/modules/foo.fine @@ -1,3 +1,3 @@ export fun hello() -> string { "hello" -} \ No newline at end of file +} diff --git a/fine/tests/modules/import.fine b/fine/tests/modules/import.fine index 94698309..b4e509cd 100644 --- a/fine/tests/modules/import.fine +++ b/fine/tests/modules/import.fine @@ -1,4 +1,4 @@ -import "./foo" as foo; +import "./foo.fine" as foo; fun test() -> string { foo.hello() + " world"