Skip to main content

wowlab_parsers/parsers/spell_desc/parser/
expr.rs

1// #t(file: rust_unchecked_indexing) parser slices token strings from regex-matched logos output with guaranteed structure
2
3use super::{
4    super::{
5        lexer::{ExprToken, lex_expr},
6        types::{
7            BinaryExpressionNode, BinaryOperator, ExpressionConditionNode, ExpressionNode,
8            FunctionCallNode, MiscVariableNode, NumberLiteralNode, ParenExpressionNode,
9            SingleConditionNode, UnaryExpressionNode, UnaryOperator, VariableNode,
10        },
11    },
12    ParseError,
13    variables::{
14        parse_at_var, parse_cross_spell_ref, parse_custom_var, parse_effect_var, parse_enchant_var,
15        parse_misc_var, parse_player_var, parse_spell_level_var,
16    },
17};
18use crate::parsers::TokenStream;
19
20pub(super) struct ExprParser<'a> {
21    stream: TokenStream<'a, ExprToken<'a>>,
22    decimal_places: Option<u8>,
23    pub(super) errors: Vec<ParseError>,
24}
25
26impl<'a> ExprParser<'a> {
27    pub(super) fn new(tokens: &'a [ExprToken<'a>]) -> Self {
28        Self {
29            stream: TokenStream::new(tokens),
30            decimal_places: None,
31            errors: Vec::new(),
32        }
33    }
34
35    pub(super) fn parse(&mut self) -> (Option<ExpressionNode>, Option<u8>) {
36        let expr = self.parse_expression();
37
38        if let Some(ExprToken::DecimalFormat(d)) = self.peek() {
39            self.decimal_places = Some(*d);
40            self.advance();
41        }
42
43        (expr, self.decimal_places)
44    }
45
46    fn peek(&self) -> Option<&ExprToken<'a>> {
47        self.stream.peek()
48    }
49
50    fn advance(&mut self) -> Option<&ExprToken<'a>> {
51        self.stream.advance()
52    }
53
54    fn parse_expression(&mut self) -> Option<ExpressionNode> {
55        self.parse_additive()
56    }
57
58    fn parse_additive(&mut self) -> Option<ExpressionNode> {
59        let mut left = self.parse_multiplicative()?;
60
61        loop {
62            let op = match self.peek() {
63                Some(ExprToken::Plus) => BinaryOperator::Add,
64                Some(ExprToken::Minus) => BinaryOperator::Sub,
65                _ => break,
66            };
67
68            self.advance();
69
70            let right = self.parse_multiplicative()?;
71
72            left = ExpressionNode::Binary(BinaryExpressionNode {
73                left: Box::new(left),
74                operator: op,
75                right: Box::new(right),
76            });
77        }
78
79        Some(left)
80    }
81
82    fn parse_multiplicative(&mut self) -> Option<ExpressionNode> {
83        let mut left = self.parse_unary()?;
84
85        loop {
86            let op = match self.peek() {
87                Some(ExprToken::Star) => BinaryOperator::Mul,
88                Some(ExprToken::Slash) => BinaryOperator::Div,
89                _ => break,
90            };
91
92            self.advance();
93
94            let right = self.parse_unary()?;
95
96            left = ExpressionNode::Binary(BinaryExpressionNode {
97                left: Box::new(left),
98                operator: op,
99                right: Box::new(right),
100            });
101        }
102
103        Some(left)
104    }
105
106    fn parse_unary(&mut self) -> Option<ExpressionNode> {
107        if matches!(self.peek(), Some(ExprToken::Minus)) {
108            self.advance();
109            let operand = self.parse_atomic()?;
110
111            return Some(ExpressionNode::Unary(UnaryExpressionNode {
112                operator: UnaryOperator::Neg,
113                operand: Box::new(operand),
114            }));
115        }
116
117        self.parse_atomic()
118    }
119
120    // #t(rust_cyclomatic_complexity) expression token dispatch over all variable kinds
121    fn parse_atomic(&mut self) -> Option<ExpressionNode> {
122        match self.peek() {
123            Some(ExprToken::LParen) => {
124                self.advance();
125                let expr = self.parse_expression()?;
126
127                if matches!(self.peek(), Some(ExprToken::RParen)) {
128                    self.advance();
129                }
130
131                Some(ExpressionNode::Paren(ParenExpressionNode {
132                    expression: Box::new(expr),
133                }))
134            }
135
136            // #t(block: rust_unchecked_indexing) regex guarantees $ prefix
137            Some(ExprToken::DollarFunc(s)) => {
138                let func_name = s[1..].to_string();
139
140                self.advance();
141
142                Some(self.parse_function_args(func_name))
143            }
144
145            Some(ExprToken::Ident(s)) => {
146                let func_name = (*s).to_string();
147
148                self.advance();
149
150                if matches!(self.peek(), Some(ExprToken::LParen)) {
151                    Some(self.parse_function_args(func_name))
152                } else {
153                    self.errors.push(ParseError::new(format!(
154                        "Unexpected identifier: {func_name}"
155                    )));
156
157                    None
158                }
159            }
160
161            Some(ExprToken::Number(n)) => {
162                let value = *n;
163
164                self.advance();
165
166                Some(ExpressionNode::Number(NumberLiteralNode::new(value)))
167            }
168
169            Some(ExprToken::CustomVar(s)) => {
170                let var = parse_custom_var(s);
171
172                self.advance();
173
174                Some(ExpressionNode::Variable(Box::new(var)))
175            }
176
177            Some(ExprToken::EffectVar(s)) => {
178                let var = parse_effect_var(s);
179
180                self.advance();
181
182                Some(ExpressionNode::Variable(Box::new(var)))
183            }
184
185            Some(ExprToken::SpellLevelVar(s)) => {
186                let var = parse_spell_level_var(s);
187
188                self.advance();
189
190                Some(ExpressionNode::Variable(Box::new(var)))
191            }
192
193            Some(ExprToken::PlayerVar(s)) => {
194                let var = parse_player_var(s);
195
196                self.advance();
197
198                Some(ExpressionNode::Variable(Box::new(var)))
199            }
200
201            Some(ExprToken::EnchantVar(s)) => {
202                let var = parse_enchant_var(s);
203
204                self.advance();
205
206                Some(ExpressionNode::Variable(Box::new(var)))
207            }
208
209            Some(ExprToken::MiscVar(s)) => {
210                let var = parse_misc_var(s);
211
212                self.advance();
213
214                Some(ExpressionNode::Variable(Box::new(var)))
215            }
216
217            Some(ExprToken::AtVar(s)) => {
218                let var = parse_at_var(s);
219
220                self.advance();
221
222                Some(ExpressionNode::Variable(Box::new(var)))
223            }
224
225            Some(ExprToken::CrossSpellRef(s)) => {
226                let var = parse_cross_spell_ref(s);
227
228                self.advance();
229
230                Some(ExpressionNode::Variable(Box::new(var)))
231            }
232
233            // #t(block: rust_unchecked_indexing) regex guarantees $ prefix
234            Some(ExprToken::SimpleVar(s)) => {
235                let var = VariableNode::Misc(MiscVariableNode {
236                    var_name: s[1..].to_string(),
237                    id: None,
238                });
239
240                self.advance();
241
242                Some(ExpressionNode::Variable(Box::new(var)))
243            }
244
245            _ => None,
246        }
247    }
248
249    fn parse_function_args(&mut self, func_name: String) -> ExpressionNode {
250        if !matches!(self.peek(), Some(ExprToken::LParen)) {
251            return ExpressionNode::FunctionCall(FunctionCallNode {
252                func_name,
253                args: Vec::new(),
254            });
255        }
256
257        self.advance();
258
259        let mut args = Vec::new();
260
261        if !matches!(self.peek(), Some(ExprToken::RParen)) {
262            if let Some(arg) = self.parse_expression() {
263                args.push(arg);
264            }
265
266            while matches!(self.peek(), Some(ExprToken::Comma)) {
267                self.advance();
268
269                if let Some(arg) = self.parse_expression() {
270                    args.push(arg);
271                }
272            }
273        }
274
275        if matches!(self.peek(), Some(ExprToken::RParen)) {
276            self.advance();
277        }
278
279        ExpressionNode::FunctionCall(FunctionCallNode { func_name, args })
280    }
281}
282
283pub(super) fn parse_cond_func_call(s: &str) -> SingleConditionNode {
284    let rest = s.strip_prefix('$').unwrap_or(s);
285    let paren_idx = rest.find('(').unwrap_or(rest.len());
286    let func_name = rest[..paren_idx].to_string();
287
288    let args_str = if paren_idx < rest.len() {
289        let inner = &rest[paren_idx + 1..];
290
291        inner.strip_suffix(')').unwrap_or(inner)
292    } else {
293        ""
294    };
295
296    let mut args = Vec::new();
297
298    if !args_str.is_empty() {
299        let args_tokens: Vec<_> = lex_expr(args_str).filter_map(Result::ok).collect();
300        let mut expr_parser = ExprParser::new(&args_tokens);
301
302        if let Some(expr) = expr_parser.parse_expression() {
303            args.push(expr);
304        }
305
306        while matches!(expr_parser.peek(), Some(ExprToken::Comma)) {
307            expr_parser.advance();
308
309            if let Some(expr) = expr_parser.parse_expression() {
310                args.push(expr);
311            }
312        }
313    }
314
315    SingleConditionNode::Expression(ExpressionConditionNode { func_name, args })
316}