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