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.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(SmolStr::from_iter(
255 lexer.chars[start..lexer.position].to_vec(),
256 )))
257 }
258
259 fn parse_hex_number(lexer: &mut Lexer) -> Option<Self> {
261 let start = lexer.position;
262 let mut found_digit = false;
263 let mut is_faulty = false;
264
265 lexer.consume('0');
266 lexer.consume('x');
267
268 loop {
269 let Some(current_char) = lexer.current_char() else {
270 break;
271 };
272
273 if current_char.is_ascii_hexdigit() {
274 lexer.increment_position_by_char(current_char);
275 found_digit = true;
276 } else {
277 break is_faulty = !current_char.is_whitespace();
278 }
279 }
280
281 if !found_digit {
283 lexer.errors.push(ParseError::new(
284 lexer.lexer_position,
285 "Hexadecimal numbers must have at least one digit after '0x'.".to_string(),
286 None,
287 ));
288 }
289 if found_digit && is_faulty {
290 lexer.errors.push(ParseError::new(
291 lexer.lexer_position,
292 "Hexadecimal numbers must only contain hexadecimal digits.".to_string(),
293 None,
294 ));
295 }
296
297 Some(Self::Hex(SmolStr::from_iter(
298 lexer.chars[start..lexer.position].to_vec(),
299 )))
300 }
301
302 fn parse_binary_number(lexer: &mut Lexer) -> Option<Self> {
304 let start = lexer.position;
305 let mut found_digit = false;
306 let mut is_faulty = false;
307
308 lexer.consume('0');
309 lexer.consume('b');
310
311 loop {
312 let Some(current_char) = lexer.current_char() else {
313 break;
314 };
315
316 if current_char == '0' || current_char == '1' {
317 lexer.increment_position_by_char(current_char);
318 found_digit = true;
319 } else {
320 break is_faulty = !current_char.is_whitespace();
321 }
322 }
323
324 if !found_digit {
326 lexer.errors.push(ParseError::new(
327 lexer.lexer_position,
328 "Binary number must have at least one digit after '0b'.".to_string(),
329 None,
330 ));
331 }
332 if found_digit && is_faulty {
333 lexer.errors.push(ParseError::new(
334 lexer.lexer_position,
335 "Binary number must only have 1s and 0s.".to_string(),
336 None,
337 ));
338 }
339
340 Some(Self::Binary(SmolStr::from_iter(
341 lexer.chars[start..lexer.position].to_vec(),
342 )))
343 }
344}
345
346impl Lexable for LuauNumber {
347 fn try_lex(lexer: &mut Lexer) -> Option<Self> {
348 match (lexer.current_char()?, lexer.next_char()) {
349 ('0', Some('b')) => Self::parse_binary_number(lexer),
350 ('0', Some('x')) => Self::parse_hex_number(lexer),
351 _ => Self::parse_number_inner(lexer),
352 }
353 }
354}
355
356#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
358#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
359pub enum Literal {
360 Number(LuauNumber),
362
363 String(LuauString),
365
366 Boolean(bool),
368}
369
370impl Literal {
371 #[inline]
373 pub fn parse_number(lexer: &mut Lexer) -> Option<Self> {
374 LuauNumber::try_lex(lexer).map(Self::Number)
375 }
376
377 #[inline]
379 pub fn parse_string(lexer: &mut Lexer) -> Option<Self> {
380 LuauString::try_lex(lexer).map(Self::String)
381 }
382}
383
384impl Lexable for Literal {
385 fn try_lex(_: &mut Lexer) -> Option<Self> {
389 panic!(
390 "\
391 `Literal::try_lex()` should never be used. \
392 Please read the documentation for this function."
393 )
394 }
395}
396
397impl_from!(Literal <= {
398 Number(LuauNumber),
399 String(LuauString),
400 Boolean(bool),
401});