Skip to main content

wowlab_parsers/parsers/spell_desc/
types.rs

1use serde::{Deserialize, Serialize};
2#[cfg(feature = "wasm")]
3use tsify::Tsify;
4
5/// A fully parsed spell description.
6#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
7#[cfg_attr(feature = "wasm", derive(Tsify))]
8#[cfg_attr(feature = "wasm", tsify(into_wasm_abi))]
9#[serde(rename_all = "camelCase")]
10pub struct ParsedSpellDescription {
11    pub nodes: Vec<SpellDescriptionNode>,
12}
13
14/// Top-level node in a spell description.
15#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
16#[cfg_attr(feature = "wasm", derive(Tsify))]
17#[serde(tag = "type", rename_all = "camelCase")]
18// #t(block: rust_non_exhaustive_on_public) stable AST nodes matching WoW spell description grammar
19// docref:start spell-desc-node-enum
20pub enum SpellDescriptionNode {
21    Text(TextNode),
22    Variable(VariableNode),
23    ExpressionBlock(ExpressionBlockNode),
24    Conditional(ConditionalNode),
25    Pluralization(PluralizationNode),
26    Gender(GenderNode),
27    ColorCode(ColorCodeNode),
28}
29// docref:end spell-desc-node-enum
30
31/// Plain text content.
32#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
33#[cfg_attr(feature = "wasm", derive(Tsify))]
34#[serde(rename_all = "camelCase")]
35pub struct TextNode {
36    value: String,
37}
38
39impl TextNode {
40    pub(crate) fn new(value: String) -> Self {
41        Self { value }
42    }
43    pub fn value(&self) -> &str {
44        &self.value
45    }
46}
47
48/// Color code like |cFFFFFFFF or |r.
49#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
50#[cfg_attr(feature = "wasm", derive(Tsify))]
51#[serde(rename_all = "camelCase")]
52pub struct ColorCodeNode {
53    code: String,
54}
55
56impl ColorCodeNode {
57    pub(crate) fn new(code: String) -> Self {
58        Self { code }
59    }
60    pub fn code(&self) -> &str {
61        &self.code
62    }
63}
64
65/// Any variable reference in a spell description.
66#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
67#[cfg_attr(feature = "wasm", derive(Tsify))]
68#[serde(tag = "varKind", rename_all = "camelCase")]
69// #t(rust_non_exhaustive_on_public) stable variable kinds matching WoW spell description grammar
70pub enum VariableNode {
71    Effect(EffectVariableNode),
72    SpellLevel(SpellLevelVariableNode),
73    Player(PlayerVariableNode),
74    CrossSpell(CrossSpellReferenceNode),
75    Custom(CustomVariableNode),
76    At(AtVariableNode),
77    Enchant(EnchantVariableNode),
78    Misc(MiscVariableNode),
79}
80
81/// Effect variable like $s1, $m1, $t1, $a1, $o1, $bc1.
82#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
83#[cfg_attr(feature = "wasm", derive(Tsify))]
84#[serde(rename_all = "camelCase")]
85pub struct EffectVariableNode {
86    pub var_type: String,
87    pub effect_index: u8,
88}
89
90/// Spell-level variable like $d (duration), $n (charges), $h (proc chance)
91#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
92#[cfg_attr(feature = "wasm", derive(Tsify))]
93#[serde(rename_all = "camelCase")]
94pub struct SpellLevelVariableNode {
95    pub var_type: String,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub index: Option<u8>,
98}
99
100/// Player stat variable like $SP (spell power), $AP (attack power), $MHP (max health)
101#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
102#[cfg_attr(feature = "wasm", derive(Tsify))]
103#[serde(rename_all = "camelCase")]
104pub struct PlayerVariableNode {
105    var_name: String,
106}
107
108impl PlayerVariableNode {
109    pub(crate) fn new(var_name: String) -> Self {
110        Self { var_name }
111    }
112    pub fn var_name(&self) -> &str {
113        &self.var_name
114    }
115}
116
117/// Cross-spell reference like $123456s1 (spell 123456, effect 1, base points)
118#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
119#[cfg_attr(feature = "wasm", derive(Tsify))]
120#[serde(rename_all = "camelCase")]
121pub struct CrossSpellReferenceNode {
122    pub spell_id: u32,
123    pub var_type: String,
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub effect_index: Option<u8>,
126}
127
128/// Custom variable from spell's `description_variables` like `$<healing>`.
129#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
130#[cfg_attr(feature = "wasm", derive(Tsify))]
131#[serde(rename_all = "camelCase")]
132pub struct CustomVariableNode {
133    var_name: String,
134}
135
136impl CustomVariableNode {
137    pub(crate) fn new(var_name: String) -> Self {
138        Self { var_name }
139    }
140    pub fn var_name(&self) -> &str {
141        &self.var_name
142    }
143}
144
145/// @ variable like @spelldesc123, @spellname456, @versadmg.
146#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
147#[cfg_attr(feature = "wasm", derive(Tsify))]
148#[serde(rename_all = "camelCase")]
149pub struct AtVariableNode {
150    pub var_type: String,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub spell_id: Option<u32>,
153}
154
155/// Enchant variable like $ec1, $ecix, $ecd.
156#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
157#[cfg_attr(feature = "wasm", derive(Tsify))]
158#[serde(rename_all = "camelCase")]
159pub struct EnchantVariableNode {
160    var_type: String,
161}
162
163impl EnchantVariableNode {
164    pub(crate) fn new(var_type: String) -> Self {
165        Self { var_type }
166    }
167    pub fn var_type(&self) -> &str {
168        &self.var_type
169    }
170}
171
172/// Misc variable like $maxcast, $pctD, $W, $W2, $B, $ctrmax1.
173#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
174#[cfg_attr(feature = "wasm", derive(Tsify))]
175#[serde(rename_all = "camelCase")]
176pub struct MiscVariableNode {
177    pub var_name: String,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub id: Option<u32>,
180}
181
182/// Pluralization node like $lstack:stacks; or $Lstack:stacks;.
183#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
184#[cfg_attr(feature = "wasm", derive(Tsify))]
185#[serde(rename_all = "camelCase")]
186pub struct PluralizationNode {
187    pub singular: String,
188    pub plural: String,
189    pub capitalized: bool,
190}
191
192/// Gender node like $ghis:her; or $Ghis:her;.
193#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
194#[cfg_attr(feature = "wasm", derive(Tsify))]
195#[serde(rename_all = "camelCase")]
196pub struct GenderNode {
197    pub male: String,
198    pub female: String,
199    pub capitalized: bool,
200}
201
202/// Expression block like ${$s1 * 2 + $SP / 100}.
203#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
204#[cfg_attr(feature = "wasm", derive(Tsify))]
205#[serde(rename_all = "camelCase")]
206pub struct ExpressionBlockNode {
207    pub expression: ExpressionNode,
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub decimal_places: Option<u8>,
210}
211
212/// An expression that evaluates to a number.
213#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
214#[cfg_attr(feature = "wasm", derive(Tsify))]
215#[serde(tag = "exprType", rename_all = "camelCase")]
216// #t(rust_non_exhaustive_on_public) stable expression AST matching WoW spell description grammar
217pub enum ExpressionNode {
218    Binary(BinaryExpressionNode),
219    Unary(UnaryExpressionNode),
220    FunctionCall(FunctionCallNode),
221    Variable(Box<VariableNode>),
222    Number(NumberLiteralNode),
223    Paren(ParenExpressionNode),
224}
225
226/// Binary expression like $s1 + $SP.
227#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
228#[cfg_attr(feature = "wasm", derive(Tsify))]
229#[serde(rename_all = "camelCase")]
230pub struct BinaryExpressionNode {
231    // #t(rust_pub_api_smart_pointers) recursive expression edge requires indirection
232    pub left: Box<ExpressionNode>,
233    pub operator: BinaryOperator,
234    // #t(rust_pub_api_smart_pointers) recursive expression edge requires indirection
235    pub right: Box<ExpressionNode>,
236}
237
238/// Arithmetic operator in a binary expression (add, subtract, multiply, divide).
239#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
240#[cfg_attr(feature = "wasm", derive(Tsify))]
241#[serde(rename_all = "camelCase")]
242// #t(rust_non_exhaustive_on_public) standard arithmetic operators
243pub enum BinaryOperator {
244    Add,
245    Sub,
246    Mul,
247    Div,
248}
249
250/// Unary expression like -$s1.
251#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
252#[cfg_attr(feature = "wasm", derive(Tsify))]
253#[serde(rename_all = "camelCase")]
254pub struct UnaryExpressionNode {
255    pub operator: UnaryOperator,
256    // #t(rust_pub_api_smart_pointers) recursive expression edge requires indirection
257    pub operand: Box<ExpressionNode>,
258}
259
260/// Unary arithmetic operator (currently only negation).
261#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
262#[cfg_attr(feature = "wasm", derive(Tsify))]
263#[serde(rename_all = "camelCase")]
264// #t(rust_non_exhaustive_on_public) standard unary operators
265pub enum UnaryOperator {
266    Neg,
267}
268
269/// Function call like $gt($s1, 5), $max($s1, $s2), floor($s1 / 100)
270#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
271#[cfg_attr(feature = "wasm", derive(Tsify))]
272#[serde(rename_all = "camelCase")]
273pub struct FunctionCallNode {
274    pub func_name: String,
275    pub args: Vec<ExpressionNode>,
276}
277
278/// Number literal like 100 or 3.5.
279#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
280#[cfg_attr(feature = "wasm", derive(Tsify))]
281#[serde(rename_all = "camelCase")]
282pub struct NumberLiteralNode {
283    value: f64,
284}
285
286impl NumberLiteralNode {
287    pub(crate) const fn new(value: f64) -> Self {
288        Self { value }
289    }
290    pub const fn value(&self) -> f64 {
291        self.value
292    }
293}
294
295/// Parenthesized expression node wrapping an inner expression AST.
296#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
297#[cfg_attr(feature = "wasm", derive(Tsify))]
298#[serde(rename_all = "camelCase")]
299pub struct ParenExpressionNode {
300    // #t(rust_pub_api_smart_pointers) recursive expression edge requires indirection
301    pub expression: Box<ExpressionNode>,
302}
303
304/// Conditional like `$?a123[true text][false text]` or `$?s456[known]?a789[has aura][else]`.
305#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
306#[cfg_attr(feature = "wasm", derive(Tsify))]
307#[serde(rename_all = "camelCase")]
308pub struct ConditionalNode {
309    pub conditions: Vec<ConditionalBranchNode>,
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub else_branch: Option<Vec<SpellDescriptionNode>>,
312}
313
314/// A single condition branch: predicate + content.
315#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
316#[cfg_attr(feature = "wasm", derive(Tsify))]
317#[serde(rename_all = "camelCase")]
318pub struct ConditionalBranchNode {
319    pub predicate: ConditionalPredicateNode,
320    pub content: Vec<SpellDescriptionNode>,
321}
322
323/// Predicate for a conditional (OR of conditions)
324#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
325#[cfg_attr(feature = "wasm", derive(Tsify))]
326#[serde(rename_all = "camelCase")]
327pub struct ConditionalPredicateNode {
328    pub conditions: Vec<SingleConditionNode>,
329}
330
331/// A single condition in a predicate.
332#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
333#[cfg_attr(feature = "wasm", derive(Tsify))]
334#[serde(tag = "condType", rename_all = "camelCase")]
335// #t(rust_non_exhaustive_on_public) stable condition types matching WoW spell description grammar
336pub enum SingleConditionNode {
337    SpellKnown(SpellKnownConditionNode),
338    Aura(AuraConditionNode),
339    Specialization(SpecializationConditionNode),
340    PlayerCondition(PlayerConditionNode),
341    Expression(ExpressionConditionNode),
342}
343
344/// Condition that checks whether a specific spell is known by the player.
345#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
346#[cfg_attr(feature = "wasm", derive(Tsify))]
347#[serde(rename_all = "camelCase")]
348pub struct SpellKnownConditionNode {
349    spell_id: u32,
350}
351
352impl SpellKnownConditionNode {
353    pub(crate) const fn new(spell_id: u32) -> Self {
354        Self { spell_id }
355    }
356    pub const fn spell_id(&self) -> u32 {
357        self.spell_id
358    }
359}
360
361/// Condition that checks whether a specific aura is active on the player.
362#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
363#[cfg_attr(feature = "wasm", derive(Tsify))]
364#[serde(rename_all = "camelCase")]
365pub struct AuraConditionNode {
366    aura_id: u32,
367}
368
369impl AuraConditionNode {
370    pub(crate) const fn new(aura_id: u32) -> Self {
371        Self { aura_id }
372    }
373    pub const fn aura_id(&self) -> u32 {
374        self.aura_id
375    }
376}
377
378/// Condition that checks the player's specialization index within their class.
379///
380/// `$?c3` on the warrior spell Shield Wall 2565 gates Protection-only text.
381///   `$?c1` on the priest spell Mind Blast 8092 gates Discipline-only text.
382///   The number is therefore `ChrSpecialization.OrderIndex + 1`, never a class ID.
383#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
384#[cfg_attr(feature = "wasm", derive(Tsify))]
385#[serde(rename_all = "camelCase")]
386pub struct SpecializationConditionNode {
387    spec_index: u8,
388}
389
390impl SpecializationConditionNode {
391    pub(crate) const fn new(spec_index: u8) -> Self {
392        Self { spec_index }
393    }
394    pub const fn spec_index(&self) -> u8 {
395        self.spec_index
396    }
397}
398
399/// Condition referencing a server-side `PlayerCondition` entry.
400#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
401#[cfg_attr(feature = "wasm", derive(Tsify))]
402#[serde(rename_all = "camelCase")]
403pub struct PlayerConditionNode {
404    condition_id: u32,
405}
406
407impl PlayerConditionNode {
408    pub(crate) const fn new(condition_id: u32) -> Self {
409        Self { condition_id }
410    }
411    pub const fn condition_id(&self) -> u32 {
412        self.condition_id
413    }
414}
415
416/// Condition using a function expression (e.g. `$gt($s1, 5)`).
417#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
418#[cfg_attr(feature = "wasm", derive(Tsify))]
419#[serde(rename_all = "camelCase")]
420// #t(rust_similar_structs) condition and expression nodes are distinct serialized AST variant contracts
421pub struct ExpressionConditionNode {
422    pub func_name: String,
423    pub args: Vec<ExpressionNode>,
424}