use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};
use crate::ast::{ImportDecl, ImportSpecifier, Program, Stmt, StmtKind};
use crate::lexer::Scanner;
use crate::parser::Parser;
use super::package::Package;
#[derive(Debug, Clone)]
pub struct ResolvedModule {
pub path: PathBuf,
pub original_program: Program,
pub program: Program,
pub exports: HashSet<String>,
}
#[derive(Debug)]
pub enum ResolveError {
NotFound(String),
CircularDependency(Vec<String>),
ImportError(String),
ParseError(String),
IoError(std::io::Error),
}
impl std::fmt::Display for ResolveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResolveError::NotFound(path) => write!(f, "Module not found: {}", path),
ResolveError::CircularDependency(cycle) => {
write!(f, "Circular dependency: {}", cycle.join(" -> "))
}
ResolveError::ImportError(msg) => write!(f, "Import error: {}", msg),
ResolveError::ParseError(msg) => write!(f, "Parse error: {}", msg),
ResolveError::IoError(e) => write!(f, "IO error: {}", e),
}
}
}
impl std::error::Error for ResolveError {}
impl From<std::io::Error> for ResolveError {
fn from(e: std::io::Error) -> Self {
ResolveError::IoError(e)
}
}
pub struct ModuleResolver {
base_dir: PathBuf,
package: Option<Package>,
cache: HashMap<PathBuf, ResolvedModule>,
resolving: Vec<PathBuf>,
}
impl ModuleResolver {
pub fn new(base_dir: &Path) -> Self {
let package = Package::find(base_dir).and_then(|p| Package::load(&p).ok());
ModuleResolver {
base_dir: base_dir.to_path_buf(),
package,
cache: HashMap::new(),
resolving: Vec::new(),
}
}
pub fn with_package(base_dir: &Path, package: Package) -> Self {
ModuleResolver {
base_dir: base_dir.to_path_buf(),
package: Some(package),
cache: HashMap::new(),
resolving: Vec::new(),
}
}
pub fn resolve(
&mut self,
program: Program,
source_path: &Path,
) -> Result<Program, ResolveError> {
let canonical = self.canonicalize(source_path)?;
let mut combined_statements = Vec::new();
for stmt in &program.statements {
if let StmtKind::Import(import) = &stmt.kind {
let module = self.resolve_import(import, &canonical)?;
let imported_stmts = get_imported_statements(&module, import)?;
combined_statements.extend(imported_stmts);
}
}
for stmt in program.statements {
match &stmt.kind {
StmtKind::Import(_) => {
}
StmtKind::Export(inner) => {
combined_statements.push((**inner).clone());
}
_ => {
combined_statements.push(stmt);
}
}
}
Ok(Program::new(combined_statements))
}
fn resolve_import(
&mut self,
import: &ImportDecl,
from_path: &Path,
) -> Result<ResolvedModule, ResolveError> {
let module_path = self.resolve_path(&import.path, from_path)?;
if self.resolving.contains(&module_path) {
let cycle: Vec<String> = self
.resolving
.iter()
.map(|p| p.display().to_string())
.chain(std::iter::once(module_path.display().to_string()))
.collect();
return Err(ResolveError::CircularDependency(cycle));
}
if let Some(cached) = self.cache.get(&module_path) {
return Ok(cached.clone());
}
let content = fs::read_to_string(&module_path)?;
let tokens = Scanner::new(&content).scan_tokens().map_err(|e| {
ResolveError::ParseError(format!("in {}: {}", module_path.display(), e))
})?;
let program = match Parser::new(tokens).parse() {
Ok(p) => p,
Err(e) => {
return Err(ResolveError::ParseError(format!(
"in {}: {}",
module_path.display(),
e
)))
}
};
self.resolving.push(module_path.clone());
let resolved_program = self.resolve(program.clone(), &module_path)?;
self.resolving.pop();
let exports = collect_exports(&program);
let module = ResolvedModule {
path: module_path.clone(),
original_program: program,
program: resolved_program,
exports,
};
self.cache.insert(module_path, module.clone());
Ok(module)
}
fn resolve_path(&self, import_path: &str, from_path: &Path) -> Result<PathBuf, ResolveError> {
if import_path.starts_with('.') {
let from_dir = from_path.parent().unwrap_or(Path::new("."));
let resolved = from_dir.join(import_path);
return self.find_module_file(&resolved);
}
if let Some(ref pkg) = self.package {
let parts: Vec<&str> = import_path.split('/').collect();
if let Some(dep) = pkg.dependencies.get(parts[0]) {
match dep {
super::package::Dependency::Path(dep_path) => {
let mut resolved = self.base_dir.join(dep_path);
for part in &parts[1..] {
resolved = resolved.join(part);
}
return self.find_module_file(&resolved);
}
super::package::Dependency::Version(_) => {
return Err(ResolveError::NotFound(format!(
"Version-based dependencies not yet supported: {}",
import_path
)));
}
}
}
}
let resolved = self.base_dir.join(import_path);
self.find_module_file(&resolved)
}
fn find_module_file(&self, path: &Path) -> Result<PathBuf, ResolveError> {
let normalized = normalize_path(path);
if normalized.exists() && normalized.is_file() {
return self.canonicalize(&normalized);
}
let with_ext = normalized.with_extension("sl");
if with_ext.exists() && with_ext.is_file() {
return self.canonicalize(&with_ext);
}
let index = normalized.join("index.sl");
if index.exists() && index.is_file() {
return self.canonicalize(&index);
}
let mod_file = normalized.join("mod.sl");
if mod_file.exists() && mod_file.is_file() {
return self.canonicalize(&mod_file);
}
Err(ResolveError::NotFound(path.display().to_string()))
}
fn canonicalize(&self, path: &Path) -> Result<PathBuf, ResolveError> {
path.canonicalize()
.map_err(|_| ResolveError::NotFound(path.display().to_string()))
}
}
fn normalize_path(path: &Path) -> PathBuf {
if let Some(s) = path.to_str() {
let mut result = Vec::new();
let starts_with_slash = s.starts_with('/');
for component in s.split('/') {
match component {
"" => {
if starts_with_slash && result.is_empty() {
}
}
"." => {}
".." => {
if !result.is_empty() && result.last() != Some(&"..") {
result.pop();
} else {
result.push("..");
}
}
_ => result.push(component),
}
}
let normalized_str = if starts_with_slash {
format!("/{}", result.join("/"))
} else {
result.join("/")
};
PathBuf::from(normalized_str)
} else {
path.to_path_buf()
}
}
fn collect_exports(program: &Program) -> HashSet<String> {
let mut exports = HashSet::new();
for stmt in &program.statements {
if let StmtKind::Export(inner) = &stmt.kind {
if let Some(name) = get_declaration_name(inner) {
exports.insert(name);
}
}
}
exports
}
fn get_imported_statements(
module: &ResolvedModule,
import: &ImportDecl,
) -> Result<Vec<Stmt>, ResolveError> {
match &import.specifier {
ImportSpecifier::All => {
let mut stmts = Vec::new();
for stmt in &module.original_program.statements {
if let StmtKind::Export(inner) = &stmt.kind {
stmts.push((**inner).clone());
}
}
Ok(stmts)
}
ImportSpecifier::Named(items) => {
let mut stmts = Vec::new();
for item in items {
if !module.exports.contains(&item.name) {
return Err(ResolveError::ImportError(format!(
"'{}' is not exported from '{}'",
item.name, import.path
)));
}
for stmt in &module.original_program.statements {
if let StmtKind::Export(inner) = &stmt.kind {
if let Some(name) = get_declaration_name(inner) {
if name == item.name {
if let Some(ref alias) = item.alias {
let renamed = rename_declaration(inner, alias);
stmts.push(renamed);
} else {
stmts.push((**inner).clone());
}
break;
}
}
}
}
}
Ok(stmts)
}
ImportSpecifier::Namespace(_name) => {
let mut stmts = Vec::new();
for stmt in &module.original_program.statements {
if let StmtKind::Export(inner) = &stmt.kind {
stmts.push((**inner).clone());
}
}
Ok(stmts)
}
}
}
fn get_declaration_name(stmt: &Stmt) -> Option<String> {
match &stmt.kind {
StmtKind::Function(decl) => Some(decl.name.clone()),
StmtKind::Class(decl) => Some(decl.name.clone()),
StmtKind::Interface(decl) => Some(decl.name.clone()),
StmtKind::Let { name, .. } => Some(name.clone()),
_ => None,
}
}
fn rename_declaration(stmt: &Stmt, new_name: &str) -> Stmt {
let mut new_stmt = stmt.clone();
match &mut new_stmt.kind {
StmtKind::Function(decl) => {
decl.name = new_name.to_string();
}
StmtKind::Class(decl) => {
decl.name = new_name.to_string();
}
StmtKind::Interface(decl) => {
decl.name = new_name.to_string();
}
StmtKind::Let { name, .. } => {
*name = new_name.to_string();
}
_ => {}
}
new_stmt
}