1use smol_str::SmolStr;
4
5use crate::{
6 prelude::{Lexable, Lexer, Error},
7 utils::is_numeric,
8};
9
10#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
15#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16pub enum LuauString {
17 SingleQuotes(SmolStr),
21
22 DoubleQuotes(SmolStr),
26
27 Backticks(SmolStr),
31
32 MultiLine(SmolStr),
42}
43
44impl LuauString {
45 fn count_back_slashes(characters: &[char]) -> usize {
47 if characters.is_empty() {
48 return 0;
49 }
50
51 let mut count = 0;
52 let mut i = characters.len() - 1;
53
54 while characters[i] == '\\' {
55 count += 1;
56
57 match i.checked_sub(1) {
58 Some(new_i) => i = new_i,
59 None => break,
60 }
61 }
62
63 count
64 }
65
66 fn is_escaped(characters: &[char]) -> bool {
68 if characters.len() < 2 {
69 false
70 } else {
71 Self::count_back_slashes(&characters[..characters.len() - 1]) % 2 != 0
72 }
73 }
74
75 #[inline]
77 fn is_multi_line_escaped(characters: &[char]) -> bool {
78 characters[characters.len() - 1] == '\n' || (Self::is_escaped(characters) )
84 }
85
86 fn parse_inner(lexer: &mut Lexer, quote_character: char) -> SmolStr {
92 let mut characters = vec![quote_character];
93 let start = lexer.lexer_position;
94 let mut is_done = false;
95
96 lexer.increment_position_by_char(quote_character);
97
98 while let Some(character) = lexer.current_char() {
99 if (character == '\n' || character == '\r') && !Self::is_multi_line_escaped(&characters)
100 {
101 lexer.errors.push(Error::new(
102 start,
103 format!(
104 "Strings must be single line, use `\\z` or `\\` here or add a {}.",
105 quote_character
106 ),
107 Some(lexer.lexer_position),
108 ));
109
110 break;
111 }
112
113 characters.push(character);
114 lexer.increment_position_by_char(character);
115
116 if character == quote_character && !Self::is_escaped(&characters) {
117 is_done = true;
118
119 break;
120 }
121 }
122
123 if !is_done {
124 lexer.errors.push(Error::new(
125 start,
126 format!("Missing {} to close string.", quote_character),
127 Some(lexer.lexer_position),
128 ));
129 }
130
131 SmolStr::from_iter(characters)
132 }
133
134 pub(crate) fn parse_multi_line(lexer: &mut Lexer) -> SmolStr {
136 let mut characters = vec!['['];
137 let start = lexer.lexer_position;
138 let mut equals_count = 0;
139 let mut is_done = false;
140
141 lexer.consume('[');
142 while lexer.consume('=') {
143 equals_count += 1;
144 characters.push('=');
145 }
146
147 if lexer.consume('[') {
148 characters.push('[');
149 } else {
150 lexer.errors.push(Error::new(
151 start,
152 "Missing `[`.".to_string(),
153 Some(lexer.lexer_position),
154 ));
155 }
156
157 while let Some(character) = lexer.current_char() {
158 characters.push(character);
159 lexer.increment_position_by_char(character);
160
161 if character == ']' && !Self::is_escaped(&characters) {
162 let mut matched_equals = true;
163
164 for _ in 0..equals_count {
165 if let Some(character) = lexer.current_char() {
166 characters.push(character);
167 lexer.increment_position_by_char(character);
168
169 matched_equals = character == '=';
170 }
171 }
172
173 if matched_equals && lexer.consume(']') {
174 characters.push(']');
175 is_done = true;
176
177 break;
178 }
179 }
180 }
181
182 if !is_done {
183 lexer.errors.push(Error::new(
184 start,
185 "Malformed multi-line string.".to_string(),
186 Some(lexer.lexer_position),
187 ));
188 }
189
190 SmolStr::from_iter(characters)
191 }
192}
193
194impl Lexable for LuauString {
195 fn try_lex(lexer: &mut Lexer) -> Option<Self> {
196 match lexer.current_char()? {
197 '"' => Some(Self::DoubleQuotes(Self::parse_inner(lexer, '"'))),
198 '\'' => Some(Self::SingleQuotes(Self::parse_inner(lexer, '\''))),
199 '`' => Some(Self::Backticks(Self::parse_inner(lexer, '`'))),
200 '[' => Some(Self::MultiLine(Self::parse_multi_line(lexer))),
201 _ => unreachable!("Invalid quote type."),
202 }
203 }
204}
205
206#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
212#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
213pub enum LuauNumber {
214 Plain(SmolStr),
220
221 Binary(SmolStr),
225
226 Hex(SmolStr),
230}
231
232impl LuauNumber {
233 fn parse_number_inner(lexer: &mut Lexer) -> Option<Self> {
235 let start = lexer.position;
236 let mut found_decimal = false;
237
238 loop {
239 let Some(current_char) = lexer.current_char() else {
240 break;
241 };
242
243 if is_numeric(current_char) {
244 lexer.increment_position_by_char(current_char);
245 } else if current_char == '.' {
246 if found_decimal {
247 break lexer.errors.push(Error::new(
248 lexer.lexer_position,
249 "Numbers can only have one decimal point.".to_string(),
250 None,
251 ));
252 }
253
254 lexer.increment_position_by_char(current_char);
255 found_decimal = true;
256 } else {
257 break;
258 }
259 }
260
261 Some(Self::Plain(SmolStr::from_iter(
262 lexer.chars[start..lexer.position].to_vec(),
263 )))
264 }
265
266 fn parse_hex_number(lexer: &mut Lexer) -> Option<Self> {
268 let start = lexer.position;
269 let mut found_digit = false;
270 let mut is_faulty = false;
271
272 lexer.consume('0');
273 lexer.consume('x');
274
275 loop {
276 let Some(current_char) = lexer.current_char() else {
277 break;
278 };
279
280 if current_char.is_ascii_hexdigit() {
281 lexer.increment_position_by_char(current_char);
282 found_digit = true;
283 } else {
284 break is_faulty = !current_char.is_whitespace();
285 }
286 }
287
288 if !found_digit {
290 lexer.errors.push(Error::new(
291 lexer.lexer_position,
292 "Hexadecimal numbers must have at least one digit after '0x'.".to_string(),
293 None,
294 ));
295 }
296 if found_digit && is_faulty {
297 lexer.errors.push(Error::new(
298 lexer.lexer_position,
299 "Hexadecimal numbers must only contain hexadecimal digits.".to_string(),
300 None,
301 ));
302 }
303
304 Some(Self::Hex(SmolStr::from_iter(
305 lexer.chars[start..lexer.position].to_vec(),
306 )))
307 }
308
309 fn parse_binary_number(lexer: &mut Lexer) -> Option<Self> {
311 let start = lexer.position;
312 let mut found_digit = false;
313 let mut is_faulty = false;
314
315 lexer.consume('0');
316 lexer.consume('b');
317
318 loop {
319 let Some(current_char) = lexer.current_char() else {
320 break;
321 };
322
323 if current_char == '0' || current_char == '1' {
324 lexer.increment_position_by_char(current_char);
325 found_digit = true;
326 } else {
327 break is_faulty = !current_char.is_whitespace();
328 }
329 }
330
331 if !found_digit {
333 lexer.errors.push(Error::new(
334 lexer.lexer_position,
335 "Binary number must have at least one digit after '0b'.".to_string(),
336 None,
337 ));
338 }
339 if found_digit && is_faulty {
340 lexer.errors.push(Error::new(
341 lexer.lexer_position,
342 "Binary number must only have 1s and 0s.".to_string(),
343 None,
344 ));
345 }
346
347 Some(Self::Binary(SmolStr::from_iter(
348 lexer.chars[start..lexer.position].to_vec(),
349 )))
350 }
351}
352
353impl Lexable for LuauNumber {
354 fn try_lex(lexer: &mut Lexer) -> Option<Self> {
355 match (lexer.current_char()?, lexer.next_char()) {
356 ('0', Some('b')) => Self::parse_binary_number(lexer),
357 ('0', Some('x')) => Self::parse_hex_number(lexer),
358 _ => Self::parse_number_inner(lexer),
359 }
360 }
361}
362
363#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
365#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
366pub enum Literal {
367 Number(LuauNumber),
369
370 String(LuauString),
372
373 Boolean(bool),
375}
376
377impl Literal {
378 #[inline]
380 pub fn parse_number(lexer: &mut Lexer) -> Option<Self> {
381 LuauNumber::try_lex(lexer).map(Self::Number)
382 }
383
384 #[inline]
386 pub fn parse_string(lexer: &mut Lexer) -> Option<Self> {
387 LuauString::try_lex(lexer).map(Self::String)
388 }
389}
390
391impl Lexable for Literal {
392 fn try_lex(_: &mut Lexer) -> Option<Self> {
396 panic!(
397 "\
398 `Literal::try_lex()` should never be used. \
399 Please read the documentation for this function."
400 )
401 }
402}
403
404impl_from!(Literal <= {
405 Number(LuauNumber),
406 String(LuauString),
407 Boolean(bool),
408});