diff --git a/src/fun/load_book.rs b/src/fun/load_book.rs index cf9184309..4e94a1fb8 100644 --- a/src/fun/load_book.rs +++ b/src/fun/load_book.rs @@ -6,7 +6,7 @@ use crate::{ diagnostics::{Diagnostics, DiagnosticsConfig}, imports::PackageLoader, }; -use std::{fmt::Display, path::Path}; +use std::path::Path; // TODO: Refactor so that we don't mix the two syntaxes here. @@ -17,11 +17,11 @@ pub fn load_file_to_book( diag: DiagnosticsConfig, ) -> Result { let code = std::fs::read_to_string(path).map_err(|e| e.to_string())?; - load_to_book(path.display(), &code, package_loader, diag) + load_to_book(path, &code, package_loader, diag) } -pub fn load_to_book( - origin: T, +pub fn load_to_book( + origin: &Path, code: &str, package_loader: impl PackageLoader, diag: DiagnosticsConfig, @@ -31,11 +31,11 @@ pub fn load_to_book( book.load_imports(package_loader, diag) } -pub fn do_parse_book(code: &str, origin: T, mut book: ParseBook) -> Result { - book.source = Name::new(format!("{origin}")); - TermParser::new(code).parse_book(book, false).map_err(|e| format!("In {} :\n{}", origin, e)) +pub fn do_parse_book(code: &str, origin: &Path, mut book: ParseBook) -> Result { + book.source = Name::new(origin.to_string_lossy()); + TermParser::new(code).parse_book(book, false).map_err(|e| format!("In {} :\n{}", origin.display(), e)) } -pub fn do_parse_book_default(code: &str, origin: T) -> Result { +pub fn do_parse_book_default(code: &str, origin: &Path) -> Result { do_parse_book(code, origin, ParseBook::builtins())?.to_fun() } diff --git a/src/imports/loader.rs b/src/imports/loader.rs index 7e8f110cf..37c071ff8 100644 --- a/src/imports/loader.rs +++ b/src/imports/loader.rs @@ -3,7 +3,7 @@ use crate::fun::Name; use indexmap::IndexMap; use std::{ collections::HashSet, - path::{Path, PathBuf}, + path::{Component, Path, PathBuf}, }; pub type Sources = IndexMap; @@ -40,23 +40,33 @@ pub trait PackageLoader { pub struct DefaultLoader { local_path: PathBuf, loaded: HashSet, + entrypoint: Name, } impl DefaultLoader { pub fn new(local_path: &Path) -> Self { + let entrypoint = Name::new(local_path.file_stem().unwrap().to_string_lossy()); let local_path = local_path.parent().unwrap().to_path_buf(); - Self { local_path, loaded: HashSet::new() } + Self { local_path, loaded: HashSet::new(), entrypoint } } - fn read_file(&mut self, path: &Path, file: Name, src: &mut Sources) -> Option { - if !self.is_loaded(&file) { - self.loaded.insert(file.clone()); + fn read_file(&mut self, path: &Path, file_path: &str, src: &mut Sources) -> Result, String> { + let normalized = normalize_path(&PathBuf::from(file_path)); + let file_path = Name::new(normalized.to_string_lossy()); + + if self.entrypoint == file_path { + return Err("Can not import the entry point of the program.".to_string()); + }; + + if !self.is_loaded(&file_path) { + self.loaded.insert(file_path.clone()); let path = path.with_extension("bend"); - let code = std::fs::read_to_string(path).ok()?; - src.insert(file.clone(), code); + let Some(code) = std::fs::read_to_string(path).ok() else { return Ok(None) }; + src.insert(file_path.clone(), code); } - Some(file) + + Ok(Some(file_path)) } fn read_file_in_folder( @@ -65,11 +75,15 @@ impl DefaultLoader { folder: &str, file_name: &str, src: &mut Sources, - ) -> Option { - let file_path = Name::new(format!("{}/{}", folder, file_name)); + ) -> Result, String> { let full_path = full_path.join(file_name); - self.read_file(&full_path, file_path, src) + if folder.is_empty() { + self.read_file(&full_path, file_name, src) + } else { + let file_name = &format!("{}/{}", folder, file_name); + self.read_file(&full_path, file_name, src) + } } fn read_path( @@ -77,12 +91,12 @@ impl DefaultLoader { base_path: &Path, path: &Name, imp_type: &ImportType, - ) -> Option<(BoundSource, Sources)> { + ) -> Result, String> { let full_path = base_path.join(path.as_ref()); let mut src = IndexMap::new(); let file = if full_path.with_extension("bend").is_file() { - self.read_file(&full_path, path.clone(), &mut src) + self.read_file(&full_path, path.as_ref(), &mut src)? } else { None }; @@ -92,13 +106,13 @@ impl DefaultLoader { match imp_type { ImportType::Single(file, _) => { - if let Some(name) = self.read_file_in_folder(&full_path, path, file, &mut src) { + if let Some(name) = self.read_file_in_folder(&full_path, path, file, &mut src)? { names.insert(file.clone(), name); } } ImportType::List(list) => { for (file, _) in list { - if let Some(name) = self.read_file_in_folder(&full_path, path, file, &mut src) { + if let Some(name) = self.read_file_in_folder(&full_path, path, file, &mut src)? { names.insert(file.clone(), name); } } @@ -109,7 +123,7 @@ impl DefaultLoader { if let Some("bend") = file.extension().and_then(|f| f.to_str()) { let file = file.file_stem().unwrap().to_string_lossy(); - if let Some(name) = self.read_file_in_folder(&full_path, path, &file, &mut src) { + if let Some(name) = self.read_file_in_folder(&full_path, path, &file, &mut src)? { names.insert(Name::new(file), name); } } @@ -126,12 +140,14 @@ impl DefaultLoader { None }; - match (file, dir) { + let src = match (file, dir) { (Some(f), None) => Some((BoundSource::File(f), src)), (None, Some(d)) => Some((BoundSource::Dir(d), src)), (Some(f), Some(d)) => Some((BoundSource::Either(f, d), src)), (None, None) => None, - } + }; + + Ok(src) } fn is_loaded(&self, name: &Name) -> bool { @@ -154,7 +170,7 @@ impl PackageLoader for DefaultLoader { }; for base in folders { - let Some((names, new_pkgs)) = self.read_path(&base, path, imp_type) else { continue }; + let Some((names, new_pkgs)) = self.read_path(&base, path, imp_type)? else { continue }; *src = names; sources.extend(new_pkgs); @@ -168,3 +184,31 @@ impl PackageLoader for DefaultLoader { Ok(sources) } } + +// Taken from 'cargo/util/paths.rs' +pub fn normalize_path(path: &Path) -> PathBuf { + let mut components = path.components().peekable(); + let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { + components.next(); + PathBuf::from(c.as_os_str()) + } else { + PathBuf::new() + }; + + for component in components { + match component { + Component::Prefix(..) => unreachable!(), + Component::RootDir => { + ret.push(component.as_os_str()); + } + Component::CurDir => {} + Component::ParentDir => { + ret.pop(); + } + Component::Normal(c) => { + ret.push(c); + } + } + } + ret +} diff --git a/src/imports/packages.rs b/src/imports/packages.rs index 380fb7a06..3e75c3919 100644 --- a/src/imports/packages.rs +++ b/src/imports/packages.rs @@ -1,14 +1,10 @@ -use super::{loader::PackageLoader, BoundSource, ImportCtx, ImportType, ImportsMap}; +use super::{loader::PackageLoader, normalize_path, BoundSource, ImportCtx, ImportType, ImportsMap}; use crate::{ diagnostics::Diagnostics, fun::{load_book::do_parse_book, parser::ParseBook, Name}, }; use indexmap::{IndexMap, IndexSet}; -use std::{ - cell::RefCell, - collections::VecDeque, - path::{Component, Path, PathBuf}, -}; +use std::{cell::RefCell, collections::VecDeque, path::PathBuf}; #[derive(Default)] pub struct Packages { @@ -23,7 +19,7 @@ pub struct Packages { impl Packages { pub fn new(book: ParseBook) -> Self { Self { - books: IndexMap::from([(Name::default(), book.into())]), + books: IndexMap::from([(book.source.clone(), book.into())]), load_queue: VecDeque::new(), loaded_adts: IndexMap::new(), } @@ -84,7 +80,7 @@ impl Packages { } for (psrc, code) in sources { - let module = do_parse_book(&code, &psrc, ParseBook::default())?; + let module = do_parse_book(&code, &PathBuf::from(psrc.as_ref()), ParseBook::default())?; self.load_queue.push_back(self.books.len()); self.books.insert(psrc, module.into()); } @@ -239,31 +235,3 @@ impl Packages { bound_book.top_level_names().cloned().collect() } } - -// Taken from 'cargo/util/paths.rs' -fn normalize_path(path: &Path) -> PathBuf { - let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { - components.next(); - PathBuf::from(c.as_os_str()) - } else { - PathBuf::new() - }; - - for component in components { - match component { - Component::Prefix(..) => unreachable!(), - Component::RootDir => { - ret.push(component.as_os_str()); - } - Component::CurDir => {} - Component::ParentDir => { - ret.pop(); - } - Component::Normal(c) => { - ret.push(c); - } - } - } - ret -} diff --git a/tests/golden_tests.rs b/tests/golden_tests.rs index 21f2bf70c..495fa6b02 100644 --- a/tests/golden_tests.rs +++ b/tests/golden_tests.rs @@ -107,7 +107,7 @@ pub fn run_book_simple( #[test] fn compile_file() { run_golden_test_dir(function_name!(), &|code, path| { - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; let compile_opts = CompileOpts::default(); let diagnostics_cfg = DiagnosticsConfig { unused_definition: Severity::Allow, ..Default::default() }; @@ -119,7 +119,7 @@ fn compile_file() { #[test] fn compile_file_o_all() { run_golden_test_dir(function_name!(), &|code, path| { - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; let opts = CompileOpts::default().set_all(); let diagnostics_cfg = DiagnosticsConfig { recursion_cycle: Severity::Warning, @@ -135,7 +135,7 @@ fn compile_file_o_all() { #[test] fn compile_file_o_no_all() { run_golden_test_dir(function_name!(), &|code, path| { - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; let compile_opts = CompileOpts::default().set_no_all(); let diagnostics_cfg = DiagnosticsConfig::default(); let res = compile_book(&mut book, compile_opts, diagnostics_cfg, None)?; @@ -147,7 +147,7 @@ fn compile_file_o_no_all() { fn linear_readback() { run_golden_test_dir(function_name!(), &|code, path| { let _guard = RUN_MUTEX.lock().unwrap(); - let book = do_parse_book_default(code, path.display())?; + let book = do_parse_book_default(code, path)?; let compile_opts = CompileOpts::default().set_all(); let diagnostics_cfg = DiagnosticsConfig::default(); let (term, _, diags) = run_book_simple( @@ -168,7 +168,7 @@ fn run_file() { function_name!(), &[(&|code, path| { let _guard = RUN_MUTEX.lock().unwrap(); - let book = do_parse_book_default(code, path.display())?; + let book = do_parse_book_default(code, path)?; let diagnostics_cfg = DiagnosticsConfig { unused_definition: Severity::Allow, ..DiagnosticsConfig::new(Severity::Error, true) @@ -199,7 +199,7 @@ fn import_system() { ..DiagnosticsConfig::new(Severity::Error, true) }; - let book = load_to_book(path.display(), code, DefaultLoader::new(path), diagnostics_cfg)?; + let book = load_to_book(path, code, DefaultLoader::new(path), diagnostics_cfg)?; let run_opts = RunOpts::default(); let mut res = String::new(); @@ -250,7 +250,7 @@ fn readback_hvm() { fn simplify_matches() { run_golden_test_dir(function_name!(), &|code, path| { let diagnostics_cfg = DiagnosticsConfig::new(Severity::Error, true); - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; let mut ctx = Ctx::new(&mut book, diagnostics_cfg); ctx.check_shared_names(); @@ -283,7 +283,7 @@ fn simplify_matches() { #[test] fn parse_file() { run_golden_test_dir(function_name!(), &|code, path| { - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; let mut ctx = Ctx::new(&mut book, Default::default()); ctx.set_entrypoint(); ctx.book.encode_adts(AdtEncoding::NumScott); @@ -301,7 +301,7 @@ fn encode_pattern_match() { let mut result = String::new(); for adt_encoding in [AdtEncoding::Scott, AdtEncoding::NumScott] { let diagnostics_cfg = DiagnosticsConfig::default(); - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; let mut ctx = Ctx::new(&mut book, diagnostics_cfg); ctx.check_shared_names(); ctx.set_entrypoint(); @@ -342,7 +342,7 @@ fn desugar_file() { unused_definition: Severity::Allow, ..DiagnosticsConfig::new(Severity::Error, true) }; - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; desugar_book(&mut book, compile_opts, diagnostics_cfg, None)?; Ok(book.to_string()) }) @@ -355,7 +355,7 @@ fn hangs() { run_golden_test_dir(function_name!(), &move |code, path| { let _guard = RUN_MUTEX.lock().unwrap(); - let book = do_parse_book_default(code, path.display())?; + let book = do_parse_book_default(code, path)?; let compile_opts = CompileOpts::default().set_all(); let diagnostics_cfg = DiagnosticsConfig::new(Severity::Allow, false); @@ -377,7 +377,7 @@ fn hangs() { #[test] fn compile_entrypoint() { run_golden_test_dir(function_name!(), &|code, path| { - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; book.entrypoint = Some(Name::new("foo")); let diagnostics_cfg = DiagnosticsConfig { ..DiagnosticsConfig::new(Severity::Error, true) }; let res = compile_book(&mut book, CompileOpts::default(), diagnostics_cfg, None)?; @@ -390,7 +390,7 @@ fn compile_entrypoint() { fn run_entrypoint() { run_golden_test_dir(function_name!(), &|code, path| { let _guard = RUN_MUTEX.lock().unwrap(); - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; book.entrypoint = Some(Name::new("foo")); let compile_opts = CompileOpts::default().set_all(); let diagnostics_cfg = DiagnosticsConfig { ..DiagnosticsConfig::new(Severity::Error, true) }; @@ -425,7 +425,7 @@ fn mutual_recursion() { run_golden_test_dir(function_name!(), &|code, path| { let diagnostics_cfg = DiagnosticsConfig { recursion_cycle: Severity::Error, ..DiagnosticsConfig::new(Severity::Allow, true) }; - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; let opts = CompileOpts { merge: true, ..CompileOpts::default() }; let res = compile_book(&mut book, opts, diagnostics_cfg, None)?; Ok(format!("{}{}", res.diagnostics, hvm_book_show_pretty(&res.hvm_book))) @@ -452,7 +452,7 @@ fn io() { }), */ (&|code, path| { let _guard = RUN_MUTEX.lock().unwrap(); - let book = do_parse_book_default(code, path.display())?; + let book = do_parse_book_default(code, path)?; let compile_opts = CompileOpts::default(); let diagnostics_cfg = DiagnosticsConfig::default(); let (term, _, diags) = @@ -479,7 +479,7 @@ fn examples() -> Result<(), Diagnostics> { eprintln!("Testing {}", path.display()); let code = std::fs::read_to_string(path).map_err(|e| e.to_string())?; - let book = do_parse_book_default(&code, path.display()).unwrap(); + let book = do_parse_book_default(&code, path).unwrap(); let compile_opts = CompileOpts::default(); let diagnostics_cfg = DiagnosticsConfig::default(); let (term, _, diags) = run_book_simple(book, RunOpts::default(), compile_opts, diagnostics_cfg, None)?; @@ -501,7 +501,7 @@ fn examples() -> Result<(), Diagnostics> { #[test] fn scott_triggers_unused() { run_golden_test_dir(function_name!(), &|code, path| { - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; let opts = CompileOpts::default(); let diagnostics_cfg = DiagnosticsConfig { unused_definition: Severity::Error, ..DiagnosticsConfig::default() }; @@ -514,7 +514,7 @@ fn scott_triggers_unused() { #[test] fn compile_long() { run_golden_test_dir(function_name!(), &|code, path| { - let mut book = do_parse_book_default(code, path.display())?; + let mut book = do_parse_book_default(code, path)?; let opts = CompileOpts::default().set_all(); let diagnostics_cfg = DiagnosticsConfig { recursion_cycle: Severity::Warning, diff --git a/tests/golden_tests/import_system/import_main.bend b/tests/golden_tests/import_system/import_main.bend new file mode 100644 index 000000000..b6e8cbb72 --- /dev/null +++ b/tests/golden_tests/import_system/import_main.bend @@ -0,0 +1,4 @@ +import lib/import_entry + +def main(): + return * \ No newline at end of file diff --git a/tests/golden_tests/import_system/import_main2.bend b/tests/golden_tests/import_system/import_main2.bend new file mode 100644 index 000000000..4aaecbcbf --- /dev/null +++ b/tests/golden_tests/import_system/import_main2.bend @@ -0,0 +1,4 @@ +import lib/import_entry2 + +def main(): + return * \ No newline at end of file diff --git a/tests/golden_tests/import_system/import_main3.bend b/tests/golden_tests/import_system/import_main3.bend new file mode 100644 index 000000000..3b3feb2ed --- /dev/null +++ b/tests/golden_tests/import_system/import_main3.bend @@ -0,0 +1,4 @@ +import lib/folder/import_entry3 + +def main(): + return * \ No newline at end of file diff --git a/tests/golden_tests/import_system/lib/folder/import_entry3.bend b/tests/golden_tests/import_system/lib/folder/import_entry3.bend new file mode 100644 index 000000000..af3ae16b2 --- /dev/null +++ b/tests/golden_tests/import_system/lib/folder/import_entry3.bend @@ -0,0 +1 @@ +import ../../../../import_main3 # multiples ../ have no effect past the main file folder \ No newline at end of file diff --git a/tests/golden_tests/import_system/lib/import_entry.bend b/tests/golden_tests/import_system/lib/import_entry.bend new file mode 100644 index 000000000..f24cfc6c5 --- /dev/null +++ b/tests/golden_tests/import_system/lib/import_entry.bend @@ -0,0 +1 @@ +import import_main \ No newline at end of file diff --git a/tests/golden_tests/import_system/lib/import_entry2.bend b/tests/golden_tests/import_system/lib/import_entry2.bend new file mode 100644 index 000000000..4de1224ff --- /dev/null +++ b/tests/golden_tests/import_system/lib/import_entry2.bend @@ -0,0 +1 @@ +import ../import_main2 \ No newline at end of file diff --git a/tests/snapshots/import_system__import_main.bend.snap b/tests/snapshots/import_system__import_main.bend.snap new file mode 100644 index 000000000..c058f3a3d --- /dev/null +++ b/tests/snapshots/import_system__import_main.bend.snap @@ -0,0 +1,6 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/import_system/import_main.bend +--- +Errors: +Can not import the entry point of the program. diff --git a/tests/snapshots/import_system__import_main2.bend.snap b/tests/snapshots/import_system__import_main2.bend.snap new file mode 100644 index 000000000..698bbe8f3 --- /dev/null +++ b/tests/snapshots/import_system__import_main2.bend.snap @@ -0,0 +1,6 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/import_system/import_main2.bend +--- +Errors: +Can not import the entry point of the program. diff --git a/tests/snapshots/import_system__import_main3.bend.snap b/tests/snapshots/import_system__import_main3.bend.snap new file mode 100644 index 000000000..f00d33137 --- /dev/null +++ b/tests/snapshots/import_system__import_main3.bend.snap @@ -0,0 +1,6 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/import_system/import_main3.bend +--- +Errors: +Can not import the entry point of the program.