wowlab_parsers/parsers/spell_desc/
spec_effects.rs1use super::types::{ConditionalNode, SingleConditionNode, SpellDescriptionNode, VariableNode};
4
5#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct SpecConditionalEffects {
8 pub spec_index: u8,
10 pub matching_effect: u8,
12 pub fallback_effect: u8,
14}
15
16impl SpecConditionalEffects {
17 #[must_use]
19 pub const fn inert_effect(&self, order_index: u8) -> u8 {
20 if self.spec_index == order_index.saturating_add(1) {
21 self.fallback_effect
22 } else {
23 self.matching_effect
24 }
25 }
26}
27
28#[must_use]
40pub fn spec_conditional_effects(description: &str) -> Vec<SpecConditionalEffects> {
41 let parsed = super::parser::parse(description);
42 let mut found = Vec::new();
43
44 collect_from_nodes(&parsed.ast.nodes, &mut found);
45 found.dedup();
46
47 let contradicted: Vec<u8> = found
49 .iter()
50 .flat_map(|statement| [statement.matching_effect, statement.fallback_effect])
51 .filter(|effect| {
52 found
53 .iter()
54 .filter(|statement| {
55 statement.matching_effect == *effect || statement.fallback_effect == *effect
56 })
57 .count()
58 > 1
59 })
60 .collect();
61
62 found.retain(|statement| {
63 !contradicted.contains(&statement.matching_effect)
64 && !contradicted.contains(&statement.fallback_effect)
65 });
66
67 found
68}
69
70fn collect_from_nodes(nodes: &[SpellDescriptionNode], found: &mut Vec<SpecConditionalEffects>) {
72 for node in nodes {
73 let SpellDescriptionNode::Conditional(conditional) = node else {
74 continue;
75 };
76
77 if let Some(statement) = spec_conditional_effects_of(conditional) {
78 found.push(statement);
79 }
80
81 for branch in &conditional.conditions {
82 collect_from_nodes(&branch.content, found);
83 }
84
85 if let Some(else_branch) = &conditional.else_branch {
86 collect_from_nodes(else_branch, found);
87 }
88 }
89}
90
91fn spec_conditional_effects_of(conditional: &ConditionalNode) -> Option<SpecConditionalEffects> {
92 let [branch] = conditional.conditions.as_slice() else {
93 return None;
94 };
95 let [SingleConditionNode::Specialization(spec)] = branch.predicate.conditions.as_slice() else {
96 return None;
97 };
98 let matching_effect = sole_base_value_effect(&branch.content)?;
99 let fallback_effect = sole_base_value_effect(conditional.else_branch.as_deref()?)?;
100
101 if matching_effect == fallback_effect {
102 return None;
103 }
104
105 Some(SpecConditionalEffects {
106 spec_index: spec.spec_index(),
107 matching_effect,
108 fallback_effect,
109 })
110}
111
112fn sole_base_value_effect(nodes: &[SpellDescriptionNode]) -> Option<u8> {
114 let mut effects = nodes.iter().filter_map(|node| {
115 let SpellDescriptionNode::Variable(VariableNode::Effect(effect)) = node else {
116 return None;
117 };
118
119 effect
120 .var_type
121 .eq_ignore_ascii_case("s")
122 .then_some(effect.effect_index)
123 });
124 let only = effects.next()?;
125
126 effects.next().is_none().then_some(only)
127}
128
129#[cfg(test)]
130mod tests;