Skip to main content

darklua_core/nodes/statements/
local_assign.rs

1use crate::nodes::{Expression, Token, TypedIdentifier};
2
3#[deprecated(since = "0.19.0", note = "Renamed to `VariableAssignmentTokens`")]
4pub type LocalAssignTokens = VariableAssignmentTokens;
5
6#[deprecated(since = "0.19.0", note = "Renamed to `VariableAssignment`")]
7pub type LocalAssignStatement = VariableAssignment;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
10pub enum AssignmentKind {
11    #[default]
12    Local,
13    Const,
14}
15
16impl AssignmentKind {
17    pub fn as_keyword(&self) -> &'static str {
18        match self {
19            AssignmentKind::Local => "local",
20            AssignmentKind::Const => "const",
21        }
22    }
23}
24
25/// Tokens associated with a local variable assignment statement.
26#[derive(Clone, Debug, PartialEq, Eq)]
27pub struct VariableAssignmentTokens {
28    pub keyword: Token,
29    /// The token for the equal sign, if any.
30    pub equal: Option<Token>,
31    /// The tokens for the commas between variables.
32    pub variable_commas: Vec<Token>,
33    /// The tokens for the commas between values.
34    pub value_commas: Vec<Token>,
35}
36
37impl VariableAssignmentTokens {
38    super::impl_token_fns!(
39        target = [keyword]
40        iter = [variable_commas, value_commas, equal]
41    );
42}
43
44/// Represents a local variable assignment statement.
45#[derive(Clone, Debug, PartialEq, Eq)]
46pub struct VariableAssignment {
47    keyword: AssignmentKind,
48    variables: Vec<TypedIdentifier>,
49    values: Vec<Expression>,
50    tokens: Option<VariableAssignmentTokens>,
51}
52
53impl VariableAssignment {
54    /// Creates a new local assignment statement with the given variables and values.
55    pub fn new(variables: Vec<TypedIdentifier>, values: Vec<Expression>) -> Self {
56        Self {
57            keyword: AssignmentKind::Local,
58            variables,
59            values,
60            tokens: None,
61        }
62    }
63
64    /// Creates a new local assignment statement with a single variable and no values.
65    pub fn from_variable<S: Into<TypedIdentifier>>(variable: S) -> Self {
66        Self {
67            keyword: AssignmentKind::Local,
68            variables: vec![variable.into()],
69            values: Vec::new(),
70            tokens: None,
71        }
72    }
73
74    /// Sets the tokens for this local assignment statement.
75    pub fn with_tokens(mut self, tokens: VariableAssignmentTokens) -> Self {
76        self.tokens = Some(tokens);
77        self
78    }
79
80    /// Sets the tokens for this local assignment statement.
81    #[inline]
82    pub fn set_tokens(&mut self, tokens: VariableAssignmentTokens) {
83        self.tokens = Some(tokens);
84    }
85
86    /// Returns the tokens for this local assignment statement, if any.
87    #[inline]
88    pub fn get_tokens(&self) -> Option<&VariableAssignmentTokens> {
89        self.tokens.as_ref()
90    }
91
92    /// Returns a mutable reference to the tokens, if any.
93    #[inline]
94    pub fn mutate_tokens(&mut self) -> Option<&mut VariableAssignmentTokens> {
95        self.tokens.as_mut()
96    }
97
98    /// Adds a variable to this local assignment statement.
99    pub fn with_variable<S: Into<TypedIdentifier>>(mut self, variable: S) -> Self {
100        self.variables.push(variable.into());
101        self
102    }
103
104    /// Adds a value to this local assignment statement.
105    pub fn with_value<E: Into<Expression>>(mut self, value: E) -> Self {
106        self.values.push(value.into());
107        self
108    }
109
110    /// Converts this statement into a tuple of variables and values.
111    pub fn into_assignments(self) -> (Vec<TypedIdentifier>, Vec<Expression>) {
112        (self.variables, self.values)
113    }
114
115    /// Adds a new variable-value pair to this local assignment statement.
116    pub fn append_assignment<S: Into<TypedIdentifier>>(&mut self, variable: S, value: Expression) {
117        self.variables.push(variable.into());
118        self.values.push(value);
119    }
120
121    /// Applies a function to each variable-value pair.
122    pub fn for_each_assignment<F>(&mut self, mut callback: F)
123    where
124        F: FnMut(&mut TypedIdentifier, Option<&mut Expression>),
125    {
126        let mut values = self.values.iter_mut();
127        self.variables
128            .iter_mut()
129            .for_each(|variable| callback(variable, values.next()));
130    }
131
132    /// Sets the assignment kind for this assignment.
133    pub fn with_assignment_kind(mut self, kind: AssignmentKind) -> Self {
134        self.set_assignment_kind(kind);
135        self
136    }
137
138    /// Sets the assignment kind for this assignment.
139    pub fn set_assignment_kind(&mut self, kind: AssignmentKind) {
140        if self.keyword == kind {
141            return;
142        }
143        if let Some(tokens) = &mut self.tokens {
144            tokens.keyword.replace_with_content(kind.as_keyword());
145        }
146        self.keyword = kind;
147    }
148
149    /// Returns the assignment kind for this assignment.
150    pub fn get_assignment_kind(&self) -> AssignmentKind {
151        self.keyword
152    }
153
154    /// Returns the list of variables.
155    #[inline]
156    pub fn get_variables(&self) -> &Vec<TypedIdentifier> {
157        &self.variables
158    }
159
160    /// Returns an iterator over the variables.
161    #[inline]
162    pub fn iter_variables(&self) -> impl Iterator<Item = &TypedIdentifier> {
163        self.variables.iter()
164    }
165
166    /// Returns a mutable iterator over the variables.
167    #[inline]
168    pub fn iter_mut_variables(&mut self) -> impl Iterator<Item = &mut TypedIdentifier> {
169        self.variables.iter_mut()
170    }
171
172    /// Appends variables from another vector.
173    #[inline]
174    pub fn append_variables(&mut self, variables: &mut Vec<TypedIdentifier>) {
175        self.variables.append(variables);
176    }
177
178    /// Extends the values with elements from an iterator.
179    #[inline]
180    pub fn extend_values<T: IntoIterator<Item = Expression>>(&mut self, iter: T) {
181        self.values.extend(iter);
182    }
183
184    /// Returns a mutable iterator over the values.
185    #[inline]
186    pub fn iter_mut_values(&mut self) -> impl Iterator<Item = &mut Expression> {
187        self.values.iter_mut()
188    }
189
190    /// Returns an iterator over the values.
191    #[inline]
192    pub fn iter_values(&self) -> impl Iterator<Item = &Expression> {
193        self.values.iter()
194    }
195
196    /// Adds a variable to this local assignment statement.
197    #[inline]
198    pub fn push_variable(&mut self, variable: impl Into<TypedIdentifier>) {
199        self.variables.push(variable.into());
200    }
201
202    /// Adds a value to this local assignment statement.
203    #[inline]
204    pub fn push_value(&mut self, value: impl Into<Expression>) {
205        self.values.push(value.into());
206    }
207
208    /// Appends values from another vector.
209    #[inline]
210    pub fn append_values(&mut self, values: &mut Vec<Expression>) {
211        self.values.append(values);
212    }
213
214    /// Returns the last value, if any.
215    #[inline]
216    pub fn last_value(&self) -> Option<&Expression> {
217        self.values.last()
218    }
219
220    /// Removes and returns the last value, adjusting tokens as needed.
221    pub fn pop_value(&mut self) -> Option<Expression> {
222        let value = self.values.pop();
223        if let Some(tokens) = &mut self.tokens {
224            let length = self.values.len();
225            if length == 0 {
226                if !tokens.value_commas.is_empty() {
227                    tokens.value_commas.clear();
228                }
229                if tokens.equal.is_some() {
230                    tokens.equal = None;
231                }
232            } else {
233                tokens.value_commas.truncate(length.saturating_sub(1));
234            }
235        }
236        value
237    }
238
239    /// Removes and returns the value at the given index, adjusting tokens as needed.
240    pub fn remove_value(&mut self, index: usize) -> Option<Expression> {
241        if index < self.values.len() {
242            let value = self.values.remove(index);
243
244            if let Some(tokens) = &mut self.tokens {
245                if index < tokens.value_commas.len() {
246                    tokens.value_commas.remove(index);
247                }
248                if self.values.is_empty() && tokens.equal.is_some() {
249                    tokens.equal = None;
250                }
251            }
252
253            Some(value)
254        } else {
255            None
256        }
257    }
258
259    /// Removes and returns the variable at the given index, adjusting tokens as needed.
260    ///
261    /// Returns None if there is only one variable or if the index is out of bounds.
262    pub fn remove_variable(&mut self, index: usize) -> Option<TypedIdentifier> {
263        let len = self.variables.len();
264
265        if len > 1 && index < len {
266            let variable = self.variables.remove(index);
267
268            if let Some(tokens) = &mut self.tokens {
269                if index < tokens.variable_commas.len() {
270                    tokens.variable_commas.remove(index);
271                }
272            }
273
274            Some(variable)
275        } else {
276            None
277        }
278    }
279
280    /// Returns the number of values.
281    #[inline]
282    pub fn values_len(&self) -> usize {
283        self.values.len()
284    }
285
286    /// Returns the number of variables.
287    #[inline]
288    pub fn variables_len(&self) -> usize {
289        self.variables.len()
290    }
291
292    /// Returns whether this statement has any values.
293    #[inline]
294    pub fn has_values(&self) -> bool {
295        !self.values.is_empty()
296    }
297
298    /// In `const` assignments, there may be a need to append `nil` values after the actual
299    /// values to make sure the assignment is valid.
300    pub fn required_nil_values(&self) -> usize {
301        match self.keyword {
302            AssignmentKind::Local => 0,
303            AssignmentKind::Const => {
304                let length = self.variables.len();
305
306                if length <= self.values.len()
307                    || self
308                        .values
309                        .last()
310                        .map(|last| {
311                            matches!(last, Expression::Call(_) | Expression::VariableArguments(_))
312                        })
313                        .unwrap_or_default()
314                {
315                    0
316                } else {
317                    length - self.values.len()
318                }
319            }
320        }
321    }
322
323    /// For `const` assignments, if there are less variables than values, this function returns the
324    /// amount of new variables that are needed to make the assignment valid.
325    /// For `local` assignments, this function returns 1 if there are no variables.
326    pub fn required_new_variables(&self) -> usize {
327        match self.keyword {
328            AssignmentKind::Local if self.variables.is_empty() => 1,
329            AssignmentKind::Local => 0,
330            AssignmentKind::Const => self.values.len().saturating_sub(self.variables.len()),
331        }
332    }
333
334    /// Removes type annotations from all variables.
335    pub fn clear_types(&mut self) {
336        for variable in &mut self.variables {
337            variable.remove_type();
338        }
339    }
340
341    /// Returns a mutable reference to the first token for this statement, creating it if missing.
342    pub fn mutate_first_token(&mut self) -> &mut Token {
343        if self.tokens.is_none() {
344            self.tokens = Some(VariableAssignmentTokens {
345                keyword: Token::from_content(self.keyword.as_keyword()),
346                equal: (!self.values.is_empty()).then(|| Token::from_content("=")),
347                variable_commas: Vec::new(),
348                value_commas: Vec::new(),
349            });
350        }
351        &mut self.tokens.as_mut().unwrap().keyword
352    }
353
354    /// Returns a mutable reference to the last token for this statement,
355    /// creating it if missing.
356    pub fn mutate_last_token(&mut self) -> &mut Token {
357        if let Some(last_value) = self.values.last_mut() {
358            return last_value.mutate_last_token();
359        }
360        self.variables
361            .last_mut()
362            .expect("local assign must have at least one variable")
363            .mutate_or_insert_token()
364    }
365
366    super::impl_token_fns!(iter = [variables, tokens]);
367}
368
369#[cfg(test)]
370mod test {
371    use super::*;
372
373    mod pop_value {
374        use super::*;
375
376        #[test]
377        fn removes_the_equal_sign() {
378            let mut assign = VariableAssignment::from_variable("var")
379                .with_value(true)
380                .with_tokens(VariableAssignmentTokens {
381                    keyword: Token::from_content("local"),
382                    equal: Some(Token::from_content("=")),
383                    variable_commas: Vec::new(),
384                    value_commas: Vec::new(),
385                });
386
387            assign.pop_value();
388
389            pretty_assertions::assert_eq!(
390                assign,
391                VariableAssignment::from_variable("var").with_tokens(VariableAssignmentTokens {
392                    keyword: Token::from_content("local"),
393                    equal: None,
394                    variable_commas: Vec::new(),
395                    value_commas: Vec::new(),
396                })
397            );
398        }
399
400        #[test]
401        fn removes_the_last_comma_token() {
402            let mut assign = VariableAssignment::from_variable("var")
403                .with_value(true)
404                .with_value(false)
405                .with_tokens(VariableAssignmentTokens {
406                    keyword: Token::from_content("local"),
407                    equal: Some(Token::from_content("=")),
408                    variable_commas: Vec::new(),
409                    value_commas: vec![Token::from_content(",")],
410                });
411
412            assign.pop_value();
413
414            pretty_assertions::assert_eq!(
415                assign,
416                VariableAssignment::from_variable("var")
417                    .with_value(true)
418                    .with_tokens(VariableAssignmentTokens {
419                        keyword: Token::from_content("local"),
420                        equal: Some(Token::from_content("=")),
421                        variable_commas: Vec::new(),
422                        value_commas: Vec::new(),
423                    })
424            );
425        }
426
427        #[test]
428        fn removes_one_comma_token() {
429            let mut assign = VariableAssignment::from_variable("var")
430                .with_value(true)
431                .with_value(false)
432                .with_value(true)
433                .with_tokens(VariableAssignmentTokens {
434                    keyword: Token::from_content("local"),
435                    equal: Some(Token::from_content("=")),
436                    variable_commas: Vec::new(),
437                    value_commas: vec![Token::from_content(","), Token::from_content(",")],
438                });
439
440            assign.pop_value();
441
442            pretty_assertions::assert_eq!(
443                assign,
444                VariableAssignment::from_variable("var")
445                    .with_value(true)
446                    .with_value(false)
447                    .with_tokens(VariableAssignmentTokens {
448                        keyword: Token::from_content("local"),
449                        equal: Some(Token::from_content("=")),
450                        variable_commas: Vec::new(),
451                        value_commas: vec![Token::from_content(",")],
452                    })
453            );
454        }
455    }
456
457    mod remove_variable {
458        use super::*;
459
460        #[test]
461        fn single_variable_returns_none_without_mutating() {
462            let mut assign = VariableAssignment::from_variable("var").with_value(true);
463            let copy = assign.clone();
464
465            assert_eq!(assign.remove_variable(0), None);
466
467            pretty_assertions::assert_eq!(assign, copy);
468        }
469
470        #[test]
471        fn single_variable_remove_outside_of_bounds() {
472            let mut assign = VariableAssignment::from_variable("var");
473            let copy = assign.clone();
474
475            assert_eq!(assign.remove_variable(1), None);
476            pretty_assertions::assert_eq!(assign, copy);
477
478            assert_eq!(assign.remove_variable(3), None);
479            pretty_assertions::assert_eq!(assign, copy);
480        }
481
482        #[test]
483        fn two_variables_remove_first() {
484            let mut assign = VariableAssignment::from_variable("var")
485                .with_variable("var2")
486                .with_value(true)
487                .with_value(false);
488
489            assert_eq!(assign.remove_variable(0), Some(TypedIdentifier::new("var")));
490
491            pretty_assertions::assert_eq!(
492                assign,
493                VariableAssignment::from_variable("var2")
494                    .with_value(true)
495                    .with_value(false)
496            );
497        }
498
499        #[test]
500        fn two_variables_remove_second() {
501            let mut assign = VariableAssignment::from_variable("var")
502                .with_variable("var2")
503                .with_value(true)
504                .with_value(false);
505
506            assert_eq!(
507                assign.remove_variable(1),
508                Some(TypedIdentifier::new("var2"))
509            );
510
511            pretty_assertions::assert_eq!(
512                assign,
513                VariableAssignment::from_variable("var")
514                    .with_value(true)
515                    .with_value(false)
516            );
517        }
518    }
519}