use crate::ast::{ExportItem, Item, Program, Span};
use crate::error::{Result, ShapeError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModuleExportKind {
Function,
BuiltinFunction,
TypeAlias,
BuiltinType,
Interface,
Enum,
Annotation,
Value,
}
#[derive(Debug, Clone)]
pub struct ModuleExportSymbol {
pub name: String,
pub alias: Option<String>,
pub kind: ModuleExportKind,
pub span: Span,
}
pub fn direct_export_target(export_item: &ExportItem) -> Option<(String, ModuleExportKind)> {
match export_item {
ExportItem::Function(function) => {
Some((function.name.clone(), ModuleExportKind::Function))
}
ExportItem::BuiltinFunction(function) => {
Some((function.name.clone(), ModuleExportKind::BuiltinFunction))
}
ExportItem::BuiltinType(type_decl) => {
Some((type_decl.name.clone(), ModuleExportKind::BuiltinType))
}
ExportItem::TypeAlias(alias) => Some((alias.name.clone(), ModuleExportKind::TypeAlias)),
ExportItem::Enum(enum_def) => Some((enum_def.name.clone(), ModuleExportKind::Enum)),
ExportItem::Struct(struct_def) => {
Some((struct_def.name.clone(), ModuleExportKind::TypeAlias))
}
ExportItem::Interface(interface) => {
Some((interface.name.clone(), ModuleExportKind::Interface))
}
ExportItem::Trait(trait_def) => {
Some((trait_def.name.clone(), ModuleExportKind::Interface))
}
ExportItem::Annotation(annotation) => {
Some((annotation.name.clone(), ModuleExportKind::Annotation))
}
ExportItem::ForeignFunction(function) => {
Some((function.name.clone(), ModuleExportKind::Function))
}
ExportItem::Named(_) => None,
}
}
pub fn strip_import_items(items: Vec<Item>) -> Vec<Item> {
items
.into_iter()
.filter(|item| !matches!(item, Item::Import(..)))
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ScopeSymbolKind {
Function,
BuiltinFunction,
TypeAlias,
BuiltinType,
Interface,
Enum,
Annotation,
Value,
}
fn scope_symbol_kind_to_export(kind: ScopeSymbolKind) -> ModuleExportKind {
match kind {
ScopeSymbolKind::Function => ModuleExportKind::Function,
ScopeSymbolKind::BuiltinFunction => ModuleExportKind::BuiltinFunction,
ScopeSymbolKind::TypeAlias => ModuleExportKind::TypeAlias,
ScopeSymbolKind::BuiltinType => ModuleExportKind::BuiltinType,
ScopeSymbolKind::Interface => ModuleExportKind::Interface,
ScopeSymbolKind::Enum => ModuleExportKind::Enum,
ScopeSymbolKind::Annotation => ModuleExportKind::Annotation,
ScopeSymbolKind::Value => ModuleExportKind::Value,
}
}
struct ScopeTable {
symbols: std::collections::HashMap<String, (ScopeSymbolKind, Span)>,
}
impl ScopeTable {
fn from_program(program: &Program) -> Self {
let mut symbols = std::collections::HashMap::new();
for item in &program.items {
match item {
Item::Function(f, span) => {
symbols.insert(f.name.clone(), (ScopeSymbolKind::Function, *span));
}
Item::BuiltinFunctionDecl(f, span) => {
symbols.insert(f.name.clone(), (ScopeSymbolKind::BuiltinFunction, *span));
}
Item::BuiltinTypeDecl(t, span) => {
symbols.insert(t.name.clone(), (ScopeSymbolKind::BuiltinType, *span));
}
Item::TypeAlias(a, span) => {
symbols.insert(a.name.clone(), (ScopeSymbolKind::TypeAlias, *span));
}
Item::Enum(e, span) => {
symbols.insert(e.name.clone(), (ScopeSymbolKind::Enum, *span));
}
Item::StructType(s, span) => {
symbols.insert(s.name.clone(), (ScopeSymbolKind::TypeAlias, *span));
}
Item::Interface(i, span) => {
symbols.insert(i.name.clone(), (ScopeSymbolKind::Interface, *span));
}
Item::Trait(t, span) => {
symbols.insert(t.name.clone(), (ScopeSymbolKind::Interface, *span));
}
Item::VariableDecl(decl, span) => {
if let Some(name) = decl.pattern.as_identifier() {
symbols.insert(name.to_string(), (ScopeSymbolKind::Value, *span));
}
}
Item::AnnotationDef(a, span) => {
symbols.insert(a.name.clone(), (ScopeSymbolKind::Annotation, *span));
}
_ => {}
}
}
Self { symbols }
}
fn resolve(&self, name: &str) -> Option<(ScopeSymbolKind, Span)> {
self.symbols.get(name).copied()
}
}
pub fn collect_exported_symbols(program: &Program) -> Result<Vec<ModuleExportSymbol>> {
let scope = ScopeTable::from_program(program);
let mut symbols = Vec::new();
for item in &program.items {
let Item::Export(export, _) = item else {
continue;
};
if let Some((name, kind)) = direct_export_target(&export.item) {
let span = match &export.item {
ExportItem::Function(f) => f.name_span,
ExportItem::BuiltinFunction(f) => f.name_span,
ExportItem::Annotation(a) => a.name_span,
ExportItem::ForeignFunction(f) => f.name_span,
_ => scope
.resolve(&name)
.map(|(_, span)| span)
.unwrap_or_default(),
};
symbols.push(ModuleExportSymbol {
name,
alias: None,
kind,
span,
});
continue;
}
if let ExportItem::Named(specs) = &export.item {
for spec in specs {
match scope.resolve(&spec.name) {
Some((kind, span)) => {
if kind == ScopeSymbolKind::Value {
return Err(ShapeError::ModuleError {
message: format!(
"Cannot export variable '{}': variable exports are not yet supported. \
Only functions and types can be exported.",
spec.name
),
module_path: None,
});
}
symbols.push(ModuleExportSymbol {
name: spec.name.clone(),
alias: spec.alias.clone(),
kind: scope_symbol_kind_to_export(kind),
span,
});
}
None => {
return Err(ShapeError::ModuleError {
message: format!(
"Cannot export '{}': not found in module scope",
spec.name
),
module_path: None,
});
}
}
}
}
}
Ok(symbols)
}
pub fn export_kind_description(kind: ModuleExportKind) -> &'static str {
match kind {
ModuleExportKind::Function => "a function",
ModuleExportKind::BuiltinFunction => "a builtin function",
ModuleExportKind::TypeAlias => "a type",
ModuleExportKind::BuiltinType => "a builtin type",
ModuleExportKind::Interface => "an interface",
ModuleExportKind::Enum => "an enum",
ModuleExportKind::Annotation => "an annotation",
ModuleExportKind::Value => "a value",
}
}