feat: Enhance #use directive for directories and cwd
The `#use` directive now supports referencing entire directories, automatically including all `.myc` files within them. Additionally, environments are now initialized with the current working directory as a default search path, simplifying module resolution.
This commit is contained in:
+73
-35
@@ -173,6 +173,15 @@ impl Environment {
|
||||
env
|
||||
}
|
||||
|
||||
/// Adds the current working directory to the global search paths.
|
||||
/// This is a builder method to easily initialize an environment for file-based usage.
|
||||
pub fn with_cwd(self) -> Self {
|
||||
if let Ok(cwd) = std::env::current_dir() {
|
||||
self.add_search_path(cwd);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_search_path(&self, path: impl AsRef<Path>) {
|
||||
self.search_paths.borrow_mut().push(path.as_ref().to_path_buf());
|
||||
}
|
||||
@@ -215,27 +224,54 @@ impl Environment {
|
||||
}
|
||||
|
||||
/// Resolves a #use module path relative to a base path, then falls back to search paths.
|
||||
fn resolve_module_path(&self, module_path: &str, base_path: &Path) -> Result<PathBuf, String> {
|
||||
/// Returns a list of all matching .myc files (single file, or all files in a directory).
|
||||
fn resolve_module_paths(&self, module_path: &str, base_path: &Path) -> Result<Vec<PathBuf>, String> {
|
||||
let relative_path = module_path.replace("->", std::path::MAIN_SEPARATOR_STR);
|
||||
let file_name = format!("{}.myc", relative_path);
|
||||
|
||||
let check_path = |base: &Path| -> Option<Vec<PathBuf>> {
|
||||
// Check for file first
|
||||
let mut file_p = base.to_path_buf();
|
||||
file_p.push(&file_name);
|
||||
if file_p.is_file() && let Ok(canon) = file_p.canonicalize() {
|
||||
return Some(vec![canon]);
|
||||
}
|
||||
|
||||
// Check for directory
|
||||
let mut dir_p = base.to_path_buf();
|
||||
dir_p.push(&relative_path);
|
||||
if dir_p.is_dir() {
|
||||
let mut files = Vec::new();
|
||||
if let Ok(entries) = std::fs::read_dir(&dir_p) {
|
||||
let mut valid_entries: Vec<_> = entries.filter_map(|e| e.ok()).collect();
|
||||
valid_entries.sort_by_key(|e| e.path());
|
||||
for entry in valid_entries {
|
||||
let p = entry.path();
|
||||
if p.is_file() && p.extension().is_some_and(|ext| ext == "myc") && let Ok(canon) = p.canonicalize() {
|
||||
files.push(canon);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !files.is_empty() {
|
||||
return Some(files);
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
// 1. Try relative to base_path
|
||||
let mut path = base_path.to_path_buf();
|
||||
path.push(&file_name);
|
||||
if path.is_file() {
|
||||
return path.canonicalize().map_err(|e| format!("IO Error: {}", e));
|
||||
if let Some(paths) = check_path(base_path) {
|
||||
return Ok(paths);
|
||||
}
|
||||
|
||||
// 2. Try search paths
|
||||
for sp in self.search_paths.borrow().iter() {
|
||||
let mut path = sp.clone();
|
||||
path.push(&file_name);
|
||||
if path.is_file() {
|
||||
return path.canonicalize().map_err(|e| format!("IO Error: {}", e));
|
||||
if let Some(paths) = check_path(sp) {
|
||||
return Ok(paths);
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!("Could not find module '{}' (looked for {})", module_path, file_name))
|
||||
Err(format!("Could not find module or directory '{}'", module_path))
|
||||
}
|
||||
|
||||
/// Recursively collects all dependencies starting from a given source string.
|
||||
@@ -248,33 +284,35 @@ impl Environment {
|
||||
let directives = self.extract_use_directives(source);
|
||||
|
||||
for module_path in directives {
|
||||
let abs_path = self.resolve_module_path(&module_path, base_path)?;
|
||||
let abs_paths = self.resolve_module_paths(&module_path, base_path)?;
|
||||
|
||||
if self.loaded_modules.borrow().contains(&abs_path) {
|
||||
continue;
|
||||
for abs_path in abs_paths {
|
||||
if self.loaded_modules.borrow().contains(&abs_path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.loaded_modules.borrow_mut().insert(abs_path.clone());
|
||||
|
||||
let lib_source = std::fs::read_to_string(&abs_path)
|
||||
.map_err(|e| format!("Failed to read module {:?}: {}", abs_path, e))?;
|
||||
|
||||
// Pre-process dependencies of the library BEFORE adding it, ensuring topological order
|
||||
let lib_base = abs_path.parent().unwrap_or(Path::new("."));
|
||||
self.collect_dependencies(&lib_source, lib_base, all_files)?;
|
||||
|
||||
let mut parser = Parser::new(&lib_source);
|
||||
let untyped_ast = parser.parse_expression();
|
||||
|
||||
if parser.diagnostics.has_errors() {
|
||||
return Err(format!(
|
||||
"Parser error in module {:?}:\n{}",
|
||||
abs_path,
|
||||
parser.diagnostics.items[0].message
|
||||
));
|
||||
}
|
||||
|
||||
all_files.push((abs_path, untyped_ast));
|
||||
}
|
||||
|
||||
self.loaded_modules.borrow_mut().insert(abs_path.clone());
|
||||
|
||||
let lib_source = std::fs::read_to_string(&abs_path)
|
||||
.map_err(|e| format!("Failed to read module {:?}: {}", abs_path, e))?;
|
||||
|
||||
// Pre-process dependencies of the library BEFORE adding it, ensuring topological order
|
||||
let lib_base = abs_path.parent().unwrap_or(Path::new("."));
|
||||
self.collect_dependencies(&lib_source, lib_base, all_files)?;
|
||||
|
||||
let mut parser = Parser::new(&lib_source);
|
||||
let untyped_ast = parser.parse_expression();
|
||||
|
||||
if parser.diagnostics.has_errors() {
|
||||
return Err(format!(
|
||||
"Parser error in module {:?}:\n{}",
|
||||
abs_path,
|
||||
parser.diagnostics.items[0].message
|
||||
));
|
||||
}
|
||||
|
||||
all_files.push((abs_path, untyped_ast));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user