1use smol_str::SmolStr;
4
5use crate::{
6 prelude::{Lexable, Lexer, ParseError},
7 utils::is_numeric,
8};
9
10#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
14#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
15pub enum LuauString {
16 SingleQuotes(SmolStr),
20
21 DoubleQuotes(SmolStr),
25
26 Backticks(SmolStr),
30
31 MultiLine(SmolStr),
41}
42
43impl LuauString {
44 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 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 #[inline]
76 fn is_multi_line_escaped(characters: &[char]) -> bool {
77 characters[characters.len() - 1] == '\n' || (Self::is_escaped(characters) && characters[characters.len() - 1] == 'z')
79 }
80
81 fn parse_inner(lexer: &mut Lexer, quote_character: char) -> SmolStr {
87 let mut characters = vec![quote_character];
88 let start = lexer.lexer_position;
89 let mut is_done = false;
90
91 lexer.increment_position_by_char(quote_character);
92
93 while let Some(character) = lexer.current_char() {
94 if (character == '\n' || character == '\r') && !Self::is_multi_line_escaped(&characters)
95 {
96 lexer.errors.push(ParseError::new(
97 start,
98 format!(
99 "Strings must be single line, use `\\z` here or add a {}.",
100 quote_character
101 ),
102 Some(lexer.lexer_position),
103 ));
104
105 break;
106 }
107
108 characters.push(character);
109 lexer.increment_position_by_char(character);
110
111 if character == quote_character && !Self::is_escaped(&characters) {
112 is_done = true;
113
114 break;
115 }
116 }
117
118 if !is_done {
119 lexer.errors.push(ParseError::new(
120 start,
121 format!("Missing {} to close string.", quote_character),
122 Some(lexer.lexer_position),
123 ));
124 }
125
126 characters.iter().collect::<String>().into()
127 }
128
129 pub(crate) fn parse_multi_line(lexer: &mut Lexer) -> SmolStr {
131 let mut characters = vec!['['];
132 let start = lexer.lexer_position;
133 let mut equals_count = 0;
134 let mut is_done = false;
135
136 lexer.consume('[');
137 while lexer.consume('=') {
138 equals_count += 1;
139 characters.push('=');
140 }
141
142 if lexer.consume('[') {
143 characters.push('[');
144 } else {
145 lexer.errors.push(ParseError::new(
146 start,
147 "Missing `[`.".to_string(),
148 Some(lexer.lexer_position),
149 ));
150 }
151
152 while let Some(character) = lexer.current_char() {
153 characters.push(character);
154 lexer.increment_position_by_char(character);
155
156 if character == ']' && !Self::is_escaped(&characters) {
157 let mut matched_equals = true;
158
159 for _ in 0..equals_count {
160 if let Some(character) = lexer.current_char() {
161 characters.push(character);
162 lexer.increment_position_by_char(character);
163
164 matched_equals = character == '=';
165 }
166 }
167
168 if matched_equals && lexer.consume(']') {
169 characters.push(']');
170 is_done = true;
171
172 break;
173 }
174 }
175 }
176
177 if !is_done {
178 lexer.errors.push(ParseError::new(
179 start,
180 "Malformed multi-line string.".to_string(),
181 Some(lexer.lexer_position),
182 ));
183 }
184
185 characters.iter().collect::<String>().into()
186 }
187}
188
189impl Lexable for LuauString {
190 fn try_lex(lexer: &mut Lexer) -> Option<Self> {
191 match lexer.current_char()? {
192 '"' => Some(Self::DoubleQuotes(Self::parse_inner(lexer, '"'))),
193 '\'' => Some(Self::SingleQuotes(Self::parse_inner(lexer, '\''))),
194 '`' => Some(Self::Backticks(Self::parse_inner(lexer, '`'))),
195 '[' => Some(Self::MultiLine(Self::parse_multi_line(lexer))),
196 _ => unreachable!("Invalid quote type."),
197 }
198 }
199}
200
201#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
205#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
206pub enum LuauNumber {
207 Plain(SmolStr),
213
214 Binary(SmolStr),
218
219 Hex(SmolStr),
223}
224
225impl LuauNumber {
226 fn parse_number_inner(lexer: &mut Lexer) -> Option<Self> {
228 let start = lexer.byte_position;
229 let mut found_decimal = false;
230
231 loop {
232 let Some(current_char) = lexer.current_char() else {
233 break;
234 };
235
236 if is_numeric(current_char) {
237 lexer.increment_position_by_char(current_char);
238 } else if current_char == '.' {
239 if found_decimal {
240 break lexer.errors.push(ParseError::new(
241 lexer.lexer_position,
242 "Numbers can only have one decimal point.".to_string(),
243 None,
244 ));
245 }
246
247 lexer.increment_position_by_char(current_char);
248 found_decimal = true;
249 } else {
250 break;
251 }
252 }
253
254 Some(Self::Plain(lexer.input[start..lexer.byte_position].into()))
255 }
256
257 fn parse_hex_number(lexer: &mut Lexer) -> Option<Self> {
259 let start = lexer.byte_position;
260 let mut found_digit = false;
261 let mut is_faulty = false;
262
263 lexer.consume('0');
264 lexer.consume('x');
265
266 loop {
267 let Some(current_char) = lexer.current_char() else {
268 break;
269 };
270
271 if current_char.is_ascii_hexdigit() {
272 lexer.increment_position_by_char(current_char);
273 found_digit = true;
274 } else {
275 break is_faulty = !current_char.is_whitespace();
276 }
277 }
278
279 if !found_digit {
281 lexer.errors.push(ParseError::new(
282 lexer.lexer_position,
283 "Hexadecimal numbers must have at least one digit after '0x'.".to_string(),
284 None,
285 ));
286 }
287 if found_digit && is_faulty {
288 lexer.errors.push(ParseError::new(
289 lexer.lexer_position,
290 "Hexadecimal numbers must only contain hexadecimal digits.".to_string(),
291 None,
292 ));
293 }
294
295 Some(Self::Hex(lexer.input[start..lexer.byte_position].into()))
296 }
297
298 fn parse_binary_number(lexer: &mut Lexer) -> Option<Self> {
300 let start = lexer.byte_position;
301 let mut found_digit = false;
302 let mut is_faulty = false;
303
304 lexer.consume('0');
305 lexer.consume('b');
306
307 loop {
308 let Some(current_char) = lexer.current_char() else {
309 break;
310 };
311
312 if current_char == '0' || current_char == '1' {
313 lexer.increment_position_by_char(current_char);
314 found_digit = true;
315 } else {
316 break is_faulty = !current_char.is_whitespace();
317 }
318 }
319
320 if !found_digit {
322 lexer.errors.push(ParseError::new(
323 lexer.lexer_position,
324 "Binary number must have at least one digit after '0b'.".to_string(),
325 None,
326 ));
327 }
328 if found_digit && is_faulty {
329 lexer.errors.push(ParseError::new(
330 lexer.lexer_position,
331 "Binary number must only have 1s and 0s.".to_string(),
332 None,
333 ));
334 }
335
336 Some(Self::Binary(lexer.input[start..lexer.byte_position].into()))
337 }
338}
339
340impl Lexable for LuauNumber {
341 fn try_lex(lexer: &mut Lexer) -> Option<Self> {
342 match (lexer.current_char()?, lexer.next_char()) {
343 ('0', Some('b')) => Self::parse_binary_number(lexer),
344 ('0', Some('x')) => Self::parse_hex_number(lexer),
345 _ => Self::parse_number_inner(lexer),
346 }
347 }
348}
349
350#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
352#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
353pub enum Literal {
354 Number(LuauNumber),
356
357 String(LuauString),
359
360 Boolean(bool),
362}
363
364impl Literal {
365 #[inline]
367 pub fn parse_number(lexer: &mut Lexer) -> Option<Self> {
368 LuauNumber::try_lex(lexer).map(Self::Number)
369 }
370
371 #[inline]
373 pub fn parse_string(lexer: &mut Lexer) -> Option<Self> {
374 LuauString::try_lex(lexer).map(Self::String)
375 }
376}
377
378impl Lexable for Literal {
379 fn try_lex(_: &mut Lexer) -> Option<Self> {
383 panic!(
384 "\
385 `Literal::try_lex()` should never be used. \
386 Please read the documentation for this function."
387 )
388 }
389}
390
391impl_from!(Literal <= {
392 Number(LuauNumber),
393 String(LuauString),
394 Boolean(bool),
395});