use crate::ast::{FnDef, Module, TopLevel, TypeDef, TypeVariant};
#[derive(Debug, Clone)]
pub struct ModuleTypeDef {
pub bare_name: String,
pub kind: ModuleTypeKind,
}
#[derive(Debug, Clone)]
pub enum ModuleTypeKind {
Record { field_names: Vec<String> },
Sum { variant_names: Vec<String> },
}
pub fn collect_module_types(items: &[TopLevel]) -> Vec<ModuleTypeDef> {
items
.iter()
.filter_map(|item| {
let TopLevel::TypeDef(td) = item else {
return None;
};
Some(match td {
TypeDef::Product { name, fields, .. } => ModuleTypeDef {
bare_name: name.clone(),
kind: ModuleTypeKind::Record {
field_names: fields.iter().map(|(n, _)| n.clone()).collect(),
},
},
TypeDef::Sum { name, variants, .. } => ModuleTypeDef {
bare_name: name.clone(),
kind: ModuleTypeKind::Sum {
variant_names: variants.iter().map(|v| v.name.clone()).collect(),
},
},
})
})
.collect()
}
pub fn is_exposed(name: &str, exposes: Option<&[String]>) -> bool {
match exposes {
Some(list) => list.iter().any(|e| e == name),
None => !name.starts_with('_'),
}
}
pub struct ExportedTypeDef<'a> {
pub def: &'a TypeDef,
pub is_opaque: bool,
}
pub struct ModuleExports<'a> {
pub functions: Vec<&'a FnDef>,
pub types: Vec<ExportedTypeDef<'a>>,
}
pub fn module_decl(items: &[TopLevel]) -> Option<&Module> {
items.iter().find_map(|i| {
if let TopLevel::Module(m) = i {
Some(m)
} else {
None
}
})
}
pub fn collect_module_exports<'a>(items: &'a [TopLevel]) -> ModuleExports<'a> {
let module = module_decl(items);
let exposes: Option<&[String]> = module.and_then(|m| {
if m.exposes.is_empty() {
None
} else {
Some(m.exposes.as_slice())
}
});
let opaque_names: Vec<&str> = module
.map(|m| m.exposes_opaque.iter().map(|s| s.as_str()).collect())
.unwrap_or_default();
let functions = items
.iter()
.filter_map(|item| {
let TopLevel::FnDef(fd) = item else {
return None;
};
if is_exposed(&fd.name, exposes) {
Some(fd)
} else {
None
}
})
.collect();
let types = items
.iter()
.filter_map(|item| {
let TopLevel::TypeDef(td) = item else {
return None;
};
let name = match td {
TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name.as_str(),
};
let is_opaque = opaque_names.contains(&name);
if is_exposed(name, exposes) || is_opaque {
Some(ExportedTypeDef { def: td, is_opaque })
} else {
None
}
})
.collect();
ModuleExports { functions, types }
}
pub fn collect_all_module_symbols<'a>(items: &'a [TopLevel]) -> ModuleExports<'a> {
let functions = items
.iter()
.filter_map(|item| {
if let TopLevel::FnDef(fd) = item {
Some(fd)
} else {
None
}
})
.collect();
let module = module_decl(items);
let opaque_names: Vec<&str> = module
.map(|m| m.exposes_opaque.iter().map(|s| s.as_str()).collect())
.unwrap_or_default();
let types = items
.iter()
.filter_map(|item| {
let TopLevel::TypeDef(td) = item else {
return None;
};
let name = match td {
TypeDef::Sum { name, .. } | TypeDef::Product { name, .. } => name.as_str(),
};
Some(ExportedTypeDef {
def: td,
is_opaque: opaque_names.contains(&name),
})
})
.collect();
ModuleExports { functions, types }
}
pub fn qualified_name(module: &str, name: &str) -> String {
format!("{}.{}", module, name)
}
pub fn member_key(type_name: &str, member: &str) -> String {
format!("{}.{}", type_name, member)
}
pub fn qualified_member_key(module: &str, type_name: &str, member: &str) -> String {
format!("{}.{}.{}", module, type_name, member)
}
#[derive(Debug, Clone)]
pub struct SymbolEntry {
pub id: u32,
pub canonical_name: String,
pub alias: Option<String>,
pub module: String,
pub kind: SymbolKind,
}
#[derive(Debug, Clone)]
pub enum SymbolKind {
Function {
name: String,
params: Vec<(String, String)>,
return_type: String,
effects: Vec<String>,
},
OpaqueType {
name: String,
},
SumType {
name: String,
variants: Vec<String>,
},
Constructor {
type_name: String,
variant_name: String,
field_types: Vec<String>,
},
RecordField {
type_name: String,
field_name: String,
field_type: String,
},
}
#[derive(Debug, Clone, Default)]
pub struct SymbolRegistry {
pub entries: Vec<SymbolEntry>,
}
impl SymbolRegistry {
pub fn from_modules(modules: &[(String, Vec<TopLevel>)]) -> Self {
let mut entries = Vec::new();
for (module_name, items) in modules {
let exports = collect_module_exports(items);
Self::collect_from_exports(module_name, &exports, &mut entries);
}
SymbolRegistry { entries }
}
pub fn from_modules_all(modules: &[(String, Vec<TopLevel>)]) -> Self {
let mut entries = Vec::new();
for (module_name, items) in modules {
let all = collect_all_module_symbols(items);
Self::collect_from_exports(module_name, &all, &mut entries);
}
SymbolRegistry { entries }
}
fn collect_from_exports(
module_name: &str,
exports: &ModuleExports<'_>,
entries: &mut Vec<SymbolEntry>,
) {
for fd in &exports.functions {
let id = entries.len() as u32;
entries.push(SymbolEntry {
id,
canonical_name: qualified_name(module_name, &fd.name),
alias: None,
module: module_name.to_string(),
kind: SymbolKind::Function {
name: fd.name.clone(),
params: fd.params.clone(),
return_type: fd.return_type.clone(),
effects: fd.effects.iter().map(|e| e.node.clone()).collect(),
},
});
}
for et in &exports.types {
match et.def {
TypeDef::Sum {
name: type_name,
variants,
..
} => {
if et.is_opaque {
let id = entries.len() as u32;
entries.push(SymbolEntry {
id,
canonical_name: type_name.clone(),
alias: None,
module: module_name.to_string(),
kind: SymbolKind::OpaqueType {
name: type_name.clone(),
},
});
} else {
let id = entries.len() as u32;
entries.push(SymbolEntry {
id,
canonical_name: type_name.clone(),
alias: None,
module: module_name.to_string(),
kind: SymbolKind::SumType {
name: type_name.clone(),
variants: variants.iter().map(|v| v.name.clone()).collect(),
},
});
Self::collect_variant_entries(
module_name,
type_name.as_str(),
variants,
entries,
);
}
}
TypeDef::Product {
name: type_name,
fields,
..
} => {
if et.is_opaque {
let id = entries.len() as u32;
entries.push(SymbolEntry {
id,
canonical_name: type_name.clone(),
alias: None,
module: module_name.to_string(),
kind: SymbolKind::OpaqueType {
name: type_name.clone(),
},
});
} else {
for (field_name, ty_str) in fields {
let id = entries.len() as u32;
let canonical =
qualified_member_key(module_name, type_name, field_name);
let alias = member_key(type_name, field_name);
entries.push(SymbolEntry {
id,
canonical_name: canonical,
alias: Some(alias),
module: module_name.to_string(),
kind: SymbolKind::RecordField {
type_name: type_name.clone(),
field_name: field_name.clone(),
field_type: ty_str.clone(),
},
});
}
}
}
}
}
}
fn collect_variant_entries(
module_name: &str,
type_name: &str,
variants: &[TypeVariant],
entries: &mut Vec<SymbolEntry>,
) {
for variant in variants {
let id = entries.len() as u32;
let canonical = qualified_member_key(module_name, type_name, &variant.name);
let alias = member_key(type_name, &variant.name);
entries.push(SymbolEntry {
id,
canonical_name: canonical,
alias: Some(alias),
module: module_name.to_string(),
kind: SymbolKind::Constructor {
type_name: type_name.to_string(),
variant_name: variant.name.clone(),
field_types: variant.fields.clone(),
},
});
}
}
}