use crate::kind::NodeKind;
use crate::lang::Lang;
pub fn local_writes<'a, L: Lang>(lang: &'a L, id: L::Id, out: &mut crate::walk::StrSet<'a>) {
match lang.kind(id) {
NodeKind::Assign => {
let children = lang.children(id);
if let Some(&t) = children.first() {
if lang.kind(t) == NodeKind::VarRef {
if let Some(n) = lang.var_name(t) {
out.insert(n);
}
}
}
if let Some(&v) = children.get(1) {
local_writes(lang, v, out);
}
}
NodeKind::Unary => {
if let Some(op) = lang.un_op(id) {
if op.is_incdec() {
let children = lang.children(id);
if let Some(&t) = children.first() {
if lang.kind(t) == NodeKind::VarRef {
if let Some(n) = lang.var_name(t) {
out.insert(n);
}
}
}
}
}
for &c in lang.children(id) {
local_writes(lang, c, out);
}
}
NodeKind::VarDecl => {
if let Some(n) = lang.var_name(id) {
out.insert(n);
}
for &c in lang.children(id) {
local_writes(lang, c, out);
}
}
_ => {
for &c in lang.children(id) {
local_writes(lang, c, out);
}
}
}
}
pub fn reads_vars<'a, L: Lang>(lang: &'a L, id: L::Id, out: &mut crate::walk::StrSet<'a>) {
if lang.kind(id) == NodeKind::VarRef {
if let Some(n) = lang.var_name(id) {
out.insert(n);
}
}
for &c in lang.children(id) {
reads_vars(lang, c, out);
}
}
pub fn subtree_contains<L: Lang>(lang: &L, id: L::Id, pred: impl Fn(L::Id) -> bool) -> bool {
let mut stack = vec![id];
while let Some(n) = stack.pop() {
if pred(n) {
return true;
}
for &c in lang.children(n) {
stack.push(c);
}
}
false
}
pub fn prefix_effects_readable<L: Lang>(lang: &L, root: L::Id, target: L::Id) -> bool {
fn go<L: Lang>(lang: &L, node: L::Id, target: L::Id) -> bool {
if node == target {
return true;
}
for &c in lang.children(node) {
if crate::analysis::subtree_contains(lang, c, |n| n == target) {
return go(lang, c, target);
} else {
if lang.effect(c) > crate::effect::Effect::MayRead {
return false;
}
}
}
true
}
go(lang, root, target)
}
pub fn structurally_equal<L: Lang>(lang: &L, a: L::Id, b: L::Id) -> bool {
if lang.kind(a) != lang.kind(b) {
return false;
}
if lang.bin_op(a) != lang.bin_op(b) || lang.un_op(a) != lang.un_op(b) {
return false;
}
if lang.var_name(a) != lang.var_name(b) {
return false;
}
if lang.member_name(a) != lang.member_name(b) {
return false;
}
if !lang.payload_equal(a, b) {
return false;
}
match (lang.literal(a), lang.literal(b)) {
(Some(x), Some(y)) if x == y => {}
(None, None) => {}
_ => return false,
}
let (ca, cb) = (lang.children(a), lang.children(b));
if ca.len() != cb.len() {
return false;
}
ca.iter().zip(cb.iter()).all(|(&x, &y)| structurally_equal(lang, x, y))
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TreeMetrics {
pub nodes: u64,
pub decisions: u64,
pub max_depth: u32,
}
impl TreeMetrics {
pub fn add(&mut self, other: TreeMetrics) {
self.nodes += other.nodes;
self.decisions += other.decisions;
self.max_depth = self.max_depth.max(other.max_depth);
}
}
fn is_container(k: NodeKind) -> bool {
matches!(
k,
NodeKind::Block
| NodeKind::If
| NodeKind::While
| NodeKind::DoWhile
| NodeKind::For
| NodeKind::ForEach
| NodeKind::Try
| NodeKind::Catch
| NodeKind::Switch
| NodeKind::Synchronized
)
}
fn is_decision_kind(k: NodeKind) -> bool {
matches!(
k,
NodeKind::If
| NodeKind::While
| NodeKind::DoWhile
| NodeKind::For
| NodeKind::ForEach
| NodeKind::Case
| NodeKind::Catch
| NodeKind::Ternary
)
}
pub fn subtree_metrics<L: Lang>(lang: &L, root: L::Id) -> TreeMetrics {
let mut m = TreeMetrics::default();
walk_metrics(lang, root, 0, &mut m);
m
}
fn walk_metrics<L: Lang>(lang: &L, id: L::Id, depth: u32, m: &mut TreeMetrics) {
m.nodes += 1;
let k = lang.kind(id);
if is_decision_kind(k) {
m.decisions += 1;
}
if k == NodeKind::Binary && lang.bin_op(id).is_some_and(|o| o.is_short_circuit()) {
m.decisions += 1;
}
let child_depth = if is_container(k) { depth + 1 } else { depth };
if child_depth > m.max_depth {
m.max_depth = child_depth;
}
for &c in lang.children(id) {
walk_metrics(lang, c, child_depth, m);
}
}