1use crate::kind::NodeKind;
5use crate::lang::Lang;
6
7pub fn local_writes<'a, L: Lang>(lang: &'a L, id: L::Id, out: &mut crate::walk::StrSet<'a>) {
11 match lang.kind(id) {
12 NodeKind::Assign => {
13 let children = lang.children(id);
14 if let Some(&t) = children.first() {
15 if lang.kind(t) == NodeKind::VarRef {
16 if let Some(n) = lang.var_name(t) {
17 out.insert(n);
18 }
19 }
20 }
21 if let Some(&v) = children.get(1) {
22 local_writes(lang, v, out);
23 }
24 }
25 NodeKind::Unary => {
26 if let Some(op) = lang.un_op(id) {
27 if op.is_incdec() {
28 let children = lang.children(id);
29 if let Some(&t) = children.first() {
30 if lang.kind(t) == NodeKind::VarRef {
31 if let Some(n) = lang.var_name(t) {
32 out.insert(n);
33 }
34 }
35 }
36 }
37 }
38 for &c in lang.children(id) {
39 local_writes(lang, c, out);
40 }
41 }
42 NodeKind::VarDecl => {
43 if let Some(n) = lang.var_name(id) {
44 out.insert(n);
45 }
46 for &c in lang.children(id) {
47 local_writes(lang, c, out);
48 }
49 }
50 _ => {
51 for &c in lang.children(id) {
52 local_writes(lang, c, out);
53 }
54 }
55 }
56}
57
58pub fn reads_vars<'a, L: Lang>(lang: &'a L, id: L::Id, out: &mut crate::walk::StrSet<'a>) {
60 if lang.kind(id) == NodeKind::VarRef {
61 if let Some(n) = lang.var_name(id) {
62 out.insert(n);
63 }
64 }
65 for &c in lang.children(id) {
66 reads_vars(lang, c, out);
67 }
68}
69
70pub fn subtree_contains<L: Lang>(lang: &L, id: L::Id, pred: impl Fn(L::Id) -> bool) -> bool {
72 let mut stack = vec![id];
73 while let Some(n) = stack.pop() {
74 if pred(n) {
75 return true;
76 }
77 for &c in lang.children(n) {
78 stack.push(c);
79 }
80 }
81 false
82}
83
84pub fn prefix_effects_readable<L: Lang>(lang: &L, root: L::Id, target: L::Id) -> bool {
89 fn go<L: Lang>(lang: &L, node: L::Id, target: L::Id) -> bool {
90 if node == target {
91 return true;
92 }
93 for &c in lang.children(node) {
94 if crate::analysis::subtree_contains(lang, c, |n| n == target) {
95 return go(lang, c, target);
98 } else {
99 if lang.effect(c) > crate::effect::Effect::MayRead {
100 return false;
101 }
102 }
103 }
104 true
105 }
106 go(lang, root, target)
107}
108
109pub fn structurally_equal<L: Lang>(lang: &L, a: L::Id, b: L::Id) -> bool {
112 if lang.kind(a) != lang.kind(b) {
113 return false;
114 }
115 if lang.bin_op(a) != lang.bin_op(b) || lang.un_op(a) != lang.un_op(b) {
116 return false;
117 }
118 if lang.var_name(a) != lang.var_name(b) {
119 return false;
120 }
121 if lang.member_name(a) != lang.member_name(b) {
125 return false;
126 }
127 if !lang.payload_equal(a, b) {
132 return false;
133 }
134 match (lang.literal(a), lang.literal(b)) {
135 (Some(x), Some(y)) if x == y => {}
136 (None, None) => {}
137 _ => return false,
138 }
139 let (ca, cb) = (lang.children(a), lang.children(b));
140 if ca.len() != cb.len() {
141 return false;
142 }
143 ca.iter().zip(cb.iter()).all(|(&x, &y)| structurally_equal(lang, x, y))
144}
145
146#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
156pub struct TreeMetrics {
157 pub nodes: u64,
159 pub decisions: u64,
162 pub max_depth: u32,
165}
166
167impl TreeMetrics {
168 pub fn add(&mut self, other: TreeMetrics) {
169 self.nodes += other.nodes;
170 self.decisions += other.decisions;
171 self.max_depth = self.max_depth.max(other.max_depth);
172 }
173}
174
175fn is_container(k: NodeKind) -> bool {
177 matches!(
178 k,
179 NodeKind::Block
180 | NodeKind::If
181 | NodeKind::While
182 | NodeKind::DoWhile
183 | NodeKind::For
184 | NodeKind::ForEach
185 | NodeKind::Try
186 | NodeKind::Catch
187 | NodeKind::Switch
188 | NodeKind::Synchronized
189 )
190}
191
192fn is_decision_kind(k: NodeKind) -> bool {
194 matches!(
195 k,
196 NodeKind::If
197 | NodeKind::While
198 | NodeKind::DoWhile
199 | NodeKind::For
200 | NodeKind::ForEach
201 | NodeKind::Case
202 | NodeKind::Catch
203 | NodeKind::Ternary
204 )
205}
206
207pub fn subtree_metrics<L: Lang>(lang: &L, root: L::Id) -> TreeMetrics {
209 let mut m = TreeMetrics::default();
210 walk_metrics(lang, root, 0, &mut m);
211 m
212}
213
214fn walk_metrics<L: Lang>(lang: &L, id: L::Id, depth: u32, m: &mut TreeMetrics) {
215 m.nodes += 1;
216 let k = lang.kind(id);
217 if is_decision_kind(k) {
218 m.decisions += 1;
219 }
220 if k == NodeKind::Binary && lang.bin_op(id).is_some_and(|o| o.is_short_circuit()) {
222 m.decisions += 1;
223 }
224 let child_depth = if is_container(k) { depth + 1 } else { depth };
225 if child_depth > m.max_depth {
226 m.max_depth = child_depth;
227 }
228 for &c in lang.children(id) {
229 walk_metrics(lang, c, child_depth, m);
230 }
231}