use std::collections::HashSet;
use super::{for_each_child_block, hoisted_names, Expr, InterpPart, Params, Span, Stmt};
pub fn expr_idents(expr: &Expr, out: &mut HashSet<String>) {
match expr {
Expr::Ident { name, .. } => {
out.insert(name.clone());
}
Expr::List { items, .. } => {
for item in items {
expr_idents(item, out);
}
}
Expr::Dict { entries, .. } => {
for (key, value) in entries {
expr_idents(key, out);
expr_idents(value, out);
}
}
Expr::Binary { lhs, rhs, .. } => {
expr_idents(lhs, out);
expr_idents(rhs, out);
}
Expr::Unary { operand, .. } => expr_idents(operand, out),
Expr::Call { callee, args, .. } => {
expr_idents(callee, out);
for arg in args {
expr_idents(arg, out);
}
}
Expr::Index { obj, index, .. } => {
expr_idents(obj, out);
expr_idents(index, out);
}
Expr::Slice {
obj,
start,
end,
step,
..
} => {
expr_idents(obj, out);
for part in [start, end, step].into_iter().flatten() {
expr_idents(part, out);
}
}
Expr::Ternary {
cond,
then,
otherwise,
..
} => {
expr_idents(cond, out);
expr_idents(then, out);
expr_idents(otherwise, out);
}
Expr::Attr { obj, .. } => expr_idents(obj, out),
Expr::SuperCall { args, .. } => {
for arg in args {
expr_idents(arg, out);
}
}
Expr::StrInterp { parts, .. } => {
for part in parts {
if let InterpPart::Expr(hole) = part {
expr_idents(hole, out);
}
}
}
Expr::Int { .. }
| Expr::Float { .. }
| Expr::Str { .. }
| Expr::Bool { .. }
| Expr::None { .. } => {}
}
}
pub fn collect_used(stmts: &[Stmt], used: &mut HashSet<String>) {
for stmt in stmts {
match stmt {
Stmt::Decl { expr, .. }
| Stmt::ConstDecl { expr, .. }
| Stmt::Bark { expr, .. }
| Stmt::Bonk { expr, .. }
| Stmt::ExprStmt { expr } => expr_idents(expr, used),
Stmt::Assign {
targets,
rest,
expr,
..
} => {
for target in targets {
expr_idents(target, used);
}
if let Some(rest) = rest {
expr_idents(rest, used);
}
expr_idents(expr, used);
}
Stmt::If {
branches,
else_body,
..
} => {
for (cond, body) in branches {
expr_idents(cond, used);
collect_used(body, used);
}
if let Some(body) = else_body {
collect_used(body, used);
}
}
Stmt::For { iter, body, .. } => {
expr_idents(iter, used);
collect_used(body, used);
}
Stmt::While { cond, body, .. } => {
expr_idents(cond, used);
collect_used(body, used);
}
Stmt::Try { body, handler, .. } => {
collect_used(body, used);
collect_used(handler, used);
}
Stmt::Return { expr, .. } => {
if let Some(expr) = expr {
expr_idents(expr, used);
}
}
Stmt::Amaze { cond, message, .. } => {
expr_idents(cond, used);
if let Some(message) = message {
expr_idents(message, used);
}
}
Stmt::FuncDef { params, body, .. } => {
for name in free_names(¶ms.binding_names(), body) {
used.insert(name);
}
}
Stmt::Import { .. }
| Stmt::Bork { .. }
| Stmt::Continue { .. }
| Stmt::ObjDef { .. } => {}
}
}
}
pub fn free_names(params: &[String], body: &[Stmt]) -> HashSet<String> {
let mut bound: HashSet<String> = params.iter().cloned().collect();
for name in hoisted_names(body) {
bound.insert(name);
}
let mut used = HashSet::new();
collect_used(body, &mut used);
used.retain(|name| !bound.contains(name));
used
}
pub fn child_funcdefs(stmts: &[Stmt]) -> Vec<(&str, &Params, &[Stmt], Span)> {
let mut out = Vec::new();
collect_child_funcdefs(stmts, &mut out);
out
}
pub fn collect_child_funcdefs<'a>(
stmts: &'a [Stmt],
out: &mut Vec<(&'a str, &'a Params, &'a [Stmt], Span)>,
) {
for stmt in stmts {
if let Stmt::FuncDef {
name,
params,
body,
span,
} = stmt
{
out.push((name, params, body, *span));
}
for_each_child_block(stmt, &mut |block| collect_child_funcdefs(block, out));
}
}
pub fn celled_locals(params: &[String], body: &[Stmt]) -> HashSet<String> {
let mut candidates: HashSet<String> = params.iter().cloned().collect();
for name in hoisted_names(body) {
candidates.insert(name);
}
let mut child_free: HashSet<String> = HashSet::new();
for (_, child_params, child_body, _) in child_funcdefs(body) {
for name in free_names(&child_params.binding_names(), child_body) {
child_free.insert(name);
}
}
let funcnames: HashSet<&str> = child_funcdefs(body)
.iter()
.map(|(name, _, _, _)| *name)
.collect();
candidates
.into_iter()
.filter(|name| funcnames.contains(name.as_str()) || child_free.contains(name))
.collect()
}