diff --git a/crates/prometeu-compiler/src/frontends/pbs/collector.rs b/crates/prometeu-compiler/src/frontends/pbs/collector.rs new file mode 100644 index 00000000..2a5c36ea --- /dev/null +++ b/crates/prometeu-compiler/src/frontends/pbs/collector.rs @@ -0,0 +1,138 @@ +use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel}; +use crate::frontends::pbs::ast::*; +use crate::frontends::pbs::symbols::*; + +pub struct SymbolCollector { + type_symbols: SymbolTable, + value_symbols: SymbolTable, + diagnostics: Vec, +} + +impl SymbolCollector { + pub fn new() -> Self { + Self { + type_symbols: SymbolTable::new(), + value_symbols: SymbolTable::new(), + diagnostics: Vec::new(), + } + } + + pub fn collect(&mut self, file: &FileNode) -> Result<(SymbolTable, SymbolTable), DiagnosticBundle> { + for decl in &file.decls { + match decl { + Node::FnDecl(fn_decl) => self.collect_fn(fn_decl), + Node::ServiceDecl(service_decl) => self.collect_service(service_decl), + Node::TypeDecl(type_decl) => self.collect_type(type_decl), + _ => {} + } + } + + if !self.diagnostics.is_empty() { + return Err(DiagnosticBundle { + diagnostics: self.diagnostics.clone(), + }); + } + + Ok(( + std::mem::replace(&mut self.type_symbols, SymbolTable::new()), + std::mem::replace(&mut self.value_symbols, SymbolTable::new()), + )) + } + + fn collect_fn(&mut self, decl: &FnDeclNode) { + // Top-level fn are always file-private in PBS v0 + let symbol = Symbol { + name: decl.name.clone(), + kind: SymbolKind::Function, + namespace: Namespace::Value, + visibility: Visibility::FilePrivate, + span: decl.span, + }; + self.insert_value_symbol(symbol); + } + + fn collect_service(&mut self, decl: &ServiceDeclNode) { + let vis = match decl.vis.as_str() { + "pub" => Visibility::Pub, + "mod" => Visibility::Mod, + _ => Visibility::FilePrivate, // Should not happen with valid parser + }; + let symbol = Symbol { + name: decl.name.clone(), + kind: SymbolKind::Service, + namespace: Namespace::Type, // Service is a type + visibility: vis, + span: decl.span, + }; + self.insert_type_symbol(symbol); + } + + fn collect_type(&mut self, decl: &TypeDeclNode) { + let vis = match decl.vis.as_deref() { + Some("pub") => Visibility::Pub, + Some("mod") => Visibility::Mod, + _ => Visibility::FilePrivate, + }; + let kind = match decl.type_kind.as_str() { + "struct" => SymbolKind::Struct, + "contract" => SymbolKind::Contract, + "error" => SymbolKind::ErrorType, + _ => SymbolKind::Struct, // Default + }; + let symbol = Symbol { + name: decl.name.clone(), + kind, + namespace: Namespace::Type, + visibility: vis, + span: decl.span, + }; + self.insert_type_symbol(symbol); + } + + fn insert_type_symbol(&mut self, symbol: Symbol) { + // Check for collision in value namespace first + if let Some(existing) = self.value_symbols.get(&symbol.name) { + let existing = existing.clone(); + self.error_collision(&symbol, &existing); + return; + } + + if let Err(existing) = self.type_symbols.insert(symbol.clone()) { + self.error_duplicate(&symbol, &existing); + } + } + + fn insert_value_symbol(&mut self, symbol: Symbol) { + // Check for collision in type namespace first + if let Some(existing) = self.type_symbols.get(&symbol.name) { + let existing = existing.clone(); + self.error_collision(&symbol, &existing); + return; + } + + if let Err(existing) = self.value_symbols.insert(symbol.clone()) { + self.error_duplicate(&symbol, &existing); + } + } + + fn error_duplicate(&mut self, symbol: &Symbol, existing: &Symbol) { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()), + message: format!("Duplicate symbol '{}' already defined at {:?}", symbol.name, existing.span), + span: Some(symbol.span), + }); + } + + fn error_collision(&mut self, symbol: &Symbol, existing: &Symbol) { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some("E_RESOLVE_NAMESPACE_COLLISION".to_string()), + message: format!( + "Symbol '{}' collides with another symbol in the {:?} namespace defined at {:?}", + symbol.name, existing.namespace, existing.span + ), + span: Some(symbol.span), + }); + } +} diff --git a/crates/prometeu-compiler/src/frontends/pbs/resolver.rs b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs new file mode 100644 index 00000000..73b92c1f --- /dev/null +++ b/crates/prometeu-compiler/src/frontends/pbs/resolver.rs @@ -0,0 +1,319 @@ +use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel}; +use crate::common::spans::Span; +use crate::frontends::pbs::ast::*; +use crate::frontends::pbs::symbols::*; +use std::collections::HashMap; + +pub trait ModuleProvider { + fn get_module_symbols(&self, from_path: &str) -> Option<&ModuleSymbols>; +} + +pub struct Resolver<'a> { + module_provider: &'a dyn ModuleProvider, + current_module: &'a ModuleSymbols, + scopes: Vec>, + imported_symbols: ModuleSymbols, + diagnostics: Vec, +} + +impl<'a> Resolver<'a> { + pub fn new( + current_module: &'a ModuleSymbols, + module_provider: &'a dyn ModuleProvider, + ) -> Self { + Self { + module_provider, + current_module, + scopes: Vec::new(), + imported_symbols: ModuleSymbols::new(), + diagnostics: Vec::new(), + } + } + + pub fn resolve(&mut self, file: &FileNode) -> Result<(), DiagnosticBundle> { + // Step 1: Process imports to populate imported_symbols + for imp in &file.imports { + if let Node::Import(imp_node) = imp { + self.resolve_import(imp_node); + } + } + + // Step 2: Resolve all top-level declarations + for decl in &file.decls { + self.resolve_node(decl); + } + + if !self.diagnostics.is_empty() { + return Err(DiagnosticBundle { + diagnostics: self.diagnostics.clone(), + }); + } + + Ok(()) + } + + fn resolve_import(&mut self, imp: &ImportNode) { + let provider = self.module_provider; + if let Some(target_symbols) = provider.get_module_symbols(&imp.from) { + if let Node::ImportSpec(spec) = &*imp.spec { + for name in &spec.path { + // Try to find in Type namespace + if let Some(sym) = target_symbols.type_symbols.get(name) { + if sym.visibility == Visibility::Pub { + if let Err(_) = self.imported_symbols.type_symbols.insert(sym.clone()) { + self.error_duplicate_import(name, imp.span); + } + } else { + self.error_visibility(sym, imp.span); + } + } + // Try to find in Value namespace + else if let Some(sym) = target_symbols.value_symbols.get(name) { + if sym.visibility == Visibility::Pub { + if let Err(_) = self.imported_symbols.value_symbols.insert(sym.clone()) { + self.error_duplicate_import(name, imp.span); + } + } else { + self.error_visibility(sym, imp.span); + } + } else { + self.error_undefined(name, imp.span); + } + } + } + } else { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some("E_RESOLVE_INVALID_IMPORT".to_string()), + message: format!("Module not found: {}", imp.from), + span: Some(imp.span), + }); + } + } + + fn resolve_node(&mut self, node: &Node) { + match node { + Node::FnDecl(n) => self.resolve_fn_decl(n), + Node::ServiceDecl(n) => self.resolve_service_decl(n), + Node::TypeDecl(n) => self.resolve_type_decl(n), + Node::Block(n) => self.resolve_block(n), + Node::LetStmt(n) => self.resolve_let_stmt(n), + Node::ExprStmt(n) => self.resolve_node(&n.expr), + Node::ReturnStmt(n) => { + if let Some(expr) = &n.expr { + self.resolve_node(expr); + } + } + Node::Call(n) => { + self.resolve_node(&n.callee); + for arg in &n.args { + self.resolve_node(arg); + } + } + Node::Unary(n) => self.resolve_node(&n.expr), + Node::Binary(n) => { + self.resolve_node(&n.left); + self.resolve_node(&n.right); + } + Node::Cast(n) => { + self.resolve_node(&n.expr); + self.resolve_type_ref(&n.ty); + } + Node::IfExpr(n) => { + self.resolve_node(&n.cond); + self.resolve_node(&n.then_block); + if let Some(else_block) = &n.else_block { + self.resolve_node(else_block); + } + } + Node::WhenExpr(n) => { + for arm in &n.arms { + if let Node::WhenArm(arm_node) = arm { + self.resolve_node(&arm_node.cond); + self.resolve_node(&arm_node.body); + } + } + } + Node::Ident(n) => { + self.resolve_identifier(&n.name, n.span, Namespace::Value); + } + Node::TypeName(n) => { + self.resolve_identifier(&n.name, n.span, Namespace::Type); + } + Node::TypeApp(n) => { + self.resolve_identifier(&n.base, n.span, Namespace::Type); + for arg in &n.args { + self.resolve_type_ref(arg); + } + } + _ => {} + } + } + + fn resolve_fn_decl(&mut self, n: &FnDeclNode) { + self.enter_scope(); + for param in &n.params { + self.resolve_type_ref(¶m.ty); + self.define_local(¶m.name, param.span, SymbolKind::Local); + } + if let Some(ret) = &n.ret { + self.resolve_type_ref(ret); + } + self.resolve_node(&n.body); + self.exit_scope(); + } + + fn resolve_service_decl(&mut self, n: &ServiceDeclNode) { + if let Some(ext) = &n.extends { + self.resolve_identifier(ext, n.span, Namespace::Type); + } + for member in &n.members { + if let Node::ServiceFnSig(sig) = member { + for param in &sig.params { + self.resolve_type_ref(¶m.ty); + } + self.resolve_type_ref(&sig.ret); + } + } + } + + fn resolve_type_decl(&mut self, n: &TypeDeclNode) { + if let Node::TypeBody(body) = &*n.body { + for member in &body.members { + self.resolve_type_ref(&member.ty); + } + } + } + + fn resolve_block(&mut self, n: &BlockNode) { + self.enter_scope(); + for stmt in &n.stmts { + self.resolve_node(stmt); + } + self.exit_scope(); + } + + fn resolve_let_stmt(&mut self, n: &LetStmtNode) { + if let Some(ty) = &n.ty { + self.resolve_type_ref(ty); + } + self.resolve_node(&n.init); + self.define_local(&n.name, n.span, SymbolKind::Local); + } + + fn resolve_type_ref(&mut self, node: &Node) { + self.resolve_node(node); + } + + fn resolve_identifier(&mut self, name: &str, span: Span, namespace: Namespace) -> Option { + // Built-ins (minimal for v0) + if namespace == Namespace::Type { + match name { + "int" | "float" | "string" | "bool" | "void" | "optional" | "result" => return None, + _ => {} + } + } + + // 1. local bindings + if namespace == Namespace::Value { + for scope in self.scopes.iter().rev() { + if let Some(sym) = scope.get(name) { + return Some(sym.clone()); + } + } + } + + let table = if namespace == Namespace::Type { + &self.current_module.type_symbols + } else { + &self.current_module.value_symbols + }; + + // 2 & 3. file-private and module symbols + if let Some(sym) = table.get(name) { + return Some(sym.clone()); + } + + // 4. imported symbols + let imp_table = if namespace == Namespace::Type { + &self.imported_symbols.type_symbols + } else { + &self.imported_symbols.value_symbols + }; + if let Some(sym) = imp_table.get(name) { + return Some(sym.clone()); + } + + self.error_undefined(name, span); + None + } + + fn define_local(&mut self, name: &str, span: Span, kind: SymbolKind) { + let scope = self.scopes.last_mut().expect("No scope to define local"); + + // Check for collision in Type namespace at top-level? + // Actually, the spec says "A name may not exist in both namespaces". + // If we want to be strict, we check current module's type symbols too. + if self.current_module.type_symbols.get(name).is_some() { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some("E_RESOLVE_NAMESPACE_COLLISION".to_string()), + message: format!("Local variable '{}' collides with a type name", name), + span: Some(span), + }); + return; + } + + if scope.contains_key(name) { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()), + message: format!("Duplicate local variable '{}'", name), + span: Some(span), + }); + } else { + scope.insert(name.to_string(), Symbol { + name: name.to_string(), + kind, + namespace: Namespace::Value, + visibility: Visibility::FilePrivate, + span, + }); + } + } + + fn enter_scope(&mut self) { + self.scopes.push(HashMap::new()); + } + + fn exit_scope(&mut self) { + self.scopes.pop(); + } + + fn error_undefined(&mut self, name: &str, span: Span) { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some("E_RESOLVE_UNDEFINED".to_string()), + message: format!("Undefined identifier: {}", name), + span: Some(span), + }); + } + + fn error_duplicate_import(&mut self, name: &str, span: Span) { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()), + message: format!("Duplicate import: {}", name), + span: Some(span), + }); + } + + fn error_visibility(&mut self, sym: &Symbol, span: Span) { + self.diagnostics.push(Diagnostic { + level: DiagnosticLevel::Error, + code: Some("E_RESOLVE_VISIBILITY".to_string()), + message: format!("Symbol '{}' is not visible here", sym.name), + span: Some(span), + }); + } +} diff --git a/crates/prometeu-compiler/src/frontends/pbs/symbols.rs b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs new file mode 100644 index 00000000..47868dc7 --- /dev/null +++ b/crates/prometeu-compiler/src/frontends/pbs/symbols.rs @@ -0,0 +1,74 @@ +use crate::common::spans::Span; +use std::collections::HashMap; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Visibility { + FilePrivate, + Mod, + Pub, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SymbolKind { + Function, + Service, + Struct, + Contract, + ErrorType, + Local, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Namespace { + Type, + Value, +} + +#[derive(Debug, Clone)] +pub struct Symbol { + pub name: String, + pub kind: SymbolKind, + pub namespace: Namespace, + pub visibility: Visibility, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct SymbolTable { + pub symbols: HashMap, +} + +#[derive(Debug, Clone)] +pub struct ModuleSymbols { + pub type_symbols: SymbolTable, + pub value_symbols: SymbolTable, +} + +impl ModuleSymbols { + pub fn new() -> Self { + Self { + type_symbols: SymbolTable::new(), + value_symbols: SymbolTable::new(), + } + } +} + +impl SymbolTable { + pub fn new() -> Self { + Self { + symbols: HashMap::new(), + } + } + + pub fn insert(&mut self, symbol: Symbol) -> Result<(), Symbol> { + if let Some(existing) = self.symbols.get(&symbol.name) { + return Err(existing.clone()); + } + self.symbols.insert(symbol.name.clone(), symbol); + Ok(()) + } + + pub fn get(&self, name: &str) -> Option<&Symbol> { + self.symbols.get(name) + } +} diff --git a/crates/prometeu-compiler/tests/pbs_resolver_tests.rs b/crates/prometeu-compiler/tests/pbs_resolver_tests.rs new file mode 100644 index 00000000..347ce42d --- /dev/null +++ b/crates/prometeu-compiler/tests/pbs_resolver_tests.rs @@ -0,0 +1,196 @@ +use prometeu_compiler::frontends::pbs::*; +use prometeu_compiler::common::files::FileManager; +use prometeu_compiler::common::spans::Span; +use std::path::PathBuf; + +fn setup_test(source: &str) -> (ast::FileNode, usize) { + let mut fm = FileManager::new(); + let file_id = fm.add(PathBuf::from("test.pbs"), source.to_string()); + let mut parser = parser::Parser::new(source, file_id); + (parser.parse_file().expect("Parsing failed"), file_id) +} + +#[test] +fn test_duplicate_symbols() { + let source = " + declare struct Foo {} + declare struct Foo {} + "; + let (ast, _) = setup_test(source); + let mut collector = SymbolCollector::new(); + let result = collector.collect(&ast); + + assert!(result.is_err()); + let bundle = result.unwrap_err(); + assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_DUPLICATE_SYMBOL".to_string()))); +} + +#[test] +fn test_namespace_collision() { + let source = " + declare struct Foo {} + fn Foo() {} + "; + let (ast, _) = setup_test(source); + let mut collector = SymbolCollector::new(); + let result = collector.collect(&ast); + + assert!(result.is_err()); + let bundle = result.unwrap_err(); + assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_NAMESPACE_COLLISION".to_string()))); +} + +#[test] +fn test_undefined_identifier() { + let source = " + fn main() { + let x = y; + } + "; + let (ast, _) = setup_test(source); + let mut collector = SymbolCollector::new(); + let (ts, vs) = collector.collect(&ast).expect("Collection failed"); + let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; + + struct EmptyProvider; + impl ModuleProvider for EmptyProvider { + fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } + } + + let mut resolver = Resolver::new(&ms, &EmptyProvider); + let result = resolver.resolve(&ast); + + assert!(result.is_err()); + let bundle = result.unwrap_err(); + assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_UNDEFINED".to_string()))); +} + +#[test] +fn test_local_variable_resolution() { + let source = " + fn main() { + let x = 10; + let y = x; + } + "; + let (ast, _) = setup_test(source); + let mut collector = SymbolCollector::new(); + let (ts, vs) = collector.collect(&ast).expect("Collection failed"); + let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; + + struct EmptyProvider; + impl ModuleProvider for EmptyProvider { + fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } + } + + let mut resolver = Resolver::new(&ms, &EmptyProvider); + let result = resolver.resolve(&ast); + + assert!(result.is_ok()); +} + +#[test] +fn test_visibility_error() { + let source = " + import PrivateType from \"./other.pbs\" + fn main() {} + "; + let (ast, _) = setup_test(source); + let mut collector = SymbolCollector::new(); + let (ts, vs) = collector.collect(&ast).expect("Collection failed"); + let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; + + struct MockProvider { + other: ModuleSymbols, + } + impl ModuleProvider for MockProvider { + fn get_module_symbols(&self, path: &str) -> Option<&ModuleSymbols> { + if path == "./other.pbs" { Some(&self.other) } else { None } + } + } + + let mut other_ts = SymbolTable::new(); + other_ts.insert(Symbol { + name: "PrivateType".to_string(), + kind: SymbolKind::Struct, + namespace: Namespace::Type, + visibility: Visibility::FilePrivate, + span: Span::new(1, 0, 0), + }).unwrap(); + + let mock_provider = MockProvider { + other: ModuleSymbols { type_symbols: other_ts, value_symbols: SymbolTable::new() }, + }; + + let mut resolver = Resolver::new(&ms, &mock_provider); + let result = resolver.resolve(&ast); + + assert!(result.is_err()); + let bundle = result.unwrap_err(); + assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_VISIBILITY".to_string()))); +} + +#[test] +fn test_import_resolution() { + let source = " + import PubType from \"./other.pbs\" + fn main() { + let x: PubType = 10; + } + "; + let (ast, _) = setup_test(source); + let mut collector = SymbolCollector::new(); + let (ts, vs) = collector.collect(&ast).expect("Collection failed"); + let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; + + struct MockProvider { + other: ModuleSymbols, + } + impl ModuleProvider for MockProvider { + fn get_module_symbols(&self, path: &str) -> Option<&ModuleSymbols> { + if path == "./other.pbs" { Some(&self.other) } else { None } + } + } + + let mut other_ts = SymbolTable::new(); + other_ts.insert(Symbol { + name: "PubType".to_string(), + kind: SymbolKind::Struct, + namespace: Namespace::Type, + visibility: Visibility::Pub, + span: Span::new(1, 0, 0), + }).unwrap(); + + let mock_provider = MockProvider { + other: ModuleSymbols { type_symbols: other_ts, value_symbols: SymbolTable::new() }, + }; + + let mut resolver = Resolver::new(&ms, &mock_provider); + let result = resolver.resolve(&ast); + + assert!(result.is_ok()); +} + +#[test] +fn test_invalid_import_module_not_found() { + let source = " + import NonExistent from \"./missing.pbs\" + fn main() {} + "; + let (ast, _) = setup_test(source); + let mut collector = SymbolCollector::new(); + let (ts, vs) = collector.collect(&ast).expect("Collection failed"); + let ms = ModuleSymbols { type_symbols: ts, value_symbols: vs }; + + struct EmptyProvider; + impl ModuleProvider for EmptyProvider { + fn get_module_symbols(&self, _path: &str) -> Option<&ModuleSymbols> { None } + } + + let mut resolver = Resolver::new(&ms, &EmptyProvider); + let result = resolver.resolve(&ast); + + assert!(result.is_err()); + let bundle = result.unwrap_err(); + assert!(bundle.diagnostics.iter().any(|d| d.code == Some("E_RESOLVE_INVALID_IMPORT".to_string()))); +}