Compare commits

...

2 commits

Author SHA1 Message Date
d7f87a37e8 IMPORTSSSSSSSSSSS 2025-12-16 19:29:32 +05:30
77ca7b179c defer 2025-12-16 18:58:52 +05:30
12 changed files with 496 additions and 39 deletions

410
src/import_resolver.rs Normal file
View file

@ -0,0 +1,410 @@
use crate::ast::{ASTNode, ASTNodeKind, Span};
use crate::lexer::Token;
use crate::parser::Parser;
use logos::Logos;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::Path;
const EXTENSION: &str = ".sui";
/// Error types for import resolution
#[derive(Debug, Clone)]
pub enum ImportError {
DuplicateSymbol {
symbol: String,
locations: Vec<String>,
},
FileNotFound {
path: String,
reason: String,
},
LexError {
file: String,
position: usize,
},
ParseError {
file: String,
message: String,
span: (usize, usize),
},
CircularDependency {
files: Vec<String>,
},
}
impl std::fmt::Display for ImportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImportError::DuplicateSymbol { symbol, locations } => {
write!(f, "Duplicate global symbol '{}' defined in:\n", symbol)?;
for (i, loc) in locations.iter().enumerate() {
write!(f, " [{}] {}", i + 1, loc)?;
if i < locations.len() - 1 {
writeln!(f)?;
}
}
Ok(())
}
ImportError::FileNotFound { path, reason } => {
write!(f, "Module not found '{}': {}", path, reason)
}
ImportError::LexError { file, position } => {
write!(f, "Lexer error at position {} in {}", position, file)
}
ImportError::ParseError {
file,
message,
span,
} => {
write!(
f,
"Parse error in {}: {} (at {}..{})",
file, message, span.0, span.1
)
}
ImportError::CircularDependency { files } => {
write!(f, "Circular dependency detected: {}", files.join(" -> "))
}
}
}
}
impl std::error::Error for ImportError {}
/// Cached parse output for a file
#[derive(Clone, Debug)]
struct ParsedFile {
/// AST nodes from this file (before import collection)
nodes: Vec<ASTNode>,
/// Hash of the file content at parse time
content_hash: u64,
}
/// Tracks global symbols and their definitions
#[derive(Debug, Default)]
struct GlobalSymbolRegistry {
/// Maps symbol name to (file, span) where it was defined
symbols: HashMap<String, Vec<(String, Span)>>,
}
impl GlobalSymbolRegistry {
fn register(&mut self, symbol: String, file: String, span: Span) -> Result<(), ImportError> {
self.symbols.entry(symbol.clone()).or_insert_with(Vec::new);
let locations = self.symbols.get_mut(&symbol).unwrap();
locations.push((file.clone(), span));
// Check for duplicates
if locations.len() > 1 {
let location_strs = locations
.iter()
.map(|(f, s)| format!("{}:{}..{}", f, s.start, s.end))
.collect();
return Err(ImportError::DuplicateSymbol {
symbol,
locations: location_strs,
});
}
Ok(())
}
}
/// Manages file dependency resolution and caching
pub struct ImportResolver {
/// Cache of parsed files keyed by path
parse_cache: HashMap<String, ParsedFile>,
/// Global symbol registry for duplicate detection
symbol_registry: GlobalSymbolRegistry,
/// Set of files currently being processed (for cycle detection)
processing_stack: HashSet<String>,
/// Dependency graph: file -> list of files it depends on
dependency_graph: HashMap<String, HashSet<String>>,
}
impl ImportResolver {
pub fn new() -> Self {
ImportResolver {
parse_cache: HashMap::new(),
symbol_registry: GlobalSymbolRegistry::default(),
processing_stack: HashSet::new(),
dependency_graph: HashMap::new(),
}
}
/// Compute a simple hash of file content
fn hash_content(content: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
content.hash(&mut hasher);
hasher.finish()
}
/// Lex source code into tokens
fn lex_source(source: &str, filename: &str) -> Result<Vec<(Token, logos::Span)>, ImportError> {
let mut tokens = Vec::new();
let mut lexer = Token::lexer(source);
while let Some(token_result) = lexer.next() {
match token_result {
Ok(token) => {
tokens.push((token, lexer.span()));
}
Err(()) => {
return Err(ImportError::LexError {
file: filename.to_string(),
position: lexer.span().start,
});
}
}
}
Ok(tokens)
}
/// Parse tokens into AST
fn parse_tokens(
tokens: Vec<(Token, logos::Span)>,
filename: String,
) -> Result<Vec<ASTNode>, ImportError> {
let mut parser = Parser::new(filename.clone(), tokens);
parser.parse().map_err(|e| ImportError::ParseError {
file: filename,
message: e.message.clone(),
span: (e.span.start, e.span.end),
})
}
/// Parse a file (using cache if content hash matches)
fn parse_file(&mut self, filename: &str) -> Result<Vec<ASTNode>, ImportError> {
let source = fs::read_to_string(filename).map_err(|e| ImportError::FileNotFound {
path: filename.to_string(),
reason: e.to_string(),
})?;
let content_hash = Self::hash_content(&source);
// Check if we have a valid cached version
if let Some(cached) = self.parse_cache.get(filename) {
if cached.content_hash == content_hash {
println!(" [cache hit] {}", filename);
return Ok(cached.nodes.clone());
}
}
// Parse the file
println!(" [parsing] {}", filename);
let tokens = Self::lex_source(&source, filename)?;
let nodes = Self::parse_tokens(tokens, filename.to_string())?;
// Cache the result
self.parse_cache.insert(
filename.to_string(),
ParsedFile {
nodes: nodes.clone(),
content_hash,
},
);
Ok(nodes)
}
/// Collect all dependencies from a file (via Use/Load statements)
fn collect_dependencies(&self, filename: &str, nodes: &[ASTNode]) -> Vec<String> {
let mut deps = Vec::new();
let mut seen = HashSet::new();
for node in nodes {
if let ASTNodeKind::Use(use_stmt) = &node.kind {
let path = &use_stmt.path;
if !seen.contains(path) {
seen.insert(path.clone());
deps.push(path.clone());
}
}
}
// Resolve relative paths
let importing_dir = Path::new(filename).parent().unwrap_or(Path::new(""));
deps.iter()
.filter_map(|path| {
let resolved = if path.starts_with("std/") {
Some(format!("src/{}{}", path, EXTENSION))
} else if path.starts_with("@") {
// Package manager support - TODO
None
} else if path.starts_with(".") || path.starts_with("~") {
let resolved_path = importing_dir.join(path);
Some(resolved_path.to_string_lossy().to_string() + EXTENSION)
} else {
None
};
resolved
})
.collect()
}
/// Check for circular dependencies
fn detect_cycles(&self, file: &str, target: &str) -> Result<(), ImportError> {
if file == target {
return Err(ImportError::CircularDependency {
files: vec![file.to_string()],
});
}
// Simple cycle detection: if target depends on file, we have a cycle
if let Some(target_deps) = self.dependency_graph.get(target) {
if target_deps.contains(file) {
return Err(ImportError::CircularDependency {
files: vec![file.to_string(), target.to_string()],
});
}
}
Ok(())
}
/// Recursively resolve imports for a file
fn resolve_imports_recursive(&mut self, filename: &str) -> Result<Vec<ASTNode>, ImportError> {
// Check for cycles in processing
if self.processing_stack.contains(filename) {
return Err(ImportError::CircularDependency {
files: self
.processing_stack
.iter()
.cloned()
.collect::<Vec<_>>()
.into_iter()
.chain(std::iter::once(filename.to_string()))
.collect(),
});
}
self.processing_stack.insert(filename.to_string());
// Parse the file
let nodes = self.parse_file(filename)?;
let mut result = Vec::new();
// Collect dependencies
let deps = self.collect_dependencies(filename, &nodes);
self.dependency_graph
.insert(filename.to_string(), deps.iter().cloned().collect());
// Process each node
for node in nodes {
match &node.kind {
ASTNodeKind::Use(use_stmt) => {
let path = &use_stmt.path;
// Resolve path
let dep_file = if path.starts_with("std/") {
format!("src/{}{}", path, EXTENSION)
} else if path.starts_with("@") {
// Package manager support - TODO
todo!("Package manager imports not yet supported")
} else if path.starts_with(".") || path.starts_with("~") {
let importing_dir = Path::new(filename).parent().unwrap_or(Path::new(""));
let resolved_path = importing_dir.join(path);
resolved_path.to_string_lossy().to_string() + EXTENSION
} else {
return Err(ImportError::FileNotFound {
path: path.clone(),
reason: "Invalid import path format".to_string(),
});
};
// Check for cycles
self.detect_cycles(filename, &dep_file)?;
// Recursively resolve the imported file
let imported_nodes = self.resolve_imports_recursive(&dep_file)?;
result.extend(imported_nodes);
}
ASTNodeKind::Struct(s) => {
// Register the symbol
self.symbol_registry.register(
s.name.clone(),
filename.to_string(),
node.span.clone(),
)?;
result.push(node);
}
ASTNodeKind::Enum(e) => {
self.symbol_registry.register(
e.name.clone(),
filename.to_string(),
node.span.clone(),
)?;
result.push(node);
}
ASTNodeKind::Function(f) => {
self.symbol_registry.register(
f.name.clone(),
filename.to_string(),
node.span.clone(),
)?;
result.push(node);
}
ASTNodeKind::Impl(_) => {
// Impl blocks don't have names but are still tracked
result.push(node);
}
ASTNodeKind::Extern(e) => {
self.symbol_registry.register(
e.name.clone(),
filename.to_string(),
node.span.clone(),
)?;
result.push(node);
}
ASTNodeKind::Load(_) => {
result.push(node);
}
ASTNodeKind::Trait(t) => {
self.symbol_registry.register(
t.name.clone(),
filename.to_string(),
node.span.clone(),
)?;
result.push(node);
}
}
}
self.processing_stack.remove(filename);
Ok(result)
}
/// Resolve all imports starting from the given file
pub fn resolve(&mut self, filename: &str) -> Result<Vec<ASTNode>, ImportError> {
println!("Starting import resolution...");
self.resolve_imports_recursive(filename)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hash_content() {
let content1 = "fn main() do end";
let content2 = "fn main() do end";
let content3 = "fn main() { x }";
assert_eq!(
ImportResolver::hash_content(content1),
ImportResolver::hash_content(content2)
);
assert_ne!(
ImportResolver::hash_content(content1),
ImportResolver::hash_content(content3)
);
}
}

View file

@ -4,6 +4,7 @@ pub mod ast;
pub mod c_ir;
pub mod c_lowerer;
pub mod codegen;
pub mod import_resolver;
pub mod lambda_lower;
pub mod lexer;
pub mod monomorphize;

View file

@ -1,11 +1,9 @@
use logos::Logos;
use std::fs;
use suicmez::{
codegen::transpiler::Transpiler,
import_resolver::ImportResolver,
lambda_lower::LambdaLowerer,
lexer::Token,
monomorphize::{Monomorphizer, check_no_typevars},
parser::Parser,
typechecker::TypeChecker,
};
@ -164,34 +162,19 @@ fn format_type_error(source: &str, error: &suicmez::typechecker::TypeError) -> S
}
fn run_file(filename: &str) -> Result<(), String> {
// Read the source file
// ========== IMPORT RESOLUTION PHASE ==========
println!("\n=== Import Resolution Phase ===");
let mut resolver = ImportResolver::new();
let ast_nodes = resolver
.resolve(filename)
.map_err(|e| format!("Import resolution error: {}", e))?;
println!("Import resolution complete! {} total nodes loaded", ast_nodes.len());
// Read the source file for error reporting
let source = fs::read_to_string(filename)
.map_err(|e| format!("Error reading file {}: {}", filename, e))?;
// First, we need to parse the source code
let mut tokens = Vec::new();
let mut lexer = Token::lexer(&source);
loop {
match lexer.next() {
Some(Ok(token)) => {
let span = lexer.span();
tokens.push((token, span));
}
Some(Err(_)) => {
return Err("Lexing error".to_string());
}
None => break,
}
}
let mut parser = Parser::new(filename.to_string(), tokens);
let ast_nodes = parser
.parse()
.map_err(|e| format_parse_error(&source, &e))?;
println!("Parsed {} AST nodes successfully", ast_nodes.len());
// Lower lambdas to generated functions
let lowerer = LambdaLowerer::new();
let lowered_nodes = lowerer

18
tests/functions.c Normal file
View file

@ -0,0 +1,18 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
int add(int x, int y);
int main(void);
int add(int x, int y) {
return (x + y);
}
int main(void) {
int sum = add(5, 3);
struct T id = identity(42);
return sum;
}

View file

@ -0,0 +1,6 @@
use "./module_a"
use "./module_b"
fn main() -> int do
0
end

View file

@ -0,0 +1,20 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
struct Point {
int x;
int y;
};
int helper_func(void);
int main(void);
int helper_func(void) {
return 42;
}
int main(void) {
return helper_func();
}

View file

@ -0,0 +1,5 @@
use "./util"
fn main() -> int do
helper_func()
end

View file

@ -0,0 +1,3 @@
fn test_func() -> int do
42
end

View file

@ -0,0 +1,3 @@
fn test_func() -> int do
99
end

View file

@ -0,0 +1,8 @@
fn helper_func() -> int do
42
end
struct Point
x: int,
y: int,
end

View file

@ -2,12 +2,10 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
struct Point {
int x;
int y;
};
int main(void);

View file

@ -1,19 +1,21 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
struct Number {
int value;
int value;
};
char *Number_show(struct Number self);
char* Number_show(struct Number self);
int main(void);
char *Number_show(struct Number self) { return "number"; }
char* Number_show(struct Number self) {
return "number";
}
int main(void) {
struct Number number = (struct Number){.value = 40};
Number_show(number);
return 0;
struct Number number = (struct Number){ .value = 40 };
Number_show(number);
return 0;
}