use perl_ast::{Node, NodeKind};
use perl_symbol_types::{SymbolKind, VarKind};
#[derive(Debug, Clone, PartialEq)]
pub struct SymbolDecl {
pub kind: SymbolKind,
pub name: String,
pub qualified_name: String,
pub full_span: (usize, usize),
pub anchor_span: Option<(usize, usize)>,
pub container: Option<String>,
}
pub fn extract_symbol_decls(root: &Node, current_package: Option<&str>) -> Vec<SymbolDecl> {
let mut out = Vec::new();
let mut ctx = WalkCtx { current_package: current_package.map(str::to_owned) };
walk(root, &mut ctx, &mut out);
out
}
struct WalkCtx {
current_package: Option<String>,
}
impl WalkCtx {
fn qualify(&self, name: &str) -> String {
match &self.current_package {
Some(pkg) => format!("{}::{}", pkg, name),
None => name.to_owned(),
}
}
}
fn walk(node: &Node, ctx: &mut WalkCtx, out: &mut Vec<SymbolDecl>) {
match &node.kind {
NodeKind::Package { name, name_span, block } => {
let anchor = Some((name_span.start, name_span.end));
let container = ctx.current_package.clone();
out.push(SymbolDecl {
kind: SymbolKind::Package,
name: name.clone(),
qualified_name: name.clone(),
full_span: (node.location.start, node.location.end),
anchor_span: anchor,
container,
});
if let Some(blk) = block {
let saved = ctx.current_package.replace(name.clone());
walk(blk, ctx, out);
ctx.current_package = saved;
} else {
ctx.current_package = Some(name.clone());
}
}
NodeKind::Class { name, body } => {
let container = ctx.current_package.clone();
out.push(SymbolDecl {
kind: SymbolKind::Class,
name: name.clone(),
qualified_name: ctx.qualify(name),
full_span: (node.location.start, node.location.end),
anchor_span: None, container,
});
let saved = ctx.current_package.replace(name.clone());
walk(body, ctx, out);
ctx.current_package = saved;
}
NodeKind::Subroutine { name: Some(sub_name), name_span, body, .. } => {
let anchor = name_span.as_ref().map(|s| (s.start, s.end));
let container = ctx.current_package.clone();
let qualified_name = ctx.qualify(sub_name);
out.push(SymbolDecl {
kind: SymbolKind::Subroutine,
name: sub_name.clone(),
qualified_name,
full_span: (node.location.start, node.location.end),
anchor_span: anchor,
container,
});
walk(body, ctx, out);
}
NodeKind::Subroutine { name: None, body, .. } => {
walk(body, ctx, out);
}
NodeKind::Method { name: method_name, body, .. } => {
let container = ctx.current_package.clone();
let qualified_name = ctx.qualify(method_name);
out.push(SymbolDecl {
kind: SymbolKind::Method,
name: method_name.clone(),
qualified_name,
full_span: (node.location.start, node.location.end),
anchor_span: None, container,
});
walk(body, ctx, out);
}
NodeKind::VariableDeclaration { variable, initializer, .. } => {
if let Some(decl) = variable_decl_from_node(variable, node, ctx) {
out.push(decl);
}
if let Some(init) = initializer {
walk(init, ctx, out);
}
}
NodeKind::VariableListDeclaration { variables, initializer, .. } => {
for var in variables {
if let Some(decl) = variable_decl_from_node(var, node, ctx) {
out.push(decl);
}
}
if let Some(init) = initializer {
walk(init, ctx, out);
}
}
NodeKind::Use { module, args, .. } if module == "constant" => {
if let Some(const_name) = args.first() {
if !const_name.is_empty() && !const_name.starts_with('{') {
let container = ctx.current_package.clone();
out.push(SymbolDecl {
kind: SymbolKind::Constant,
name: const_name.clone(),
qualified_name: ctx.qualify(const_name),
full_span: (node.location.start, node.location.end),
anchor_span: None, container,
});
}
}
}
NodeKind::Program { statements } | NodeKind::Block { statements } => {
walk_statements(statements, ctx, out);
}
NodeKind::ExpressionStatement { expression } => {
walk(expression, ctx, out);
}
_ => {}
}
}
fn walk_statements(statements: &[Node], ctx: &mut WalkCtx, out: &mut Vec<SymbolDecl>) {
for stmt in statements {
walk(stmt, ctx, out);
}
}
fn variable_decl_from_node(var_node: &Node, decl_node: &Node, ctx: &WalkCtx) -> Option<SymbolDecl> {
match &var_node.kind {
NodeKind::Variable { sigil, name } => {
let kind = sigil_to_symbol_kind(sigil);
let anchor_span = Some((var_node.location.start, var_node.location.end));
let container = ctx.current_package.clone();
Some(SymbolDecl {
kind,
name: name.clone(),
qualified_name: ctx.qualify(name),
full_span: (decl_node.location.start, decl_node.location.end),
anchor_span,
container,
})
}
NodeKind::VariableWithAttributes { variable, .. } => {
variable_decl_from_node(variable, decl_node, ctx)
}
_ => None,
}
}
fn sigil_to_symbol_kind(sigil: &str) -> SymbolKind {
match sigil {
"@" => SymbolKind::Variable(VarKind::Array),
"%" => SymbolKind::Variable(VarKind::Hash),
_ => SymbolKind::Variable(VarKind::Scalar),
}
}