dev/pbs #8
138
crates/prometeu-compiler/src/frontends/pbs/collector.rs
Normal file
138
crates/prometeu-compiler/src/frontends/pbs/collector.rs
Normal file
@ -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<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
319
crates/prometeu-compiler/src/frontends/pbs/resolver.rs
Normal file
319
crates/prometeu-compiler/src/frontends/pbs/resolver.rs
Normal file
@ -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<HashMap<String, Symbol>>,
|
||||||
|
imported_symbols: ModuleSymbols,
|
||||||
|
diagnostics: Vec<Diagnostic>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Symbol> {
|
||||||
|
// 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),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
74
crates/prometeu-compiler/src/frontends/pbs/symbols.rs
Normal file
74
crates/prometeu-compiler/src/frontends/pbs/symbols.rs
Normal file
@ -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<String, Symbol>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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)
|
||||||
|
}
|
||||||
|
}
|
||||||
196
crates/prometeu-compiler/tests/pbs_resolver_tests.rs
Normal file
196
crates/prometeu-compiler/tests/pbs_resolver_tests.rs
Normal file
@ -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())));
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user