1use mago_allocator::prelude::*;
2
3use crate::input::Input;
4use crate::number_separator;
5
6pub fn parse_literal_string_in<'arena, A>(
17 arena: &'arena A,
18 s: &'arena [u8],
19 quote_char: Option<u8>,
20 has_quote: bool,
21) -> Option<&'arena [u8]>
22where
23 A: Arena,
24{
25 if s.is_empty() {
26 return Some(b"");
27 }
28
29 let s = if has_quote
30 && (s.starts_with(b"b\"") || s.starts_with(b"b'") || s.starts_with(b"B\"") || s.starts_with(b"B'"))
31 {
32 &s[1..]
33 } else {
34 s
35 };
36
37 let (quote_char, content) = if let Some(quote_char) = quote_char {
38 (Some(quote_char), s)
39 } else if !has_quote {
40 (None, s)
41 } else if s.starts_with(b"\"") && s.ends_with(b"\"") && s.len() >= 2 {
42 (Some(b'"'), &s[1..s.len() - 1])
43 } else if s.starts_with(b"'") && s.ends_with(b"'") && s.len() >= 2 {
44 (Some(b'\''), &s[1..s.len() - 1])
45 } else {
46 return None;
47 };
48
49 let needs_processing = content.contains(&b'\\') || quote_char.is_some_and(|q| content.contains(&q));
50 if !needs_processing {
51 return Some(content);
52 }
53
54 let mut result = Vec::with_capacity_in(content.len(), arena);
55 let mut i = 0;
56
57 while i < content.len() {
58 let b = content[i];
59 if b != b'\\' {
60 result.push(b);
61 i += 1;
62 continue;
63 }
64
65 let next_index = i + 1;
66 let Some(&next) = content.get(next_index) else {
67 result.push(b'\\');
68 i += 1;
69 continue;
70 };
71
72 let mut consumed = 2;
75
76 match next {
77 b'\\' => result.push(b'\\'),
78 b'\'' if quote_char == Some(b'\'') => result.push(b'\''),
79 b'"' if quote_char == Some(b'"') => result.push(b'"'),
80 b'$' if quote_char == Some(b'"') => result.push(b'$'),
81 b'n' if quote_char == Some(b'"') => result.push(b'\n'),
82 b't' if quote_char == Some(b'"') => result.push(b'\t'),
83 b'r' if quote_char == Some(b'"') => result.push(b'\r'),
84 b'v' if quote_char == Some(b'"') => result.push(0x0B),
85 b'e' if quote_char == Some(b'"') => result.push(0x1B),
86 b'f' if quote_char == Some(b'"') => result.push(0x0C),
87 b'x' if quote_char == Some(b'"') => {
88 let mut hex_val = 0u8;
89 let mut hex_len = 0;
90 let mut j = i + 2;
91 while hex_len < 2 && j < content.len() {
92 let c = content[j];
93 let digit = if c.is_ascii_digit() {
94 c - b'0'
95 } else if (b'a'..=b'f').contains(&c) {
96 c - b'a' + 10
97 } else if (b'A'..=b'F').contains(&c) {
98 c - b'A' + 10
99 } else {
100 break;
101 };
102 hex_val = hex_val * 16 + digit;
103 hex_len += 1;
104 j += 1;
105 }
106 if hex_len > 0 {
107 result.push(hex_val);
108 consumed = 2 + hex_len;
109 } else {
110 result.push(b'\\');
112 result.push(b'x');
113 }
114 }
115 b'u' if quote_char == Some(b'"') && content.get(i + 2) == Some(&b'{') => {
116 let mut code_point = 0u32;
117 let mut hex_len = 0;
118 let mut overflowed = false;
119 let mut j = i + 3;
120 while j < content.len() {
121 let c = content[j];
122 let digit = if c.is_ascii_digit() {
123 c - b'0'
124 } else if (b'a'..=b'f').contains(&c) {
125 c - b'a' + 10
126 } else if (b'A'..=b'F').contains(&c) {
127 c - b'A' + 10
128 } else {
129 break;
130 };
131
132 match code_point.checked_mul(16).and_then(|value| value.checked_add(u32::from(digit))) {
133 Some(value) => code_point = value,
134 None => {
135 overflowed = true;
136 break;
137 }
138 }
139
140 hex_len += 1;
141 j += 1;
142 }
143
144 if hex_len > 0 && !overflowed && content.get(j) == Some(&b'}') && code_point <= 0x10_FFFF {
145 match code_point {
146 0x0000..=0x007F => result.push(code_point as u8),
147 0x0080..=0x07FF => {
148 result.push(0xC0 | (code_point >> 6) as u8);
149 result.push(0x80 | (code_point & 0x3F) as u8);
150 }
151 0x0800..=0xFFFF => {
152 result.push(0xE0 | (code_point >> 12) as u8);
153 result.push(0x80 | ((code_point >> 6) & 0x3F) as u8);
154 result.push(0x80 | (code_point & 0x3F) as u8);
155 }
156 _ => {
157 result.push(0xF0 | (code_point >> 18) as u8);
158 result.push(0x80 | ((code_point >> 12) & 0x3F) as u8);
159 result.push(0x80 | ((code_point >> 6) & 0x3F) as u8);
160 result.push(0x80 | (code_point & 0x3F) as u8);
161 }
162 }
163
164 consumed = (j + 1) - i;
165 } else {
166 return None;
168 }
169 }
170 c if quote_char == Some(b'"') && c.is_ascii_digit() => {
171 let mut octal_val = 0u16;
172 let mut octal_len = 0;
173 let mut j = i + 1;
174 while octal_len < 3 && j < content.len() {
175 let d = content[j];
176 if d.is_ascii_digit() && d <= b'7' {
177 octal_val = octal_val * 8 + u16::from(d - b'0');
178 octal_len += 1;
179 j += 1;
180 } else {
181 break;
182 }
183 }
184 if octal_len > 0 {
185 result.push(octal_val as u8);
187 consumed = 1 + octal_len;
188 } else {
189 result.push(b'\\');
190 result.push(next);
191 }
192 }
193 _ => {
194 result.push(b'\\');
196 result.push(next);
197 }
198 }
199
200 i += consumed;
201 }
202
203 Some(result.leak())
204}
205
206#[inline]
208#[must_use]
209pub fn parse_literal_float(value: &[u8]) -> Option<f64> {
210 if memchr::memchr(b'_', value).is_none() {
211 return std::str::from_utf8(value).ok()?.parse::<f64>().ok();
212 }
213
214 let mut buf = [0u8; 64];
215 let mut len = 0;
216
217 for &b in value {
218 if b != b'_' {
219 if len < 64 {
220 buf[len] = b;
221 len += 1;
222 } else {
223 let source: std::vec::Vec<u8> = value.iter().copied().filter(|&b| b != b'_').collect();
224 return std::str::from_utf8(&source).ok()?.parse::<f64>().ok();
225 }
226 }
227 }
228
229 std::str::from_utf8(&buf[..len]).ok()?.parse::<f64>().ok()
230}
231
232#[inline]
236#[must_use]
237pub fn parse_literal_integer(bytes: &[u8]) -> Option<u64> {
238 if bytes.is_empty() {
239 return None;
240 }
241
242 let (radix, start) = match bytes {
243 [b'0', b'x' | b'X', ..] => (16u128, 2),
244 [b'0', b'o' | b'O', ..] => (8u128, 2),
245 [b'0', b'b' | b'B', ..] => (2u128, 2),
246 [b'0', _, ..] if bytes[1..].iter().all(|&b| b == b'_' || (b'0'..=b'7').contains(&b)) => (8u128, 1), [b'0', _, ..] => (10u128, 0), _ => (10u128, 0),
249 };
250
251 let mut result: u128 = 0;
252 let mut has_digits = false;
253
254 for &b in &bytes[start..] {
255 if b == b'_' {
256 continue;
257 }
258
259 let digit = if b.is_ascii_digit() {
260 (b - b'0') as u128
261 } else if (b'a'..=b'f').contains(&b) {
262 (b - b'a' + 10) as u128
263 } else if (b'A'..=b'F').contains(&b) {
264 (b - b'A' + 10) as u128
265 } else {
266 return None;
267 };
268
269 if digit >= radix {
270 return None;
271 }
272
273 has_digits = true;
274
275 result = match result.checked_mul(radix) {
276 Some(r) => r,
277 None => return Some(u64::MAX),
278 };
279
280 result = match result.checked_add(digit) {
281 Some(r) => r,
282 None => return Some(u64::MAX),
283 };
284 }
285
286 if !has_digits {
287 return None;
288 }
289
290 Some(result.min(u64::MAX as u128) as u64)
291}
292
293#[inline]
300#[must_use]
301pub fn parse_literal_integer_as_float(bytes: &[u8]) -> Option<f64> {
302 if bytes.is_empty() {
303 return None;
304 }
305
306 let (radix, start) = match bytes {
307 [b'0', b'x' | b'X', ..] => (16u128, 2),
308 [b'0', b'o' | b'O', ..] => (8u128, 2),
309 [b'0', b'b' | b'B', ..] => (2u128, 2),
310 [b'0', _, ..] if bytes[1..].iter().all(|&b| b == b'_' || (b'0'..=b'7').contains(&b)) => (8u128, 1),
311 _ => (10u128, 0),
312 };
313
314 if radix == 10 {
315 return parse_literal_float(bytes);
316 }
317
318 let mut result: u128 = 0;
319 let mut has_digits = false;
320
321 for &b in &bytes[start..] {
322 if b == b'_' {
323 continue;
324 }
325
326 let digit = if b.is_ascii_digit() {
327 (b - b'0') as u128
328 } else if (b'a'..=b'f').contains(&b) {
329 (b - b'a' + 10) as u128
330 } else if (b'A'..=b'F').contains(&b) {
331 (b - b'A' + 10) as u128
332 } else {
333 return None;
334 };
335
336 if digit >= radix {
337 return None;
338 }
339
340 has_digits = true;
341 result = match result.checked_mul(radix).and_then(|value| value.checked_add(digit)) {
342 Some(value) => value,
343 None => return Some(f64::INFINITY),
344 };
345 }
346
347 if !has_digits {
348 return None;
349 }
350
351 Some(result as f64)
352}
353
354static IS_IDENT_START: [bool; 256] = {
357 let mut table = [false; 256];
358 let mut i = 0u8;
359 loop {
360 table[i as usize] = matches!(i, b'a'..=b'z' | b'A'..=b'Z' | b'_');
361 if i == 255 {
362 break;
363 }
364 i += 1;
365 }
366
367 table
368};
369
370static IS_IDENT_PART: [bool; 256] = {
373 let mut table = [false; 256];
374 let mut i = 0u8;
375 loop {
376 table[i as usize] = matches!(i, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | 0x80..=0xFF);
377 if i == 255 {
378 break;
379 }
380 i += 1;
381 }
382 table
383};
384
385#[inline(always)]
387#[must_use]
388pub const fn is_start_of_identifier(byte: &u8) -> bool {
389 IS_IDENT_START[*byte as usize]
390}
391
392#[inline(always)]
394#[must_use]
395pub const fn is_part_of_identifier(byte: &u8) -> bool {
396 IS_IDENT_PART[*byte as usize]
397}
398
399#[inline(always)]
405#[must_use]
406pub fn scan_identifier_length(bytes: &[u8], offset: usize) -> usize {
407 let mut len = 1;
408 let remaining = &bytes[offset + 1..];
409
410 for &b in remaining {
411 if IS_IDENT_PART[b as usize] {
412 len += 1;
413 } else {
414 break;
415 }
416 }
417
418 len
419}
420
421#[inline]
446pub fn read_digits_of_base(input: &Input, offset: usize, base: u8) -> usize {
447 if base == 16 {
448 read_digits_with(input, offset, u8::is_ascii_hexdigit)
449 } else {
450 let max = b'0' + base;
451
452 read_digits_with(input, offset, |b| b >= &b'0' && b < &max)
453 }
454}
455
456#[inline]
457fn read_digits_with<F>(input: &Input, offset: usize, is_digit: F) -> usize
458where
459 F: Fn(&u8) -> bool,
460{
461 let bytes = input.bytes;
462 let total = input.length;
463 let start = input.offset;
464 let mut pos = start + offset; while pos < total {
467 let current = bytes[pos];
468 if is_digit(¤t) {
469 pos += 1;
470 } else if pos + 1 < total && bytes[pos] == number_separator!() && is_digit(&bytes[pos + 1]) {
471 pos += 2; } else {
473 break;
474 }
475 }
476
477 pos - start
479}
480
481#[cfg(test)]
482mod tests {
483 use mago_allocator::LocalArena;
484
485 use super::*;
486
487 macro_rules! parse_int {
488 ($input:expr, $expected:expr) => {
489 assert_eq!(parse_literal_integer($input), $expected);
490 };
491 }
492
493 #[test]
494 fn test_unicode_escape_in_double_quoted() {
495 let arena = LocalArena::new();
496
497 assert_eq!(parse_literal_string_in(&arena, b"A\\u{1F600}", Some(b'"'), false), Some(&b"A\xF0\x9F\x98\x80"[..]),);
498 assert_eq!(parse_literal_string_in(&arena, b"\\u{41}", Some(b'"'), false), Some(&b"A"[..]));
499 assert_eq!(parse_literal_string_in(&arena, b"\\u{E9}", Some(b'"'), false), Some(&b"\xC3\xA9"[..]));
500 assert_eq!(parse_literal_string_in(&arena, b"\\u{D800}", Some(b'"'), false), Some(&b"\xED\xA0\x80"[..]));
501 assert_eq!(parse_literal_string_in(&arena, b"\\u{}", Some(b'"'), false), None);
502 assert_eq!(parse_literal_string_in(&arena, b"\\u{12", Some(b'"'), false), None);
503 assert_eq!(parse_literal_string_in(&arena, b"\\u{ZZ}", Some(b'"'), false), None);
504 assert_eq!(parse_literal_string_in(&arena, b"\\u{110000}", Some(b'"'), false), None);
505 assert_eq!(parse_literal_string_in(&arena, b"\\uABC", Some(b'"'), false), Some(&b"\\uABC"[..]));
506 assert_eq!(parse_literal_string_in(&arena, b"\\u{41}", Some(b'\''), false), Some(&b"\\u{41}"[..]));
507 assert_eq!(parse_literal_string_in(&arena, b"\\x", Some(b'"'), false), Some(&b"\\x"[..]));
508 assert_eq!(parse_literal_string_in(&arena, b"\\q", Some(b'"'), false), Some(&b"\\q"[..]));
509 }
510
511 #[test]
512 fn test_parse_literal_integer() {
513 parse_int!(b"123", Some(123));
514 parse_int!(b"0", Some(0));
515 parse_int!(b"0b1010", Some(10));
516 parse_int!(b"0o17", Some(15));
517 parse_int!(b"0x1A3F", Some(6719));
518 parse_int!(b"0XFF", Some(255));
519 parse_int!(b"0_1_2_3", Some(83));
520 parse_int!(b"0b1_0_1_0", Some(10));
521 parse_int!(b"0o1_7", Some(15));
522 parse_int!(b"0x1_A_3_F", Some(6719));
523 parse_int!(b"", None);
524 parse_int!(b"0xGHI", None);
525 parse_int!(b"0b102", None);
526 parse_int!(b"0o89", None);
527 }
528
529 #[test]
530 fn test_parse_literal_integer_as_float() {
531 assert_eq!(parse_literal_integer_as_float(b"0"), Some(0.0));
532 assert_eq!(parse_literal_integer_as_float(b"255"), Some(255.0));
533 assert_eq!(parse_literal_integer_as_float(b"0xff"), Some(255.0));
534 assert_eq!(parse_literal_integer_as_float(b"0o17"), Some(15.0));
535 assert_eq!(parse_literal_integer_as_float(b"017"), Some(15.0));
536 assert_eq!(parse_literal_integer_as_float(b"0b101"), Some(5.0));
537 assert_eq!(parse_literal_integer_as_float(b"1_000"), Some(1000.0));
538 assert_eq!(parse_literal_integer_as_float(b"111111111111111111111"), Some(1.111_111_111_111_111_1e20));
539 assert_eq!(parse_literal_integer_as_float(b"0xFFFFFFFFFFFFFFFF0"), Some(2.951_479_051_793_528_3e20));
540 assert_eq!(parse_literal_integer_as_float(&[b'9'; 400]), Some(f64::INFINITY));
541 assert_eq!(parse_literal_integer_as_float(b""), None);
542 assert_eq!(parse_literal_integer_as_float(b"0xGHI"), None);
543 assert_eq!(parse_literal_integer_as_float(b"0b102"), None);
544 }
545}