wowlab_parsers/parsers/spell_desc/
precision.rs1use super::types::{BinaryOperator, ExpressionNode, SpellDescriptionNode, VariableNode};
4
5const MIN_PRECISION_DIVISOR: f64 = 10.0;
7const MAX_PRECISION_DIVISOR: f64 = 1_000_000.0;
9
10#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct EffectPrecisionScale {
13 pub effect_index: u8,
15 pub divisor: f64,
17}
18
19#[must_use]
29pub fn effect_precision_scales(description: &str) -> Vec<EffectPrecisionScale> {
30 let parsed = super::parser::parse(description);
31 let mut scales = Vec::new();
32
33 collect_from_nodes(&parsed.ast.nodes, &mut scales);
34 scales.sort_unstable_by_key(|scale| scale.effect_index);
35 scales.dedup();
36
37 let ambiguous: Vec<u8> = scales
39 .iter()
40 .filter(|scale| {
41 scales
42 .iter()
43 .filter(|other| other.effect_index == scale.effect_index)
44 .count()
45 > 1
46 })
47 .map(|scale| scale.effect_index)
48 .collect();
49
50 scales.retain(|scale| !ambiguous.contains(&scale.effect_index));
51
52 scales
53}
54
55fn collect_from_nodes(nodes: &[SpellDescriptionNode], scales: &mut Vec<EffectPrecisionScale>) {
57 for (position, node) in nodes.iter().enumerate() {
58 match node {
59 SpellDescriptionNode::ExpressionBlock(block) => {
60 if !renders_percentage(nodes.get(position + 1)) {
61 continue;
62 }
63
64 if let Some(scale) = precision_scale(&block.expression) {
65 scales.push(scale);
66 }
67 }
68
69 SpellDescriptionNode::Conditional(conditional) => {
70 for branch in &conditional.conditions {
71 collect_from_nodes(&branch.content, scales);
72 }
73
74 if let Some(else_branch) = &conditional.else_branch {
75 collect_from_nodes(else_branch, scales);
76 }
77 }
78
79 SpellDescriptionNode::Text(_)
80 | SpellDescriptionNode::Variable(_)
81 | SpellDescriptionNode::Pluralization(_)
82 | SpellDescriptionNode::Gender(_)
83 | SpellDescriptionNode::ColorCode(_) => {}
84 }
85 }
86}
87
88pub(super) fn renders_percentage(node: Option<&SpellDescriptionNode>) -> bool {
90 matches!(node, Some(SpellDescriptionNode::Text(text)) if text.value().starts_with('%'))
91}
92
93fn precision_scale(expression: &ExpressionNode) -> Option<EffectPrecisionScale> {
94 let ExpressionNode::Binary(binary) = expression else {
95 return None;
96 };
97
98 if binary.operator != BinaryOperator::Div {
99 return None;
100 }
101
102 let effect_index = base_value_effect_index(&binary.left)?;
103 let divisor = power_of_ten(&binary.right)?;
104
105 Some(EffectPrecisionScale {
106 effect_index,
107 divisor,
108 })
109}
110
111fn base_value_effect_index(expression: &ExpressionNode) -> Option<u8> {
112 let ExpressionNode::Variable(variable) = expression else {
113 return None;
114 };
115 let VariableNode::Effect(effect) = variable.as_ref() 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
125pub(super) fn power_of_ten(expression: &ExpressionNode) -> Option<f64> {
127 let ExpressionNode::Number(number) = expression else {
128 return None;
129 };
130 let value = number.value();
131 let mut candidate = MIN_PRECISION_DIVISOR;
132
133 while candidate <= MAX_PRECISION_DIVISOR {
134 if (value - candidate).abs() < f64::EPSILON {
135 return Some(candidate);
136 }
137
138 candidate *= MIN_PRECISION_DIVISOR;
139 }
140
141 None
142}
143
144#[cfg(test)]
145mod tests;