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]
38pub fn spec_conditional_effects(description: &str) -> Vec<SpecConditionalEffects> {
39 let parsed = super::parser::parse(description);
40 let mut found = Vec::new();
41
42 collect_from_nodes(&parsed.ast.nodes, &mut found);
43 found.dedup();
44
45 let contradicted: Vec<u8> = found
47 .iter()
48 .flat_map(|statement| [statement.matching_effect, statement.fallback_effect])
49 .filter(|effect| {
50 found
51 .iter()
52 .filter(|statement| {
53 statement.matching_effect == *effect || statement.fallback_effect == *effect
54 })
55 .count()
56 > 1
57 })
58 .collect();
59
60 found.retain(|statement| {
61 !contradicted.contains(&statement.matching_effect)
62 && !contradicted.contains(&statement.fallback_effect)
63 });
64
65 found
66}
67
68fn collect_from_nodes(nodes: &[SpellDescriptionNode], found: &mut Vec<SpecConditionalEffects>) {
70 for node in nodes {
71 let SpellDescriptionNode::Conditional(conditional) = node else {
72 continue;
73 };
74
75 if let Some(statement) = spec_conditional_effects_of(conditional) {
76 found.push(statement);
77 }
78
79 for branch in &conditional.conditions {
80 collect_from_nodes(&branch.content, found);
81 }
82
83 if let Some(else_branch) = &conditional.else_branch {
84 collect_from_nodes(else_branch, found);
85 }
86 }
87}
88
89fn spec_conditional_effects_of(conditional: &ConditionalNode) -> Option<SpecConditionalEffects> {
90 let [branch] = conditional.conditions.as_slice() else {
91 return None;
92 };
93 let [SingleConditionNode::Specialization(spec)] = branch.predicate.conditions.as_slice() else {
94 return None;
95 };
96 let matching_effect = sole_base_value_effect(&branch.content)?;
97 let fallback_effect = sole_base_value_effect(conditional.else_branch.as_deref()?)?;
98
99 if matching_effect == fallback_effect {
100 return None;
101 }
102
103 Some(SpecConditionalEffects {
104 spec_index: spec.spec_index(),
105 matching_effect,
106 fallback_effect,
107 })
108}
109
110fn sole_base_value_effect(nodes: &[SpellDescriptionNode]) -> Option<u8> {
112 let mut effects = nodes.iter().filter_map(|node| {
113 let SpellDescriptionNode::Variable(VariableNode::Effect(effect)) = node else {
114 return None;
115 };
116
117 effect
118 .var_type
119 .eq_ignore_ascii_case("s")
120 .then_some(effect.effect_index)
121 });
122 let only = effects.next()?;
123
124 effects.next().is_none().then_some(only)
125}
126
127#[cfg(test)]
128mod tests;