Skip to main content

darklua_core/nodes/
literal_expression.rs

1use std::{convert::TryFrom, num::FpCategory};
2
3use crate::nodes::{
4    BinaryNumber, DecimalNumber, Expression, HexNumber, Identifier, NumberExpression,
5    StringExpression, TableEntry, TableExpression, TableFieldEntry, TableTokens, Token, Trivia,
6};
7
8/// A literal expression in Luau.
9///
10/// Literal expressions are used in function attribute arguments
11/// (e.g., `@[attr(true, 42, "text")]`).
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum LiteralExpression {
14    /// The boolean literal `true`
15    True(Option<Token>),
16    /// The boolean literal `false`
17    False(Option<Token>),
18    /// The nil literal
19    Nil(Option<Token>),
20    /// A numeric literal (e.g., `42`, `3.14`)
21    Number(NumberExpression),
22    /// A string literal (e.g., `"hello"`, `[[text]]`)
23    String(StringExpression),
24    /// A table literal (e.g., `{ key = value }`)
25    Table(Box<LiteralTable>),
26}
27
28impl LiteralExpression {
29    /// Creates a new nil literal expression.
30    pub fn nil() -> Self {
31        Self::Nil(None)
32    }
33}
34
35impl From<LiteralTable> for LiteralExpression {
36    fn from(v: LiteralTable) -> Self {
37        Self::Table(Box::new(v))
38    }
39}
40
41impl From<StringExpression> for LiteralExpression {
42    fn from(v: StringExpression) -> Self {
43        Self::String(v)
44    }
45}
46
47impl From<bool> for LiteralExpression {
48    fn from(v: bool) -> Self {
49        if v {
50            Self::True(None)
51        } else {
52            Self::False(None)
53        }
54    }
55}
56
57impl TryFrom<f64> for LiteralExpression {
58    type Error = &'static str;
59
60    fn try_from(value: f64) -> Result<Self, Self::Error> {
61        match value.classify() {
62            FpCategory::Nan => Err("NaN is not a valid literal expression"),
63            FpCategory::Infinite => Err("Infinity is not a valid literal expression"),
64            FpCategory::Zero => {
65                Ok(DecimalNumber::new(if value.is_sign_positive() {
66                    0.0
67                } else {
68                    // if literal expression are allowed to have negative values in the
69                    // future, we should return -0.0 here
70                    0.0
71                })
72                .into())
73            }
74            FpCategory::Subnormal | FpCategory::Normal => {
75                if value < 0.0 {
76                    Err("Negative numbers are not a valid literal expression")
77                } else if value < 0.1 {
78                    let exponent = value.log10().floor();
79
80                    Ok(DecimalNumber::new(value)
81                        .with_exponent(exponent as i64, true)
82                        .into())
83                } else if value > 999.0 && (value / 100.0).fract() == 0.0 {
84                    let mut exponent = value.log10().floor();
85                    let mut power = 10_f64.powf(exponent);
86
87                    while exponent > 2.0 && (value / power).fract() != 0.0 {
88                        exponent -= 1.0;
89                        power /= 10.0;
90                    }
91
92                    Ok(DecimalNumber::new(value)
93                        .with_exponent(exponent as i64, true)
94                        .into())
95                } else {
96                    Ok(DecimalNumber::new(value).into())
97                }
98            }
99        }
100    }
101}
102
103impl TryFrom<f32> for LiteralExpression {
104    type Error = &'static str;
105
106    fn try_from(value: f32) -> Result<Self, Self::Error> {
107        LiteralExpression::try_from(value as f64)
108    }
109}
110
111impl From<NumberExpression> for LiteralExpression {
112    fn from(v: NumberExpression) -> Self {
113        Self::Number(v)
114    }
115}
116
117impl TryFrom<u64> for LiteralExpression {
118    type Error = &'static str;
119
120    fn try_from(value: u64) -> Result<Self, Self::Error> {
121        LiteralExpression::try_from(value as f64)
122    }
123}
124
125impl From<u32> for LiteralExpression {
126    fn from(value: u32) -> Self {
127        LiteralExpression::try_from(value as f64)
128            .expect("converting a u32 to a literal number expression should never fail")
129    }
130}
131
132impl From<u16> for LiteralExpression {
133    fn from(value: u16) -> Self {
134        LiteralExpression::try_from(value as f64)
135            .expect("converting a u16 to a literal number expression should never fail")
136    }
137}
138
139impl From<u8> for LiteralExpression {
140    fn from(value: u8) -> Self {
141        LiteralExpression::try_from(value as f64)
142            .expect("converting a u8 to a literal number expression should never fail")
143    }
144}
145
146impl TryFrom<i64> for LiteralExpression {
147    type Error = &'static str;
148
149    fn try_from(value: i64) -> Result<Self, Self::Error> {
150        LiteralExpression::try_from(value as f64)
151    }
152}
153
154impl TryFrom<i32> for LiteralExpression {
155    type Error = &'static str;
156
157    fn try_from(value: i32) -> Result<Self, Self::Error> {
158        LiteralExpression::try_from(value as f64)
159    }
160}
161
162impl TryFrom<i16> for LiteralExpression {
163    type Error = &'static str;
164
165    fn try_from(value: i16) -> Result<Self, Self::Error> {
166        LiteralExpression::try_from(value as f64)
167    }
168}
169
170impl TryFrom<i8> for LiteralExpression {
171    type Error = &'static str;
172
173    fn try_from(value: i8) -> Result<Self, Self::Error> {
174        LiteralExpression::try_from(value as f64)
175    }
176}
177
178impl From<DecimalNumber> for LiteralExpression {
179    fn from(number: DecimalNumber) -> Self {
180        Self::Number(NumberExpression::Decimal(number))
181    }
182}
183
184impl From<HexNumber> for LiteralExpression {
185    fn from(number: HexNumber) -> Self {
186        Self::Number(NumberExpression::Hex(number))
187    }
188}
189
190impl From<BinaryNumber> for LiteralExpression {
191    fn from(number: BinaryNumber) -> Self {
192        Self::Number(NumberExpression::Binary(number))
193    }
194}
195
196impl<T: Into<LiteralExpression>> From<Option<T>> for LiteralExpression {
197    fn from(value: Option<T>) -> Self {
198        match value {
199            None => Self::nil(),
200            Some(value) => value.into(),
201        }
202    }
203}
204
205impl From<LiteralExpression> for Expression {
206    fn from(literal: LiteralExpression) -> Self {
207        match literal {
208            LiteralExpression::True(token) => Self::True(token),
209            LiteralExpression::False(token) => Self::False(token),
210            LiteralExpression::Nil(token) => Self::Nil(token),
211            LiteralExpression::Number(num) => Self::Number(num),
212            LiteralExpression::String(string) => Self::String(string),
213            LiteralExpression::Table(table) => Self::Table((*table).into()),
214        }
215    }
216}
217
218/// A literal table in Luau.
219///
220/// Table literals follow the syntax `{ entry1, entry2, key = value }`.
221#[derive(Clone, Debug, Default, PartialEq, Eq)]
222pub struct LiteralTable {
223    entries: Vec<LiteralTableEntry>,
224    tokens: Option<TableTokens>,
225}
226
227impl LiteralTable {
228    /// Creates a new literal table from a vector of entries.
229    pub fn from_entries(entries: Vec<LiteralTableEntry>) -> Self {
230        Self {
231            entries,
232            tokens: None,
233        }
234    }
235
236    /// Adds an entry to this table.
237    pub fn with_entry(mut self, entry: impl Into<LiteralTableEntry>) -> Self {
238        self.entries.push(entry.into());
239        self
240    }
241
242    /// Appends an entry to this table.
243    pub fn append_entry(&mut self, entry: impl Into<LiteralTableEntry>) {
244        self.entries.push(entry.into());
245    }
246
247    /// Appends a field entry to this table.
248    pub fn append_field(&mut self, field: Identifier, value: impl Into<LiteralExpression>) {
249        self.append_entry(LiteralTableEntry::Field(Box::new(LiteralTableFieldEntry {
250            field,
251            value: value.into(),
252            token: None,
253        })));
254    }
255
256    /// Appends an array value entry to this table.
257    pub fn append_array_value(&mut self, value: impl Into<LiteralExpression>) {
258        self.append_entry(LiteralTableEntry::Value(Box::new(value.into())));
259    }
260
261    /// Returns an iterator over the table entries.
262    pub fn iter_entries(&self) -> impl Iterator<Item = &LiteralTableEntry> {
263        self.entries.iter()
264    }
265
266    /// Returns a mutable iterator over the table entries.
267    pub fn iter_mut_entries(&mut self) -> impl Iterator<Item = &mut LiteralTableEntry> {
268        self.entries.iter_mut()
269    }
270
271    /// Attaches tokens to this table.
272    pub fn with_tokens(mut self, tokens: TableTokens) -> Self {
273        self.tokens = Some(tokens);
274        self
275    }
276
277    /// Sets the tokens for this table.
278    pub fn set_tokens(&mut self, tokens: TableTokens) {
279        self.tokens = Some(tokens);
280    }
281
282    /// Returns the tokens for this table, if any.
283    pub fn get_tokens(&self) -> Option<&TableTokens> {
284        self.tokens.as_ref()
285    }
286
287    /// Returns the number of entries in this table.
288    pub fn len(&self) -> usize {
289        self.entries.len()
290    }
291
292    /// Returns whether this table is empty.
293    pub fn is_empty(&self) -> bool {
294        self.entries.is_empty()
295    }
296
297    super::impl_token_fns!(iter = [tokens, entries]);
298}
299
300/// An entry in a literal table.
301#[derive(Clone, Debug, PartialEq, Eq)]
302pub enum LiteralTableEntry {
303    /// A named field entry (e.g., `{ field = value }`)
304    Field(Box<LiteralTableFieldEntry>),
305    /// A value entry for array-like tables (e.g., `{ value }`)
306    Value(Box<LiteralExpression>),
307}
308
309impl LiteralTableEntry {
310    /// Creates a value entry from a literal expression.
311    pub fn from_value(value: impl Into<LiteralExpression>) -> Self {
312        Self::Value(Box::new(value.into()))
313    }
314
315    /// Clears all comments from the tokens in this node.
316    pub fn clear_comments(&mut self) {
317        match self {
318            Self::Field(entry) => entry.clear_comments(),
319            Self::Value(_value) => {}
320        }
321    }
322
323    /// Clears all whitespaces information from the tokens in this node.
324    pub fn clear_whitespaces(&mut self) {
325        match self {
326            Self::Field(entry) => entry.clear_whitespaces(),
327            Self::Value(_value) => {}
328        }
329    }
330
331    pub(crate) fn replace_referenced_tokens(&mut self, code: &str) {
332        match self {
333            Self::Field(entry) => entry.replace_referenced_tokens(code),
334            Self::Value(_value) => {}
335        }
336    }
337
338    pub(crate) fn shift_token_line(&mut self, amount: isize) {
339        match self {
340            Self::Field(entry) => entry.shift_token_line(amount),
341            Self::Value(_value) => {}
342        }
343    }
344
345    pub(crate) fn filter_comments(&mut self, filter: impl Fn(&Trivia) -> bool) {
346        match self {
347            Self::Field(entry) => entry.filter_comments(filter),
348            Self::Value(_value) => {}
349        }
350    }
351}
352
353impl From<LiteralTableFieldEntry> for LiteralTableEntry {
354    fn from(v: LiteralTableFieldEntry) -> Self {
355        Self::Field(Box::new(v))
356    }
357}
358
359impl From<LiteralExpression> for LiteralTableEntry {
360    fn from(v: LiteralExpression) -> Self {
361        Self::Value(Box::new(v))
362    }
363}
364
365/// A field entry in a literal table.
366///
367/// Represents a named field assignment: `{ field = value }`.
368#[derive(Clone, Debug, PartialEq, Eq)]
369pub struct LiteralTableFieldEntry {
370    field: Identifier,
371    value: LiteralExpression,
372    token: Option<Token>,
373}
374
375impl LiteralTableFieldEntry {
376    /// Attaches a token to this field entry for the `=` symbol.
377    pub fn with_token(mut self, token: Token) -> Self {
378        self.token = Some(token);
379        self
380    }
381
382    /// Sets the token for this field entry's `=` symbol.
383    pub fn set_token(&mut self, token: Token) {
384        self.token = Some(token);
385    }
386
387    /// Returns the token for this field entry's `=` symbol, if any.
388    pub fn get_token(&self) -> Option<&Token> {
389        self.token.as_ref()
390    }
391
392    /// Returns the field name.
393    pub fn get_field(&self) -> &Identifier {
394        &self.field
395    }
396
397    /// Returns a mutable reference to the field name.
398    pub fn mutate_field(&mut self) -> &mut Identifier {
399        &mut self.field
400    }
401
402    /// Returns the field value.
403    pub fn get_value(&self) -> &LiteralExpression {
404        &self.value
405    }
406
407    /// Returns a mutable reference to the field value.
408    pub fn mutate_value(&mut self) -> &mut LiteralExpression {
409        &mut self.value
410    }
411
412    /// Returns a mutable reference to the token for this field entry's `=` symbol, if any.
413    pub fn mutate_token(&mut self) -> Option<&mut Token> {
414        self.token.as_mut()
415    }
416
417    super::impl_token_fns!(
418        target = [field]
419        iter = [token]
420    );
421}
422
423impl From<LiteralTable> for TableExpression {
424    fn from(literal_table: LiteralTable) -> Self {
425        let entries = literal_table
426            .entries
427            .into_iter()
428            .map(|entry| match entry {
429                LiteralTableEntry::Field(field) => {
430                    let field = *field;
431                    TableEntry::Field(Box::new(TableFieldEntry::new(
432                        field.field,
433                        Expression::from(field.value),
434                    )))
435                }
436                LiteralTableEntry::Value(value) => {
437                    TableEntry::Value(Box::new(Expression::from(*value)))
438                }
439            })
440            .collect();
441
442        let mut table = TableExpression::new(entries);
443        if let Some(tokens) = literal_table.tokens {
444            table.set_tokens(tokens);
445        }
446        table
447    }
448}