wowlab_parsers/parsers/spell_desc/parser/
variables.rs1use super::super::types::{
4 AtVariableNode, AuraConditionNode, CrossSpellReferenceNode, CustomVariableNode,
5 EffectVariableNode, EnchantVariableNode, GenderNode, MiscVariableNode, PlayerConditionNode,
6 PlayerVariableNode, PluralizationNode, SingleConditionNode, SpecializationConditionNode,
7 SpellKnownConditionNode, SpellLevelVariableNode, VariableNode,
8};
9
10const DECIMAL_RADIX: u32 = 10;
11const CONTENT_SPLIT_PARTS: usize = 2;
12const DOLLAR_TWO_CHAR_PREFIX: usize = 2;
13
14#[rustfmt::skip]
15const EFFECT_VAR_TYPES: &[&str] = &[
16 "s",
17 "m",
18 "o",
19 "t",
20 "a",
21 "e",
22 "w",
23 "x",
24 "bc",
25 "q",
26 "sw",
27];
28
29pub(super) fn parse_effect_var(s: &str) -> VariableNode {
30 let body = &s[1..];
31 let index = body
32 .chars()
33 .last()
34 .and_then(|c| c.to_digit(DECIMAL_RADIX))
35 .and_then(|digit| u8::try_from(digit).ok())
36 .unwrap_or(1);
37 let var_type = &body[..body.len() - 1];
38
39 VariableNode::Effect(EffectVariableNode {
42 var_type: var_type.to_string(),
43 effect_index: index,
44 })
45}
46
47pub(super) fn parse_spell_level_var(s: &str) -> VariableNode {
48 let body = &s[1..];
49
50 let last_char = body.chars().last();
51 let has_index = last_char.is_some_and(|c| c.is_ascii_digit()) && body.len() > 1;
52
53 let (var_type, index) = if has_index {
54 let idx = last_char
55 .and_then(|c| c.to_digit(DECIMAL_RADIX))
56 .and_then(|digit| u8::try_from(digit).ok());
57
58 (&body[..body.len() - 1], idx)
59 } else {
60 (body, None)
61 };
62
63 VariableNode::SpellLevel(SpellLevelVariableNode {
64 var_type: var_type.to_string(),
65 index,
66 })
67}
68
69pub(super) fn parse_player_var(s: &str) -> VariableNode {
70 VariableNode::Player(PlayerVariableNode::new(s[1..].to_string()))
71}
72
73pub(super) fn parse_enchant_var(s: &str) -> VariableNode {
74 VariableNode::Enchant(EnchantVariableNode::new(s[1..].to_string()))
75}
76
77pub(super) fn parse_misc_var(s: &str) -> VariableNode {
78 let body = &s[1..];
79
80 let digits: String = body
81 .chars()
82 .rev()
83 .take_while(char::is_ascii_digit)
84 .collect();
85 let id = if digits.is_empty() {
86 None
87 } else {
88 digits.chars().rev().collect::<String>().parse().ok()
89 };
90
91 let var_name = if id.is_some() {
92 &body[..body.len() - digits.len()]
93 } else {
94 body
95 };
96
97 VariableNode::Misc(MiscVariableNode {
98 var_name: var_name.to_string(),
99 id,
100 })
101}
102
103pub(super) fn parse_custom_var(s: &str) -> VariableNode {
104 let var_name = &s[DOLLAR_TWO_CHAR_PREFIX..s.len() - 1];
105
106 VariableNode::Custom(CustomVariableNode::new(var_name.to_string()))
107}
108
109pub(super) fn parse_at_var(s: &str) -> VariableNode {
110 let body = &s[DOLLAR_TWO_CHAR_PREFIX..];
111
112 let digits: String = body
113 .chars()
114 .rev()
115 .take_while(char::is_ascii_digit)
116 .collect();
117 let spell_id = if digits.is_empty() {
118 None
119 } else {
120 digits.chars().rev().collect::<String>().parse().ok()
121 };
122
123 let var_type = if spell_id.is_some() {
124 &body[..body.len() - digits.len()]
125 } else {
126 body
127 };
128
129 VariableNode::At(AtVariableNode {
130 var_type: var_type.to_string(),
131 spell_id,
132 })
133}
134
135pub(super) fn parse_cross_spell_ref(s: &str) -> VariableNode {
136 let body = &s[1..];
137
138 let id_digits: String = body.chars().take_while(char::is_ascii_digit).collect();
139 let spell_id: u32 = id_digits.parse().unwrap_or(0);
140
141 let remainder = &body[id_digits.len()..];
142
143 let trail_digits: String = remainder
144 .chars()
145 .rev()
146 .take_while(char::is_ascii_digit)
147 .collect();
148 let mut effect_index = if trail_digits.is_empty() {
149 None
150 } else {
151 trail_digits.chars().rev().collect::<String>().parse().ok()
152 };
153
154 let var_type = if effect_index.is_some() {
155 &remainder[..remainder.len() - trail_digits.len()]
156 } else {
157 remainder
158 };
159
160 let var_type_lower = var_type.to_lowercase();
161
162 if effect_index.is_none() && EFFECT_VAR_TYPES.contains(&var_type_lower.as_str()) {
163 effect_index = Some(1);
164 }
165
166 VariableNode::CrossSpell(CrossSpellReferenceNode {
167 spell_id,
168 var_type: var_type.to_string(),
169 effect_index,
170 })
171}
172
173pub(super) fn parse_pluralization(content: &str, capitalized: bool) -> PluralizationNode {
174 let parts: Vec<&str> = content.splitn(CONTENT_SPLIT_PARTS, ':').collect();
175 let singular = parts.first().unwrap_or(&"").to_string();
176 let plural = parts.get(1).unwrap_or(&"").to_string();
177
178 PluralizationNode {
179 singular,
180 plural,
181 capitalized,
182 }
183}
184
185pub(super) fn parse_gender(content: &str, capitalized: bool) -> GenderNode {
186 let parts: Vec<&str> = content.splitn(CONTENT_SPLIT_PARTS, ':').collect();
187 let male = parts.first().unwrap_or(&"").to_string();
188 let female = parts.get(1).unwrap_or(&"").to_string();
189
190 GenderNode {
191 male,
192 female,
193 capitalized,
194 }
195}
196
197pub(super) fn parse_condition_type(s: &str) -> Option<SingleConditionNode> {
199 let s = s.trim();
200
201 if s.is_empty() {
202 return None;
203 }
204
205 if let Some(rest) = s.strip_prefix('s') {
206 if let Ok(id) = rest.parse::<u32>() {
207 return Some(SingleConditionNode::SpellKnown(
208 SpellKnownConditionNode::new(id),
209 ));
210 }
211 }
212
213 if let Some(rest) = s.strip_prefix('a') {
214 if let Ok(id) = rest.parse::<u32>() {
215 return Some(SingleConditionNode::Aura(AuraConditionNode::new(id)));
216 }
217 }
218
219 if let Some(rest) = s.strip_prefix('c') {
220 if let Ok(spec_index) = rest.parse::<u8>() {
221 return Some(SingleConditionNode::Specialization(
222 SpecializationConditionNode::new(spec_index),
223 ));
224 }
225 }
226
227 if let Some(rest) = s.strip_prefix("pc") {
228 if let Ok(id) = rest.parse::<u32>() {
229 return Some(SingleConditionNode::PlayerCondition(
230 PlayerConditionNode::new(id),
231 ));
232 }
233 }
234
235 None
236}