Skip to main content

wowlab_parsers/parsers/spell_desc/parser/
segment.rs

1// #t(file: rust_unchecked_indexing, rust_alloc_in_loop) parser slices token strings from regex-matched logos output with guaranteed structure
2
3use super::{
4    super::{
5        lexer::{Token, lex_expr},
6        types::{
7            ColorCodeNode, ConditionalBranchNode, ConditionalNode, ConditionalPredicateNode,
8            ExpressionBlockNode, MiscVariableNode, SingleConditionNode, SpellDescriptionNode,
9            TextNode, VariableNode,
10        },
11    },
12    ParseError,
13    expr::{ExprParser, parse_cond_func_call},
14    variables::{
15        parse_at_var, parse_condition_type, parse_cross_spell_ref, parse_custom_var,
16        parse_effect_var, parse_enchant_var, parse_gender, parse_misc_var, parse_player_var,
17        parse_pluralization, parse_spell_level_var,
18    },
19};
20use crate::parsers::TokenStream;
21
22pub(super) struct Parser<'a> {
23    stream: TokenStream<'a, Token<'a>>,
24    pub(super) errors: Vec<ParseError>,
25}
26
27impl<'a> Parser<'a> {
28    pub(super) fn new(tokens: &'a [Token<'a>]) -> Self {
29        Self {
30            stream: TokenStream::new(tokens),
31            errors: Vec::new(),
32        }
33    }
34
35    pub(super) fn parse_description(&mut self) -> Vec<SpellDescriptionNode> {
36        let mut nodes = Vec::new();
37
38        while !self.stream.is_empty() {
39            if let Some(node) = self.parse_segment() {
40                nodes.push(node);
41            }
42        }
43
44        nodes
45    }
46
47    fn peek(&self) -> Option<&Token<'a>> {
48        self.stream.peek()
49    }
50
51    fn advance(&mut self) -> Option<&Token<'a>> {
52        self.stream.advance()
53    }
54
55    // #t(rust_cyclomatic_complexity) token type dispatch over all AST node kinds
56    fn parse_segment(&mut self) -> Option<SpellDescriptionNode> {
57        let token = self.peek()?.clone();
58
59        self.advance();
60
61        match token {
62            Token::Text(s) => Some(SpellDescriptionNode::Text(TextNode::new(s.to_string()))),
63
64            Token::ExpressionBlock(content) => self.parse_expression_block(content),
65
66            Token::CustomVariable(s) => Some(SpellDescriptionNode::Variable(parse_custom_var(s))),
67
68            Token::AtVariable(s) => Some(SpellDescriptionNode::Variable(parse_at_var(s))),
69
70            Token::EffectVariable(s) => Some(SpellDescriptionNode::Variable(parse_effect_var(s))),
71
72            Token::SpellLevelVariable(s) => {
73                Some(SpellDescriptionNode::Variable(parse_spell_level_var(s)))
74            }
75
76            Token::PlayerVariable(s) => Some(SpellDescriptionNode::Variable(parse_player_var(s))),
77
78            Token::EnchantVariable(s) => Some(SpellDescriptionNode::Variable(parse_enchant_var(s))),
79
80            Token::MiscVariable(s) => Some(SpellDescriptionNode::Variable(parse_misc_var(s))),
81
82            Token::CrossSpellRef(s) => {
83                Some(SpellDescriptionNode::Variable(parse_cross_spell_ref(s)))
84            }
85
86            Token::SimpleVariable(s) => {
87                // #t(block: rust_unchecked_indexing) regex guarantees $ prefix
88                Some(SpellDescriptionNode::Variable(VariableNode::Misc(
89                    MiscVariableNode {
90                        var_name: s[1..].to_string(),
91                        id: None,
92                    },
93                )))
94            }
95
96            Token::ConditionalStart => Some(self.parse_conditional()),
97
98            Token::Pluralization((content, capitalized)) => Some(
99                SpellDescriptionNode::Pluralization(parse_pluralization(content, capitalized)),
100            ),
101
102            Token::Gender((content, capitalized)) => Some(SpellDescriptionNode::Gender(
103                parse_gender(content, capitalized),
104            )),
105
106            // #t(block: rust_unchecked_indexing) regex guarantees | prefix
107            Token::ColorCode(s) => Some(SpellDescriptionNode::ColorCode(ColorCodeNode::new(
108                s[1..].to_string(),
109            ))),
110
111            Token::Dollar => Some(SpellDescriptionNode::Text(TextNode::new("$".to_string()))),
112
113            Token::Pipe => Some(SpellDescriptionNode::Text(TextNode::new("|".to_string()))),
114
115            Token::LBracket => Some(SpellDescriptionNode::Text(TextNode::new("[".to_string()))),
116
117            Token::RBracket => Some(SpellDescriptionNode::Text(TextNode::new("]".to_string()))),
118
119            Token::Question => Some(SpellDescriptionNode::Text(TextNode::new("?".to_string()))),
120
121            Token::CondFuncCall(s) => {
122                Some(SpellDescriptionNode::Text(TextNode::new(s.to_string())))
123            }
124        }
125    }
126
127    // #t(block: rust_unchecked_indexing) slicing on find() result guarantees valid indices
128    fn parse_expression_block(&mut self, content: &str) -> Option<SpellDescriptionNode> {
129        let (expr_content, decimal_places) = if let Some(brace_idx) = content.find('}') {
130            let expr = &content[..brace_idx];
131            let after = &content[brace_idx + 1..];
132
133            let decimals = if let Some(after_dot) = after.strip_prefix('.') {
134                after_dot
135                    .chars()
136                    .take_while(char::is_ascii_digit)
137                    .collect::<String>()
138                    .parse::<u8>()
139                    .ok()
140            } else {
141                None
142            };
143
144            (expr, decimals)
145        } else {
146            (content, None)
147        };
148
149        let expr_tokens: Vec<_> = lex_expr(expr_content).filter_map(Result::ok).collect();
150        let mut expr_parser = ExprParser::new(&expr_tokens);
151        let (expression, inline_decimals) = expr_parser.parse();
152
153        self.errors.extend(expr_parser.errors);
154
155        let final_decimals = decimal_places.or(inline_decimals);
156
157        expression.map(|expr| {
158            SpellDescriptionNode::ExpressionBlock(ExpressionBlockNode {
159                expression: expr,
160                decimal_places: final_decimals,
161            })
162        })
163    }
164
165    fn parse_conditional(&mut self) -> SpellDescriptionNode {
166        let mut conditions = Vec::new();
167        let mut else_branch = None;
168
169        let predicate = self.parse_condition_predicate();
170
171        let content = self.parse_branch_content();
172
173        conditions.push(ConditionalBranchNode { predicate, content });
174
175        loop {
176            match self.peek() {
177                Some(Token::Question) => {
178                    self.advance();
179                    let pred = self.parse_condition_predicate();
180                    let cont = self.parse_branch_content();
181
182                    conditions.push(ConditionalBranchNode {
183                        predicate: pred,
184                        content: cont,
185                    });
186                }
187                Some(Token::LBracket) => {
188                    else_branch = Some(self.parse_branch_content());
189                    break;
190                }
191                _ => break,
192            }
193        }
194
195        SpellDescriptionNode::Conditional(ConditionalNode {
196            conditions,
197            else_branch,
198        })
199    }
200
201    fn parse_condition_predicate(&mut self) -> ConditionalPredicateNode {
202        let mut conditions = Vec::new();
203
204        if let Some(cond) = self.parse_single_condition() {
205            conditions.push(cond);
206        }
207
208        while matches!(self.peek(), Some(Token::Pipe)) {
209            self.advance();
210
211            if let Some(cond) = self.parse_single_condition() {
212                conditions.push(cond);
213            }
214        }
215
216        ConditionalPredicateNode { conditions }
217    }
218
219    fn parse_single_condition(&mut self) -> Option<SingleConditionNode> {
220        match self.peek() {
221            Some(Token::CondFuncCall(s)) => {
222                let s = *s;
223
224                self.advance();
225
226                Some(parse_cond_func_call(s))
227            }
228            Some(Token::Text(s) | Token::SimpleVariable(s)) => {
229                let s = *s;
230
231                self.advance();
232
233                parse_condition_type(s)
234            }
235            _ => None,
236        }
237    }
238
239    fn parse_branch_content(&mut self) -> Vec<SpellDescriptionNode> {
240        let mut nodes = Vec::new();
241
242        if !matches!(self.peek(), Some(Token::LBracket)) {
243            return nodes;
244        }
245
246        self.advance();
247
248        let mut depth = 1;
249
250        while depth > 0 && !self.stream.is_empty() {
251            match self.peek() {
252                Some(Token::LBracket) => {
253                    depth += 1;
254                    self.advance();
255                    nodes.push(SpellDescriptionNode::Text(TextNode::new("[".to_string())));
256                }
257                Some(Token::RBracket) => {
258                    depth -= 1;
259                    self.advance();
260
261                    if depth > 0 {
262                        nodes.push(SpellDescriptionNode::Text(TextNode::new("]".to_string())));
263                    }
264                }
265                Some(Token::ConditionalStart) => {
266                    self.advance();
267                    nodes.push(self.parse_conditional());
268                }
269                _ => {
270                    if let Some(node) = self.parse_segment() {
271                        nodes.push(node);
272                    }
273                }
274            }
275        }
276
277        nodes
278    }
279}