luau_lexer/token/
literal.rs

1//! Luau literals
2
3use smol_str::SmolStr;
4
5use crate::{
6    prelude::{Lexable, Lexer, ParseError},
7    utils::is_numeric,
8};
9
10/// A Luau string. The stored string will include the quotes/double quotes/backticks.
11/// The only reason the different types actually exist is to allow the user to
12/// easily know which one is used without needing to check the actual string.
13#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
14#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15pub enum LuauString {
16    ///```lua
17    /// 'single quotes'
18    /// ```
19    SingleQuotes(SmolStr),
20
21    ///```lua
22    /// "double quotes"
23    /// ```
24    DoubleQuotes(SmolStr),
25
26    ///```lua
27    /// `backticks`
28    /// ```
29    Backticks(SmolStr),
30
31    ///```lua
32    /// [[ multi line ]]
33    /// [[
34    ///     multi line
35    /// ]]
36    /// [==[
37    ///     multi line
38    /// ]==]
39    /// ```
40    MultiLine(SmolStr),
41}
42
43impl LuauString {
44    /// Counts the number of backslashes at the end of the passed characters array.
45    fn count_back_slashes(characters: &[char]) -> usize {
46        if characters.is_empty() {
47            return 0;
48        }
49
50        let mut count = 0;
51        let mut i = characters.len() - 1;
52
53        while characters[i] == '\\' {
54            count += 1;
55
56            match i.checked_sub(1) {
57                Some(new_i) => i = new_i,
58                None => break,
59            }
60        }
61
62        count
63    }
64
65    /// Whether or not the last character is escaped.
66    fn is_escaped(characters: &[char]) -> bool {
67        if characters.len() < 2 {
68            false
69        } else {
70            Self::count_back_slashes(&characters[..characters.len() - 1]) % 2 != 0
71        }
72    }
73
74    /// Whether or not the array ends with a multiline escape character.
75    #[inline]
76    fn is_multi_line_escaped(characters: &[char]) -> bool {
77        characters[characters.len() - 1] == '\n' // Been checked before. No need to recheck.
78
79            // No need to check for \z, turns out, it can be excluded. It only
80            // affects how the string is displayed when, say, printed, but in code,
81            // they're both the same.
82            || (Self::is_escaped(characters) /* && characters[characters.len() - 1] == 'z' */)
83    }
84
85    /// Parses one of the single line variants:
86    ///
87    /// * [`LuauString::SingleQuotes`]
88    /// * [`LuauString::DoubleQuotes`]
89    /// * [`LuauString::Backticks`]
90    fn parse_inner(lexer: &mut Lexer, quote_character: char) -> SmolStr {
91        let mut characters = vec![quote_character];
92        let start = lexer.lexer_position;
93        let mut is_done = false;
94
95        lexer.increment_position_by_char(quote_character);
96
97        while let Some(character) = lexer.current_char() {
98            if (character == '\n' || character == '\r') && !Self::is_multi_line_escaped(&characters)
99            {
100                lexer.errors.push(ParseError::new(
101                    start,
102                    format!(
103                        "Strings must be single line, use `\\z` or `\\` here or add a {}.",
104                        quote_character
105                    ),
106                    Some(lexer.lexer_position),
107                ));
108
109                break;
110            }
111
112            characters.push(character);
113            lexer.increment_position_by_char(character);
114
115            if character == quote_character && !Self::is_escaped(&characters) {
116                is_done = true;
117
118                break;
119            }
120        }
121
122        if !is_done {
123            lexer.errors.push(ParseError::new(
124                start,
125                format!("Missing {} to close string.", quote_character),
126                Some(lexer.lexer_position),
127            ));
128        }
129
130        SmolStr::from_iter(characters)
131    }
132
133    /// Parses [`LuauString::MultiLine`].
134    pub(crate) fn parse_multi_line(lexer: &mut Lexer) -> SmolStr {
135        let mut characters = vec!['['];
136        let start = lexer.lexer_position;
137        let mut equals_count = 0;
138        let mut is_done = false;
139
140        lexer.consume('[');
141        while lexer.consume('=') {
142            equals_count += 1;
143            characters.push('=');
144        }
145
146        if lexer.consume('[') {
147            characters.push('[');
148        } else {
149            lexer.errors.push(ParseError::new(
150                start,
151                "Missing `[`.".to_string(),
152                Some(lexer.lexer_position),
153            ));
154        }
155
156        while let Some(character) = lexer.current_char() {
157            characters.push(character);
158            lexer.increment_position_by_char(character);
159
160            if character == ']' && !Self::is_escaped(&characters) {
161                let mut matched_equals = true;
162
163                for _ in 0..equals_count {
164                    if let Some(character) = lexer.current_char() {
165                        characters.push(character);
166                        lexer.increment_position_by_char(character);
167
168                        matched_equals = character == '=';
169                    }
170                }
171
172                if matched_equals && lexer.consume(']') {
173                    characters.push(']');
174                    is_done = true;
175
176                    break;
177                }
178            }
179        }
180
181        if !is_done {
182            lexer.errors.push(ParseError::new(
183                start,
184                "Malformed multi-line string.".to_string(),
185                Some(lexer.lexer_position),
186            ));
187        }
188
189        SmolStr::from_iter(characters)
190    }
191}
192
193impl Lexable for LuauString {
194    fn try_lex(lexer: &mut Lexer) -> Option<Self> {
195        match lexer.current_char()? {
196            '"' => Some(Self::DoubleQuotes(Self::parse_inner(lexer, '"'))),
197            '\'' => Some(Self::SingleQuotes(Self::parse_inner(lexer, '\''))),
198            '`' => Some(Self::Backticks(Self::parse_inner(lexer, '`'))),
199            '[' => Some(Self::MultiLine(Self::parse_multi_line(lexer))),
200            _ => unreachable!("Invalid quote type."),
201        }
202    }
203}
204
205/// A luau number. The stored string will include the `0b`, or `0x`. The only
206/// reason the different types actually exist is to allow the user to easily
207/// know which one is used without needing to check the actual string.
208#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
209#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
210pub enum LuauNumber {
211    ///```luau
212    /// 1
213    /// 1.1
214    /// .1
215    /// ```
216    Plain(SmolStr),
217
218    ///```luau
219    /// 0b111001101
220    /// ```
221    Binary(SmolStr),
222
223    ///```luau
224    /// 0xAB02C
225    /// ```
226    Hex(SmolStr),
227}
228
229impl LuauNumber {
230    /// Parses a [`LuauNumber::Plain`].
231    fn parse_number_inner(lexer: &mut Lexer) -> Option<Self> {
232        let start = lexer.position;
233        let mut found_decimal = false;
234
235        loop {
236            let Some(current_char) = lexer.current_char() else {
237                break;
238            };
239
240            if is_numeric(current_char) {
241                lexer.increment_position_by_char(current_char);
242            } else if current_char == '.' {
243                if found_decimal {
244                    break lexer.errors.push(ParseError::new(
245                        lexer.lexer_position,
246                        "Numbers can only have one decimal point.".to_string(),
247                        None,
248                    ));
249                }
250
251                lexer.increment_position_by_char(current_char);
252                found_decimal = true;
253            } else {
254                break;
255            }
256        }
257
258        Some(Self::Plain(SmolStr::from_iter(
259            lexer.chars[start..lexer.position].to_vec(),
260        )))
261    }
262
263    /// Parses a [`LuauNumber::Hex`].
264    fn parse_hex_number(lexer: &mut Lexer) -> Option<Self> {
265        let start = lexer.position;
266        let mut found_digit = false;
267        let mut is_faulty = false;
268
269        lexer.consume('0');
270        lexer.consume('x');
271
272        loop {
273            let Some(current_char) = lexer.current_char() else {
274                break;
275            };
276
277            if current_char.is_ascii_hexdigit() {
278                lexer.increment_position_by_char(current_char);
279                found_digit = true;
280            } else {
281                break is_faulty = !current_char.is_whitespace();
282            }
283        }
284
285        // ? Do we exit or return the faulty number?
286        if !found_digit {
287            lexer.errors.push(ParseError::new(
288                lexer.lexer_position,
289                "Hexadecimal numbers must have at least one digit after '0x'.".to_string(),
290                None,
291            ));
292        }
293        if found_digit && is_faulty {
294            lexer.errors.push(ParseError::new(
295                lexer.lexer_position,
296                "Hexadecimal numbers must only contain hexadecimal digits.".to_string(),
297                None,
298            ));
299        }
300
301        Some(Self::Hex(SmolStr::from_iter(
302            lexer.chars[start..lexer.position].to_vec(),
303        )))
304    }
305
306    /// Parses a [`LuauNumber::Binary`].
307    fn parse_binary_number(lexer: &mut Lexer) -> Option<Self> {
308        let start = lexer.position;
309        let mut found_digit = false;
310        let mut is_faulty = false;
311
312        lexer.consume('0');
313        lexer.consume('b');
314
315        loop {
316            let Some(current_char) = lexer.current_char() else {
317                break;
318            };
319
320            if current_char == '0' || current_char == '1' {
321                lexer.increment_position_by_char(current_char);
322                found_digit = true;
323            } else {
324                break is_faulty = !current_char.is_whitespace();
325            }
326        }
327
328        // ? Do we exit or return the faulty number?
329        if !found_digit {
330            lexer.errors.push(ParseError::new(
331                lexer.lexer_position,
332                "Binary number must have at least one digit after '0b'.".to_string(),
333                None,
334            ));
335        }
336        if found_digit && is_faulty {
337            lexer.errors.push(ParseError::new(
338                lexer.lexer_position,
339                "Binary number must only have 1s and 0s.".to_string(),
340                None,
341            ));
342        }
343
344        Some(Self::Binary(SmolStr::from_iter(
345            lexer.chars[start..lexer.position].to_vec(),
346        )))
347    }
348}
349
350impl Lexable for LuauNumber {
351    fn try_lex(lexer: &mut Lexer) -> Option<Self> {
352        match (lexer.current_char()?, lexer.next_char()) {
353            ('0', Some('b')) => Self::parse_binary_number(lexer),
354            ('0', Some('x')) => Self::parse_hex_number(lexer),
355            _ => Self::parse_number_inner(lexer),
356        }
357    }
358}
359
360/// A Luau literal value
361#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
362#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
363pub enum Literal {
364    /// A numeric value
365    Number(LuauNumber),
366
367    /// A string
368    String(LuauString),
369
370    /// A boolean
371    Boolean(bool),
372}
373
374impl Literal {
375    /// Parses a [`Literal::Number`].
376    #[inline]
377    pub fn parse_number(lexer: &mut Lexer) -> Option<Self> {
378        LuauNumber::try_lex(lexer).map(Self::Number)
379    }
380
381    /// Parses a [`Literal::String`].
382    #[inline]
383    pub fn parse_string(lexer: &mut Lexer) -> Option<Self> {
384        LuauString::try_lex(lexer).map(Self::String)
385    }
386}
387
388impl Lexable for Literal {
389    /// This just marks literals as lexable, refrain from using it. Use
390    /// [`Literal::parse_number`], or [`Literal::parse_string`], or the more
391    /// specific [`LuauString::try_lex`], and [`LuauNumber::try_lex`] instead.
392    fn try_lex(_: &mut Lexer) -> Option<Self> {
393        panic!(
394            "\
395            `Literal::try_lex()` should never be used. \
396            Please read the documentation for this function."
397        )
398    }
399}
400
401impl_from!(Literal <= {
402    Number(LuauNumber),
403    String(LuauString),
404    Boolean(bool),
405});