wowlab_parsers/parsers/spell_desc/
analyzer.rs1use wowlab_types::spell_desc::SpellDescDependencies;
2
3use super::types::{
4 ConditionalPredicateNode, ExpressionNode, ParsedSpellDescription, SingleConditionNode,
5 SpellDescriptionNode, VariableNode,
6};
7
8#[must_use]
10pub fn analyze_dependencies(
11 ast: &ParsedSpellDescription,
12 self_spell_id: u32,
13) -> SpellDescDependencies {
14 let mut deps = SpellDescDependencies::new();
15
16 deps.add_spell_id(self_spell_id);
17
18 for node in &ast.nodes {
19 analyze_node(node, self_spell_id, &mut deps);
20 }
21
22 deps
23}
24
25fn analyze_node(node: &SpellDescriptionNode, self_spell_id: u32, deps: &mut SpellDescDependencies) {
27 match node {
28 SpellDescriptionNode::Variable(var) => {
29 analyze_variable(var, self_spell_id, deps);
30 }
31
32 SpellDescriptionNode::ExpressionBlock(block) => {
33 analyze_expression(&block.expression, self_spell_id, deps);
34 }
35
36 SpellDescriptionNode::Conditional(cond) => {
37 for branch in &cond.conditions {
38 analyze_predicate(&branch.predicate, deps);
39
40 for node in &branch.content {
41 analyze_node(node, self_spell_id, deps);
42 }
43 }
44
45 if let Some(else_branch) = &cond.else_branch {
46 for node in else_branch {
47 analyze_node(node, self_spell_id, deps);
48 }
49 }
50 }
51
52 SpellDescriptionNode::Text(_)
53 | SpellDescriptionNode::ColorCode(_)
54 | SpellDescriptionNode::Pluralization(_) => {}
55
56 SpellDescriptionNode::Gender(_) => {
57 deps.needs_gender = true;
58 }
59 }
60}
61
62fn analyze_variable(var: &VariableNode, self_spell_id: u32, deps: &mut SpellDescDependencies) {
63 match var {
64 VariableNode::Effect(effect) => {
65 deps.add_effect(self_spell_id, effect.effect_index, effect.var_type.clone());
66 }
67
68 VariableNode::SpellLevel(spell_level) => {
69 deps.add_spell_value(self_spell_id, spell_level.var_type.clone());
70 }
71
72 VariableNode::Player(player) => {
73 deps.add_player_stat(player.var_name().to_owned());
74 }
75
76 VariableNode::CrossSpell(cross) => {
77 if let Some(idx) = cross.effect_index {
78 deps.add_effect(cross.spell_id, idx, cross.var_type.clone());
79 } else {
80 deps.add_spell_value(cross.spell_id, cross.var_type.clone());
81 }
82 }
83
84 VariableNode::Custom(custom) => {
85 deps.add_custom_var(custom.var_name().to_owned());
86 }
87
88 VariableNode::At(at) => match at.var_type.as_str() {
89 "spelldesc" | "spellname" | "spellicon" => {
90 if let Some(spell_id) = at.spell_id {
91 deps.add_embedded_spell(spell_id);
92 }
93 }
94 _ => {
95 deps.add_player_stat(at.var_type.clone());
96 }
97 },
98
99 VariableNode::Enchant(_) => {}
100
101 VariableNode::Misc(misc) => match misc.var_name.as_str() {
102 "maxcast" | "pctD" | "W" | "B" => {
103 deps.add_player_stat(misc.var_name.clone());
104 }
105 _ => {}
106 },
107 }
108}
109
110fn analyze_expression(expr: &ExpressionNode, self_spell_id: u32, deps: &mut SpellDescDependencies) {
112 match expr {
113 ExpressionNode::Variable(var) => {
114 analyze_variable(var, self_spell_id, deps);
115 }
116
117 ExpressionNode::Binary(binary) => {
118 analyze_expression(&binary.left, self_spell_id, deps);
119 analyze_expression(&binary.right, self_spell_id, deps);
120 }
121
122 ExpressionNode::Unary(unary) => {
123 analyze_expression(&unary.operand, self_spell_id, deps);
124 }
125
126 ExpressionNode::FunctionCall(call) => {
127 for arg in &call.args {
128 analyze_expression(arg, self_spell_id, deps);
129 }
130 }
131
132 ExpressionNode::Number(_) => {}
133
134 ExpressionNode::Paren(paren) => {
135 analyze_expression(&paren.expression, self_spell_id, deps);
136 }
137 }
138}
139
140fn analyze_predicate(predicate: &ConditionalPredicateNode, deps: &mut SpellDescDependencies) {
141 for condition in &predicate.conditions {
142 match condition {
143 SingleConditionNode::SpellKnown(spell_known) => {
144 deps.add_spell_known_check(spell_known.spell_id());
145 }
146
147 SingleConditionNode::Aura(aura) => {
148 deps.add_aura_check(aura.aura_id());
149 }
150
151 SingleConditionNode::Specialization(spec) => {
152 deps.add_specialization_check(spec.spec_index());
153 }
154
155 SingleConditionNode::PlayerCondition(_) => {}
156
157 SingleConditionNode::Expression(expr_cond) => {
158 for arg in &expr_cond.args {
159 analyze_expression(arg, 0, deps);
160 }
161 }
162 }
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use googletest::prelude::*;
169 use insta::assert_debug_snapshot;
170 use wowlab_types::spell_desc::{EffectDependency, SpellValueDependency};
171
172 use super::*;
173 use crate::parsers::spell_desc::parse;
174
175 fn deps(input: &str, self_spell_id: u32) -> SpellDescDependencies {
176 analyze_dependencies(&parse(input).ast, self_spell_id)
177 }
178
179 #[gtest]
180 fn self_ref_adds_self_spell_and_effect() -> Result<()> {
181 let d = deps("$s1", 100);
182
183 verify_that!(d.spell_ids, contains(eq(&100u32)))?;
184 verify_that!(
185 d.effects,
186 contains(eq(&EffectDependency {
187 spell_id: 100,
188 effect_index: 1,
189 var_type: "s".to_string(),
190 }))
191 )?;
192
193 Ok(())
194 }
195
196 #[gtest]
197 fn cross_spell_with_effect_index() -> Result<()> {
198 let d = deps("$12345s2", 100);
199
200 verify_that!(d.spell_ids, contains(eq(&12345u32)))?;
201 verify_that!(d.spell_ids, contains(eq(&100u32)))?;
202 verify_that!(
203 d.effects,
204 contains(eq(&EffectDependency {
205 spell_id: 12345,
206 effect_index: 2,
207 var_type: "s".to_string(),
208 }))
209 )?;
210
211 Ok(())
212 }
213
214 #[gtest]
215 fn cross_spell_value_form() -> Result<()> {
216 let d = deps("$12345d", 100);
217
218 verify_that!(d.spell_ids, contains(eq(&12345u32)))?;
219 verify_that!(
220 d.spell_values,
221 contains(eq(&SpellValueDependency {
222 spell_id: 12345,
223 var_type: "d".to_string(),
224 }))
225 )?;
226
227 Ok(())
228 }
229
230 #[gtest]
231 fn gender_node_sets_needs_gender() -> Result<()> {
232 verify_that!(deps("$ghis:her;", 100).needs_gender, eq(true))?;
233 verify_that!(deps("$s1", 100).needs_gender, eq(false))?;
234
235 Ok(())
236 }
237
238 #[gtest]
239 fn conditional_predicates_collected() -> Result<()> {
240 verify_that!(
241 deps("$?s99[a][b]", 100).spell_known_checks,
242 contains(eq(&99u32))
243 )?;
244 verify_that!(deps("$?a88[a][b]", 100).aura_checks, contains(eq(&88u32)))?;
245 verify_that!(
246 deps("$?c1[a][b]", 100).specialization_checks,
247 contains(eq(&1u8))
248 )?;
249
250 Ok(())
251 }
252
253 #[gtest]
254 fn embedded_and_player_stat_at_vars() -> Result<()> {
255 verify_that!(
256 deps("$@spelldesc12345", 100).embedded_spell_ids,
257 contains(eq(&12345u32))
258 )?;
259 verify_that!(
260 deps("$@spellname12345", 100).embedded_spell_ids,
261 contains(eq(&12345u32))
262 )?;
263 verify_that!(
264 deps("$@spellicon12345", 100).embedded_spell_ids,
265 contains(eq(&12345u32))
266 )?;
267 verify_that!(
268 deps("$@somestat", 100).player_stats,
269 contains(eq("somestat"))
270 )?;
271
272 Ok(())
273 }
274
275 #[gtest]
276 fn self_id_always_present() -> Result<()> {
277 verify_that!(deps("plain text", 777).spell_ids, contains(eq(&777u32)))?;
278
279 Ok(())
280 }
281
282 #[gtest]
283 fn complex_description_deps_snapshot() {
284 let d = deps(
285 "$s1 $12345s2 $?s99[known][unknown] $ghis:her; $@spelldesc500",
286 100,
287 );
288
289 assert_debug_snapshot!(d);
290 }
291}