diff --git a/src/ast.rs b/src/ast.rs index e2ecb67..518c18d 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -240,6 +240,7 @@ pub enum ExprKind { Return(Option>), Break, Continue, + Defer(Box), } #[derive(Debug, Clone, PartialEq)] @@ -273,7 +274,7 @@ pub enum UnOp { Not, Ref, Deref, - PreInc + PreInc, } #[derive(Debug, Clone)] @@ -429,6 +430,7 @@ pub enum TypedExprKind { Return(Option>), Break, Continue, + Defer(Box), } #[derive(Debug, Clone)] diff --git a/src/c_ir.rs b/src/c_ir.rs index a132d42..87ac418 100644 --- a/src/c_ir.rs +++ b/src/c_ir.rs @@ -122,7 +122,7 @@ pub enum CUnaryOp { Not, Ref, Deref, - PreInc + PreInc, } impl CUnaryOp { @@ -132,7 +132,7 @@ impl CUnaryOp { CUnaryOp::Not => "!", CUnaryOp::Ref => "&", CUnaryOp::Deref => "*", - CUnaryOp::PreInc => "++" + CUnaryOp::PreInc => "++", } } } diff --git a/src/c_lowerer/mod.rs b/src/c_lowerer/mod.rs index 72605cf..a6f32a4 100644 --- a/src/c_lowerer/mod.rs +++ b/src/c_lowerer/mod.rs @@ -1,2 +1,2 @@ pub mod declaration_transpiler; -pub mod statements_transpiler; \ No newline at end of file +pub mod statements_transpiler; diff --git a/src/c_lowerer/statements_transpiler.rs b/src/c_lowerer/statements_transpiler.rs index bb1e1dd..e277af3 100644 --- a/src/c_lowerer/statements_transpiler.rs +++ b/src/c_lowerer/statements_transpiler.rs @@ -187,8 +187,20 @@ impl StatementsTranspiler { } TypedExprKind::Do(exprs) => { let mut stmts = Vec::new(); + let mut defers = Vec::new(); for expr in exprs { - stmts.push(self.transpile_stmt(expr)?); + match &expr.kind { + TypedExprKind::Defer(defer_expr) => { + defers.push(self.transpile_stmt(defer_expr)?); + } + _ => { + stmts.push(self.transpile_stmt(expr)?); + } + } + } + // Execute defers in reverse order at the end + for defer_stmt in defers.into_iter().rev() { + stmts.push(defer_stmt); } Ok(CStmt::Block(stmts)) } @@ -211,10 +223,8 @@ impl StatementsTranspiler { Box::new(end_expr), ); - let incr = CExpr::UnOp( - CUnaryOp::PreInc, - Box::new(CExpr::Var(var_name.clone())), - ); + let incr = + CExpr::UnOp(CUnaryOp::PreInc, Box::new(CExpr::Var(var_name.clone()))); let body_stmts = self.expr_to_stmts(body)?; Ok(CStmt::For(init, cond, incr, body_stmts)) @@ -224,32 +234,26 @@ impl StatementsTranspiler { TypedExprKind::Variable(var) => { let elem_ty = match &iterable.ty { Type::Array(inner) => self.type_to_ctype(inner)?, - _ => return Err(format!( - "Cannot iterate over non-array variable `{}`", - var - )), + _ => { + return Err(format!( + "Cannot iterate over non-array variable `{}`", + var + )); + } }; // i = 0 - let (idx_name, idx_decl) = self.fresh_tmp_var( - "_i", - CType::Int, - Some(CExpr::IntLit(0)), - ); + let (idx_name, idx_decl) = + self.fresh_tmp_var("_i", CType::Int, Some(CExpr::IntLit(0))); let cond = CExpr::BinOp( Box::new(CExpr::Var(idx_name.clone())), CBinaryOp::Lt, - Box::new(CExpr::Dot( - Box::new(CExpr::Var(var.clone())), - "len".into(), - )), + Box::new(CExpr::Dot(Box::new(CExpr::Var(var.clone())), "len".into())), ); - let incr = CExpr::UnOp( - CUnaryOp::PreInc, - Box::new(CExpr::Var(idx_name.clone())), - ); + let incr = + CExpr::UnOp(CUnaryOp::PreInc, Box::new(CExpr::Var(idx_name.clone()))); // let x = array.data[i] let bind = CStmt::VarDecl(CVarDecl { @@ -292,11 +296,8 @@ impl StatementsTranspiler { ); // i = 0 - let (idx_name, idx_decl) = self.fresh_tmp_var( - "_i", - CType::Int, - Some(CExpr::IntLit(0)), - ); + let (idx_name, idx_decl) = + self.fresh_tmp_var("_i", CType::Int, Some(CExpr::IntLit(0))); let cond = CExpr::BinOp( Box::new(CExpr::Var(idx_name.clone())), @@ -304,10 +305,8 @@ impl StatementsTranspiler { Box::new(CExpr::IntLit(arr_len as i64)), ); - let incr = CExpr::UnOp( - CUnaryOp::PreInc, - Box::new(CExpr::Var(idx_name.clone())), - ); + let incr = + CExpr::UnOp(CUnaryOp::PreInc, Box::new(CExpr::Var(idx_name.clone()))); let bind = CStmt::VarDecl(CVarDecl { name: var_name.clone(), @@ -332,6 +331,7 @@ impl StatementsTranspiler { } TypedExprKind::Break => Ok(CStmt::Break), TypedExprKind::Continue => Ok(CStmt::Continue), + TypedExprKind::Defer(_) => Err("Defer should be handled in Do blocks".to_string()), _ => { // For other expressions, treat as expression statements let c_expr = self.transpile_expr(expr)?; @@ -344,21 +344,36 @@ impl StatementsTranspiler { match &expr.kind { TypedExprKind::Do(stmts) => { let mut c_stmts = Vec::new(); + let mut defers = Vec::new(); + let mut last_stmt = None; for (i, stmt) in stmts.iter().enumerate() { - if i == stmts.len() - 1 { - // Last expression in a block should be returned - match &stmt.kind { - TypedExprKind::Return(_) => { + match &stmt.kind { + TypedExprKind::Defer(defer_expr) => { + defers.push(self.transpile_stmt(defer_expr)?); + } + _ => { + if i == stmts.len() - 1 { + last_stmt = Some(stmt); + } else { c_stmts.push(self.transpile_stmt(stmt)?); } - _ => { - // Convert to return statement - let c_expr = self.transpile_expr(stmt)?; - c_stmts.push(CStmt::Return(Some(c_expr))); - } } - } else { - c_stmts.push(self.transpile_stmt(stmt)?); + } + } + // Execute defers in reverse order before the last statement + for defer_stmt in defers.into_iter().rev() { + c_stmts.push(defer_stmt); + } + if let Some(last) = last_stmt { + match &last.kind { + TypedExprKind::Return(_) => { + c_stmts.push(self.transpile_stmt(last)?); + } + _ => { + // Convert to return statement + let c_expr = self.transpile_expr(last)?; + c_stmts.push(CStmt::Return(Some(c_expr))); + } } } Ok(c_stmts) @@ -402,12 +417,8 @@ impl StatementsTranspiler { Type::Bool => Ok(CType::Bool), Type::String => Ok(CType::Ptr(Box::new(CType::Char))), Type::Unit => Ok(CType::Void), - Type::Ptr(inner) => { - Ok(CType::Ptr(Box::new(self.type_to_ctype(inner)?))) - } - Type::Array(inner) => { - Ok(CType::Ptr(Box::new(self.type_to_ctype(inner)?))) - } + Type::Ptr(inner) => Ok(CType::Ptr(Box::new(self.type_to_ctype(inner)?))), + Type::Array(inner) => Ok(CType::Ptr(Box::new(self.type_to_ctype(inner)?))), Type::Struct(name, _) => Ok(CType::Struct(name.clone())), Type::Enum(name, _) => Ok(CType::Struct(name.clone())), Type::Tuple(types) => { @@ -480,8 +491,7 @@ impl StatementsTranspiler { } let c_ret = self.type_annot_to_ctype(ret)?; Ok(CType::Func(c_args, Box::new(c_ret))) - } - //_ => Err(format!("Unsupported type annotation: {:?}", annot)), + } //_ => Err(format!("Unsupported type annotation: {:?}", annot)), } } } diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs index ed9dfaf..fdebb90 100644 --- a/src/codegen/mod.rs +++ b/src/codegen/mod.rs @@ -1 +1 @@ -pub mod transpiler; \ No newline at end of file +pub mod transpiler; diff --git a/src/codegen/transpiler.rs b/src/codegen/transpiler.rs index c13424e..dbb7855 100644 --- a/src/codegen/transpiler.rs +++ b/src/codegen/transpiler.rs @@ -1,8 +1,8 @@ use crate::ast::*; use crate::c_ir::*; -use std::collections::HashMap; use crate::c_lowerer::declaration_transpiler::DeclarationTranspiler; use crate::c_lowerer::statements_transpiler::StatementsTranspiler; +use std::collections::HashMap; pub struct Transpiler { structs: HashMap, @@ -99,7 +99,7 @@ impl Transpiler { // But for now, skip as they're handled differently } TypedASTNodeKind::Load(_) => {} - TypedASTNodeKind::Trait(typed_trait ) => {} + TypedASTNodeKind::Trait(typed_trait) => {} TypedASTNodeKind::Use(_) => {} } Ok(()) @@ -227,19 +227,20 @@ impl Transpiler { } CStmt::For(init, cond, incr, body) => { - let mut output = format!("for ({}; {}; {}) {{", self.generate_var_decl(init), self.generate_expr(cond), self.generate_expr(incr)); + let mut output = format!( + "for ({}; {}; {}) {{", + self.generate_var_decl(init), + self.generate_expr(cond), + self.generate_expr(incr) + ); for stmt in body { output.push_str(&format!(" {}", self.generate_stmt(stmt))); } output.push_str(" }\n"); output } - CStmt::Break => { - "break;\n".to_string() - } - CStmt::Continue => { - "continue;\n".to_string() - } + CStmt::Break => "break;\n".to_string(), + CStmt::Continue => "continue;\n".to_string(), } } @@ -313,7 +314,10 @@ impl Transpiler { } CExpr::ArrayLit(array_lit) => { - let vec = array_lit.iter().map(|expr| self.generate_expr(expr)).collect::>(); + let vec = array_lit + .iter() + .map(|expr| self.generate_expr(expr)) + .collect::>(); let len = vec.len(); format!("{}[{}]{{ {} }}", vec[0], len, vec.join(", ")) } diff --git a/src/import_resolver.rs b/src/import_resolver.rs new file mode 100644 index 0000000..7852dc3 --- /dev/null +++ b/src/import_resolver.rs @@ -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, + }, + FileNotFound { + path: String, + reason: String, + }, + LexError { + file: String, + position: usize, + }, + ParseError { + file: String, + message: String, + span: (usize, usize), + }, + CircularDependency { + files: Vec, + }, +} + +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, + /// 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>, +} + +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, + /// Global symbol registry for duplicate detection + symbol_registry: GlobalSymbolRegistry, + /// Set of files currently being processed (for cycle detection) + processing_stack: HashSet, + /// Dependency graph: file -> list of files it depends on + dependency_graph: HashMap>, +} + +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, 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, 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, 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 { + 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, ImportError> { + // Check for cycles in processing + if self.processing_stack.contains(filename) { + return Err(ImportError::CircularDependency { + files: self + .processing_stack + .iter() + .cloned() + .collect::>() + .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, 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) + ); + } +} diff --git a/src/lambda_lower.rs b/src/lambda_lower.rs index 6ccc465..b9996b1 100644 --- a/src/lambda_lower.rs +++ b/src/lambda_lower.rs @@ -145,6 +145,9 @@ impl LambdaLowerer { self.collect_free_vars_expr(start, lambda_params, free_vars, local_scope); self.collect_free_vars_expr(end, lambda_params, free_vars, local_scope); } + ExprKind::Defer(expr) => { + self.collect_free_vars_expr(expr, lambda_params, free_vars, local_scope); + } // Terminal expressions don't contain variables ExprKind::Int(_) | ExprKind::Float(_) @@ -372,6 +375,7 @@ impl LambdaLowerer { | ExprKind::Variable(_) | ExprKind::Break | ExprKind::Continue => expr.kind.clone(), + ExprKind::Defer(inner) => ExprKind::Defer(Box::new(self.lower_expr(inner)?)), }; Ok(Expr { diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 85bf92b..a64b756 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -150,6 +150,9 @@ pub enum Token { #[token("continue")] KeywordContinue, + #[token("defer")] + KeywordDefer, + #[token("+")] Plus, diff --git a/src/lib.rs b/src/lib.rs index 8260c34..a9fac3c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,9 @@ pub const EXTENSION: &str = ".sui"; pub mod ast; pub mod c_ir; -pub mod codegen; pub mod c_lowerer; +pub mod codegen; +pub mod import_resolver; pub mod lambda_lower; pub mod lexer; pub mod monomorphize; diff --git a/src/main.rs b/src/main.rs index 587e7c0..d2317ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,40 @@ -use logos::Logos; -use std::fs; -use suicmez::{ - codegen::transpiler::Transpiler, - lambda_lower::LambdaLowerer, - lexer::Token, - monomorphize::{Monomorphizer, check_no_typevars}, - parser::Parser, - typechecker::TypeChecker, -}; + use clap::Parser; + use std::fs; + use suicmez::{ + codegen::transpiler::Transpiler, + import_resolver::ImportResolver, + lambda_lower::LambdaLowerer, + monomorphize::{Monomorphizer, check_no_typevars}, + typechecker::TypeChecker, + }; -fn main() { - // Check if a file was provided as argument - let args: Vec = std::env::args().collect(); - if args.len() < 2 { - // Run all test files in the tests directory - run_test_suite(); - return; - } + #[derive(Parser)] + #[command(author, version, about = "A compiler for the Sui language")] + struct Args { + /// Run the test suite instead of compiling a file + #[arg(short, long)] + test: bool, - let filename = &args[1]; - println!("Type checking file: {}", filename); + /// The Sui source file to compile + file: Option, + } - if let Err(e) = run_file(filename) { - eprintln!("Error: {}", e); - } -} + fn main() { + let args = Args::parse(); + + if args.test { + run_test_suite(); + } else if let Some(filename) = args.file { + println!("Type checking file: {}", filename); + + if let Err(e) = run_file(&filename) { + eprintln!("Error: {}", e); + } + } else { + eprintln!("No file specified. Use --test to run the test suite or provide a Sui file to compile."); + std::process::exit(1); + } + } fn run_test_suite() { println!("Running test suite...\n"); @@ -164,34 +174,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 diff --git a/src/monomorphize.rs b/src/monomorphize.rs index a064b15..f97a196 100644 --- a/src/monomorphize.rs +++ b/src/monomorphize.rs @@ -327,6 +327,11 @@ impl Monomorphizer { | TypedExprKind::String(_) | TypedExprKind::Break | TypedExprKind::Continue => expr.kind.clone(), + TypedExprKind::Defer(inner) => { + let (new_inner, mut inner_needs) = self.monomorphize_expr(inner)?; + needs.append(&mut inner_needs); + TypedExprKind::Defer(Box::new(new_inner)) + } TypedExprKind::Array(elems) => { let mut new_elems = Vec::new(); diff --git a/src/parser.rs b/src/parser.rs index eb87645..b06654a 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1720,7 +1720,18 @@ impl Parser { self.next(); break; } - exprs.push(self.parse_expr()?); + if matches!(self.peek(), Some(Token::KeywordDefer)) { + self.next(); + let expr = self.parse_expr()?; + let end = expr.span.end; + exprs.push(Expr { + kind: ExprKind::Defer(Box::new(expr)), + span: Span::new(&(start..end), self.file.clone()), + attributes: Vec::new(), + }); + } else { + exprs.push(self.parse_expr()?); + } if matches!(self.peek(), Some(Token::Semicolon)) { self.next(); diff --git a/src/typechecker.rs b/src/typechecker.rs index f0ad2be..9e7481e 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -1410,6 +1410,11 @@ impl TypeChecker { ExprKind::Break => (TypedExprKind::Break, Type::Never), ExprKind::Continue => (TypedExprKind::Continue, Type::Never), + ExprKind::Defer(expr) => { + let typed_expr = self.typecheck_expr(expr)?; + (TypedExprKind::Defer(Box::new(typed_expr)), Type::Unit) + } + ExprKind::EarlyReturn(value) => { let typed_value = if let Some(v) = value { Some(Box::new(self.typecheck_expr(v)?)) diff --git a/tests/basic_types.c b/tests/basic_types.c index 85ba69b..a916271 100644 --- a/tests/basic_types.c +++ b/tests/basic_types.c @@ -2,7 +2,6 @@ #include #include #include - int main(void); diff --git a/tests/functions.c b/tests/functions.c new file mode 100644 index 0000000..ee37205 --- /dev/null +++ b/tests/functions.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include +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; +} + diff --git a/tests/import_tests/duplicate_main.sui b/tests/import_tests/duplicate_main.sui new file mode 100644 index 0000000..ad4a6ab --- /dev/null +++ b/tests/import_tests/duplicate_main.sui @@ -0,0 +1,6 @@ +use "./module_a" +use "./module_b" + +fn main() -> int do + 0 +end diff --git a/tests/import_tests/good_import.c b/tests/import_tests/good_import.c new file mode 100644 index 0000000..556be3d --- /dev/null +++ b/tests/import_tests/good_import.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include +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(); +} + diff --git a/tests/import_tests/good_import.sui b/tests/import_tests/good_import.sui new file mode 100644 index 0000000..74a48ad --- /dev/null +++ b/tests/import_tests/good_import.sui @@ -0,0 +1,5 @@ +use "./util" + +fn main() -> int do + helper_func() +end diff --git a/tests/import_tests/module_a.sui b/tests/import_tests/module_a.sui new file mode 100644 index 0000000..9ee28c9 --- /dev/null +++ b/tests/import_tests/module_a.sui @@ -0,0 +1,3 @@ +fn test_func() -> int do + 42 +end diff --git a/tests/import_tests/module_b.sui b/tests/import_tests/module_b.sui new file mode 100644 index 0000000..850a737 --- /dev/null +++ b/tests/import_tests/module_b.sui @@ -0,0 +1,3 @@ +fn test_func() -> int do + 99 +end diff --git a/tests/import_tests/util.sui b/tests/import_tests/util.sui new file mode 100644 index 0000000..47bd1bd --- /dev/null +++ b/tests/import_tests/util.sui @@ -0,0 +1,8 @@ +fn helper_func() -> int do + 42 +end + +struct Point + x: int, + y: int, +end diff --git a/tests/structs.c b/tests/structs.c index d2a2577..e8915e2 100644 --- a/tests/structs.c +++ b/tests/structs.c @@ -2,12 +2,10 @@ #include #include #include - struct Point { int x; int y; }; - int main(void); diff --git a/tests/traits.c b/tests/traits.c index 8cb9442..a4a89b8 100644 --- a/tests/traits.c +++ b/tests/traits.c @@ -1,19 +1,21 @@ -#include #include #include +#include #include - 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; } +