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 is_escaped(characters: &[char]) -> bool {
46 let length = characters.len();
47 if length == 0 {
48 return false;
49 }
50
51 let has_backslash = characters[length - 2] == '\\';
52 if length == 1 {
53 has_backslash
54 } else {
55 has_backslash && characters[length - 3] != '\\'
56 }
57 }
58
59 fn is_multi_line_escaped(characters: &[char]) -> bool {
61 let mut iter = characters.iter().rev().skip_while(|c| c.is_whitespace()); let Some(&last) = iter.next() else {
64 return false;
65 };
66 let Some(&second_last) = iter.next() else {
67 return false;
68 };
69
70 second_last == '\\' && last == 'z'
71 }
72
73 fn parse_inner(lexer: &mut Lexer, quote_character: char) -> SmolStr {
79 let mut characters = vec![quote_character];
80 let start = lexer.lexer_position;
81 let mut is_done = false;
82
83 lexer.increment_position_by_char(quote_character);
84
85 while let Some(character) = lexer.current_char() {
86 if character == '\n' && !Self::is_multi_line_escaped(&characters) {
87 lexer.errors.push(ParseError::new(
88 start,
89 format!(
90 "SmolStr must be single line, use `\\z` here or add a {}.",
91 quote_character
92 ),
93 Some(lexer.lexer_position),
94 ));
95
96 break;
97 }
98
99 characters.push(character);
100 lexer.increment_position_by_char(character);
101
102 if character == quote_character && !Self::is_escaped(&characters) {
103 is_done = true;
104
105 break;
106 }
107 }
108
109 if !is_done {
110 lexer.errors.push(ParseError::new(
111 start,
112 format!("Missing {} to close string.", quote_character),
113 Some(lexer.lexer_position),
114 ));
115 }
116
117 characters.iter().collect::<String>().into()
118 }
119
120 pub(crate) fn parse_multi_line(lexer: &mut Lexer) -> SmolStr {
122 let mut characters = vec!['['];
123 let start = lexer.lexer_position;
124 let mut equals_count = 0;
125 let mut is_done = false;
126
127 lexer.consume('[');
128 while lexer.consume('=') {
129 equals_count += 1;
130 characters.push('=');
131 }
132
133 if lexer.consume('[') {
134 characters.push('[');
135 } else {
136 lexer.errors.push(ParseError::new(
137 start,
138 "Missing `[`.".to_string(),
139 Some(lexer.lexer_position),
140 ));
141 }
142
143 while let Some(character) = lexer.current_char() {
144 characters.push(character);
145 lexer.increment_position_by_char(character);
146
147 if character == ']' && !Self::is_escaped(&characters) {
148 let mut matched_equals = true;
149
150 for _ in 0..equals_count {
151 if let Some(character) = lexer.current_char() {
152 characters.push(character);
153 lexer.increment_position_by_char(character);
154
155 matched_equals = character == '=';
156 }
157 }
158
159 if matched_equals && lexer.consume(']') {
160 characters.push(']');
161 is_done = true;
162
163 break;
164 }
165 }
166 }
167
168 if !is_done {
169 lexer.errors.push(ParseError::new(
170 start,
171 "Malformed multi-line string.".to_string(),
172 Some(lexer.lexer_position),
173 ));
174 }
175
176 characters.iter().collect::<String>().into()
177 }
178}
179
180impl Lexable for LuauString {
181 fn try_lex(lexer: &mut Lexer) -> Option<Self> {
182 match lexer.current_char()? {
183 '"' => Some(Self::DoubleQuotes(Self::parse_inner(lexer, '"'))),
184 '\'' => Some(Self::SingleQuotes(Self::parse_inner(lexer, '\''))),
185 '`' => Some(Self::Backticks(Self::parse_inner(lexer, '`'))),
186 '[' => Some(Self::MultiLine(Self::parse_multi_line(lexer))),
187 _ => unreachable!("Invalid quote type."),
188 }
189 }
190}
191
192#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
196#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
197pub enum LuauNumber {
198 Plain(SmolStr),
204
205 Binary(SmolStr),
209
210 Hex(SmolStr),
214}
215
216impl LuauNumber {
217 fn parse_number_inner(lexer: &mut Lexer) -> Option<Self> {
219 let start = lexer.position;
220 let mut found_decimal = false;
221
222 loop {
223 let Some(current_char) = lexer.current_char() else {
224 break;
225 };
226
227 if is_numeric(current_char) {
228 lexer.increment_position_by_char(current_char);
229 } else if current_char == '.' {
230 if found_decimal {
231 break lexer.errors.push(ParseError::new(
232 lexer.lexer_position,
233 "Numbers can only have one decimal point.".to_string(),
234 None,
235 ));
236 }
237
238 lexer.increment_position_by_char(current_char);
239 found_decimal = true;
240 } else {
241 break;
242 }
243 }
244
245 Some(Self::Plain(lexer.input[start..lexer.position].into()))
246 }
247
248 fn parse_hex_number(lexer: &mut Lexer) -> Option<Self> {
250 let start = lexer.position;
251 let mut found_digit = false;
252 let mut is_faulty = false;
253
254 lexer.consume('0');
255 lexer.consume('x');
256
257 loop {
258 let Some(current_char) = lexer.current_char() else {
259 break;
260 };
261
262 if current_char.is_ascii_hexdigit() {
263 lexer.increment_position_by_char(current_char);
264 found_digit = true;
265 } else {
266 break is_faulty = !current_char.is_whitespace();
267 }
268 }
269
270 if !found_digit {
272 lexer.errors.push(ParseError::new(
273 lexer.lexer_position,
274 "Hexadecimal numbers must have at least one digit after '0x'.".to_string(),
275 None,
276 ));
277 }
278 if found_digit && is_faulty {
279 lexer.errors.push(ParseError::new(
280 lexer.lexer_position,
281 "Hexadecimal numbers must only contain hexadecimal digits.".to_string(),
282 None,
283 ));
284 }
285
286 Some(Self::Hex(lexer.input[start..lexer.position].into()))
287 }
288
289 fn parse_binary_number(lexer: &mut Lexer) -> Option<Self> {
291 let start = lexer.position;
292 let mut found_digit = false;
293 let mut is_faulty = false;
294
295 lexer.consume('0');
296 lexer.consume('b');
297
298 loop {
299 let Some(current_char) = lexer.current_char() else {
300 break;
301 };
302
303 if current_char == '0' || current_char == '1' {
304 lexer.increment_position_by_char(current_char);
305 found_digit = true;
306 } else {
307 break is_faulty = !current_char.is_whitespace();
308 }
309 }
310
311 if !found_digit {
313 lexer.errors.push(ParseError::new(
314 lexer.lexer_position,
315 "Binary number must have at least one digit after '0b'.".to_string(),
316 None,
317 ));
318 }
319 if found_digit && is_faulty {
320 lexer.errors.push(ParseError::new(
321 lexer.lexer_position,
322 "Binary number must only have 1s and 0s.".to_string(),
323 None,
324 ));
325 }
326
327 Some(Self::Binary(lexer.input[start..lexer.position].into()))
328 }
329}
330
331impl Lexable for LuauNumber {
332 fn try_lex(lexer: &mut Lexer) -> Option<Self> {
333 match (lexer.current_char()?, lexer.next_char()) {
334 ('0', Some('b')) => Self::parse_binary_number(lexer),
335 ('0', Some('x')) => Self::parse_hex_number(lexer),
336 _ => Self::parse_number_inner(lexer),
337 }
338 }
339}
340
341#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
343#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
344pub enum Literal {
345 Number(LuauNumber),
347
348 String(LuauString),
350
351 Boolean(bool),
353}
354
355impl Literal {
356 #[inline]
358 pub fn parse_number(lexer: &mut Lexer) -> Option<Self> {
359 LuauNumber::try_lex(lexer).map(Self::Number)
360 }
361
362 #[inline]
364 pub fn parse_string(lexer: &mut Lexer) -> Option<Self> {
365 LuauString::try_lex(lexer).map(Self::String)
366 }
367}
368
369impl Lexable for Literal {
370 fn try_lex(_: &mut Lexer) -> Option<Self> {
374 panic!(
375 "\
376 `Literal::try_lex()` should never be used. \
377 Please read the documentation for this function."
378 )
379 }
380}
381
382impl_from!(Literal <= {
383 Number(LuauNumber),
384 String(LuauString),
385 Boolean(bool),
386});