use crate::code_unit::CodeUnitKind;
use crate::extractor::SubUnit;
use crate::node::NodeKind;
use crate::node::NormalizedNode;
use crate::node::count_nodes;
use crate::node::reindex_placeholders;
pub struct SubUnitExtractor {
min_node_count: usize,
}
impl SubUnitExtractor {
#[must_use]
pub const fn new(min_node_count: usize) -> Self {
Self { min_node_count }
}
#[must_use]
pub fn extract(&self, node: &NormalizedNode) -> Vec<SubUnit> {
let mut found = self.own_units(node);
for child in &node.children {
found.extend(self.extract(child));
}
found
}
fn own_units(&self, node: &NormalizedNode) -> Vec<SubUnit> {
match &node.kind {
NodeKind::If => self.if_units(node),
NodeKind::Match => self.match_units(node),
_ => self.bodied_unit(node),
}
}
fn if_units(&self, node: &NormalizedNode) -> Vec<SubUnit> {
let then_branch = node
.children
.get(1)
.and_then(|n| self.made(n, CodeUnitKind::IfBranch, "if-then branch"));
let else_branch = node
.children
.get(2)
.filter(|n| !n.is_none())
.and_then(|n| self.made(n, CodeUnitKind::IfBranch, "if-else branch"));
then_branch.into_iter().chain(else_branch).collect()
}
fn match_units(&self, node: &NormalizedNode) -> Vec<SubUnit> {
node.children
.iter()
.skip(1)
.enumerate()
.filter_map(|(i, arm)| {
let body = arm.children.get(2)?;
self.made(
body,
CodeUnitKind::MatchArm,
&format!("match arm {}", i + 1),
)
})
.collect()
}
fn bodied_unit(&self, node: &NormalizedNode) -> Vec<SubUnit> {
let Some(target) = Self::body_target(&node.kind) else {
return Vec::new();
};
let (index, kind, description) = target;
node.children
.get(index)
.and_then(|body| self.made(body, kind, description))
.into_iter()
.collect()
}
const fn body_target(kind: &NodeKind) -> Option<(usize, CodeUnitKind, &'static str)> {
match kind {
NodeKind::Loop => Some((0, CodeUnitKind::LoopBody, "loop body")),
NodeKind::While => Some((1, CodeUnitKind::LoopBody, "while body")),
NodeKind::ForLoop => Some((2, CodeUnitKind::LoopBody, "for body")),
NodeKind::Closure => Some((0, CodeUnitKind::Block, "closure body")),
_ => None,
}
}
fn made(
&self,
node: &NormalizedNode,
kind: CodeUnitKind,
description: &str,
) -> Option<SubUnit> {
let reindexed = reindex_placeholders(node);
let node_count = count_nodes(&reindexed);
if node_count < self.min_node_count {
return None;
}
Some(SubUnit {
kind,
node: reindexed,
node_count,
description: description.to_string(),
})
}
}