1#![allow(clippy::unreachable)]
2
3use core::hint::unreachable_unchecked;
4
5use std::collections::VecDeque;
6use std::fmt::Debug;
7
8use memchr::memchr2;
9use memchr::memmem;
10
11const SIMPLE_TOKEN_TABLE: [Option<TokenKind>; 256] = {
15 let mut table: [Option<TokenKind>; 256] = [None; 256];
16 table[b';' as usize] = Some(TokenKind::Semicolon);
17 table[b',' as usize] = Some(TokenKind::Comma);
18 table[b')' as usize] = Some(TokenKind::RightParenthesis);
19 table[b'[' as usize] = Some(TokenKind::LeftBracket);
20 table[b']' as usize] = Some(TokenKind::RightBracket);
21 table[b'{' as usize] = Some(TokenKind::LeftBrace);
22 table[b'}' as usize] = Some(TokenKind::RightBrace);
23 table[b'~' as usize] = Some(TokenKind::Tilde);
24 table[b'@' as usize] = Some(TokenKind::At);
25 table
26};
27
28const IDENT_START_TABLE: [bool; 256] = {
30 let mut table = [false; 256];
31 let mut i = 0usize;
32 while i < 256 {
33 table[i] = matches!(i as u8, b'a'..=b'z' | b'A'..=b'Z' | b'_' | 0x80..=0xFF);
34 i += 1;
35 }
36
37 table
38};
39
40use mago_database::file::FileId;
41use mago_database::file::HasFileId;
42use mago_span::Position;
43use mago_syntax_core::float_exponent;
44use mago_syntax_core::float_separator;
45use mago_syntax_core::input::Input;
46use mago_syntax_core::number_sign;
47use mago_syntax_core::start_of_binary_number;
48use mago_syntax_core::start_of_float_number;
49use mago_syntax_core::start_of_hexadecimal_number;
50use mago_syntax_core::start_of_identifier;
51use mago_syntax_core::start_of_number;
52use mago_syntax_core::start_of_octal_number;
53use mago_syntax_core::start_of_octal_or_float_number;
54use mago_syntax_core::utils::is_part_of_identifier;
55use mago_syntax_core::utils::is_start_of_identifier;
56use mago_syntax_core::utils::read_digits_of_base;
57
58use crate::error::SyntaxError;
59use crate::lexer::internal::mode::HaltStage;
60use crate::lexer::internal::mode::Interpolation;
61use crate::lexer::internal::mode::LexerMode;
62use crate::lexer::internal::utils::NumberKind;
63use crate::settings::LexerSettings;
64use crate::token::DocumentKind;
65use crate::token::Token;
66use crate::token::TokenKind;
67
68mod internal;
69
70#[derive(Debug)]
82pub struct Lexer<'input> {
83 input: Input<'input>,
84 settings: LexerSettings,
85 mode: LexerMode<'input>,
86 interpolating: bool,
87 brace_interpolating: bool,
88 var_offset_depth: u32,
89 expect_string_varname: bool,
90 buffer: VecDeque<Token<'input>>,
92}
93
94impl<'input> Lexer<'input> {
95 const BUFFER_INITIAL_CAPACITY: usize = 8;
98
99 #[must_use]
110 pub fn new(input: Input<'input>, settings: LexerSettings) -> Lexer<'input> {
111 Lexer {
112 input,
113 settings,
114 mode: LexerMode::Inline,
115 interpolating: false,
116 brace_interpolating: false,
117 var_offset_depth: 0,
118 expect_string_varname: false,
119 buffer: VecDeque::with_capacity(Self::BUFFER_INITIAL_CAPACITY),
120 }
121 }
122
123 #[must_use]
134 pub fn scripting(input: Input<'input>, settings: LexerSettings) -> Lexer<'input> {
135 Lexer {
136 input,
137 settings,
138 mode: LexerMode::Script,
139 interpolating: false,
140 brace_interpolating: false,
141 var_offset_depth: 0,
142 expect_string_varname: false,
143 buffer: VecDeque::with_capacity(Self::BUFFER_INITIAL_CAPACITY),
144 }
145 }
146
147 #[must_use]
151 pub fn has_reached_eof(&self) -> bool {
152 self.input.has_reached_eof()
153 }
154
155 #[inline]
157 #[must_use]
158 pub const fn current_position(&self) -> Position {
159 self.input.current_position()
160 }
161
162 #[inline]
195 pub fn advance(&mut self) -> Option<Result<Token<'input>, SyntaxError>> {
196 if !self.interpolating
198 && let Some(token) = self.buffer.pop_front()
199 {
200 return Some(Ok(token));
201 }
202
203 if self.input.has_reached_eof() {
204 return None;
205 }
206
207 match self.mode {
208 LexerMode::Inline => {
209 let start = self.input.current_position();
210 let offset = self.input.current_offset();
211
212 if offset == 0
214 && self.input.len() >= 2
215 && unsafe { *self.input.read_at_unchecked(0) } == b'#'
217 && unsafe { *self.input.read_at_unchecked(1) } == b'!'
219 {
220 let buffer = self.input.consume_through(b'\n');
221 let end = self.input.current_position();
222
223 return Some(Ok(self.token(TokenKind::InlineShebang, buffer, start, end)));
224 }
225
226 let bytes = self.input.read_remaining();
228
229 if self.settings.enable_short_tags {
230 if let Some(pos) = memchr::memmem::find(bytes, b"<?") {
231 if pos > 0 {
232 let buffer = self.input.consume(pos);
233 let end = self.input.current_position();
234
235 return Some(Ok(self.token(TokenKind::InlineText, buffer, start, end)));
236 }
237
238 if self.input.is_at(b"<?php", true) {
239 let buffer = self.input.consume(5);
240 self.mode = LexerMode::Script;
241 return Some(Ok(self.token(
242 TokenKind::OpenTag,
243 buffer,
244 start,
245 self.input.current_position(),
246 )));
247 }
248
249 if self.input.is_at(b"<?=", false) {
250 let buffer = self.input.consume(3);
251 self.mode = LexerMode::Script;
252 return Some(Ok(self.token(
253 TokenKind::EchoTag,
254 buffer,
255 start,
256 self.input.current_position(),
257 )));
258 }
259
260 let buffer = self.input.consume(2);
261 self.mode = LexerMode::Script;
262 return Some(Ok(self.token(
263 TokenKind::ShortOpenTag,
264 buffer,
265 start,
266 self.input.current_position(),
267 )));
268 }
269 } else {
270 let iter = memchr::memmem::find_iter(bytes, b"<?");
271
272 for pos in iter {
273 let candidate = unsafe { bytes.get_unchecked(pos..) };
275
276 if candidate.len() >= 5
277 && (unsafe { *candidate.get_unchecked(2) } | 0x20) == b'p'
279 && (unsafe { *candidate.get_unchecked(3) } | 0x20) == b'h'
281 && (unsafe { *candidate.get_unchecked(4) } | 0x20) == b'p'
283 {
284 if pos > 0 {
285 let buffer = self.input.consume(pos);
286 let end = self.input.current_position();
287 return Some(Ok(self.token(TokenKind::InlineText, buffer, start, end)));
288 }
289
290 let buffer = self.input.consume(5);
291 self.mode = LexerMode::Script;
292 return Some(Ok(self.token(
293 TokenKind::OpenTag,
294 buffer,
295 start,
296 self.input.current_position(),
297 )));
298 }
299
300 if candidate.len() >= 3 && unsafe { *candidate.get_unchecked(2) } == b'=' {
303 if pos > 0 {
304 let buffer = self.input.consume(pos);
305 let end = self.input.current_position();
306 return Some(Ok(self.token(TokenKind::InlineText, buffer, start, end)));
307 }
308
309 let buffer = self.input.consume(3);
310 self.mode = LexerMode::Script;
311 return Some(Ok(self.token(
312 TokenKind::EchoTag,
313 buffer,
314 start,
315 self.input.current_position(),
316 )));
317 }
318 }
319 }
320
321 if self.input.has_reached_eof() {
322 return None;
323 }
324
325 let buffer = self.input.consume_remaining();
326 let end = self.input.current_position();
327 Some(Ok(self.token(TokenKind::InlineText, buffer, start, end)))
328 }
329 LexerMode::Script => {
330 let start = self.input.current_position();
331
332 if self.expect_string_varname {
333 self.expect_string_varname = false;
334 let (length, _) = self.input.scan_identifier(0);
335 let buffer = self.input.consume(length);
336 let end = self.input.current_position();
337
338 return Some(Ok(self.token(TokenKind::StringVariableName, buffer, start, end)));
339 }
340
341 let whitespaces = self.input.consume_whitespaces();
342 if !whitespaces.is_empty() {
343 return Some(Ok(self.token(
344 TokenKind::Whitespace,
345 whitespaces,
346 start,
347 self.input.current_position(),
348 )));
349 }
350
351 let Some(&first_byte) = self.input.read(1).first() else {
352 unsafe { unreachable_unchecked() }
354 };
355
356 if let Some(kind) = SIMPLE_TOKEN_TABLE[first_byte as usize] {
357 let buffer = self.input.consume(1);
358 let end = self.input.current_position();
359 return Some(Ok(self.token(kind, buffer, start, end)));
360 }
361
362 if self.var_offset_depth > 0 && IDENT_START_TABLE[first_byte as usize] {
363 let (length, _) = self.input.scan_identifier(0);
364 let buffer = self.input.consume(length);
365 let end = self.input.current_position();
366 return Some(Ok(self.token(TokenKind::OffsetString, buffer, start, end)));
367 }
368
369 if IDENT_START_TABLE[first_byte as usize] {
370 let is_binary_string_prefix = !self.interpolating
371 && matches!(first_byte, b'b' | b'B')
372 && matches!(self.input.read(4), [_, b'\'' | b'"', ..] | [_, b'<', b'<', b'<']);
373
374 if !is_binary_string_prefix {
375 let (token_kind, len) = self.scan_identifier_or_keyword_info();
376
377 if token_kind == TokenKind::HaltCompiler {
378 self.mode = LexerMode::Halt(HaltStage::LookingForLeftParenthesis);
379 }
380
381 let buffer = self.input.consume(len);
382 let end = self.input.current_position();
383 return Some(Ok(self.token(token_kind, buffer, start, end)));
384 }
385
386 }
388
389 if first_byte == b'$'
390 && let Some(&next) = self.input.read(2).get(1)
391 && IDENT_START_TABLE[next as usize]
392 {
393 let (ident_len, _) = self.input.scan_identifier(1);
394 let buffer = self.input.consume(1 + ident_len);
395 let end = self.input.current_position();
396 return Some(Ok(self.token(TokenKind::Variable, buffer, start, end)));
397 }
398
399 let mut document_label: &[u8] = &[];
400
401 let (token_kind, len) = match self.input.read(3) {
402 [b'!', b'=', b'='] => (TokenKind::BangEqualEqual, 3),
403 [b'?', b'?', b'='] => (TokenKind::QuestionQuestionEqual, 3),
404 [b'?', b'-', b'>'] => (TokenKind::QuestionMinusGreaterThan, 3),
405 [b'=', b'=', b'='] => (TokenKind::EqualEqualEqual, 3),
406 [b'.', b'.', b'.'] => (TokenKind::DotDotDot, 3),
407 [b'<', b'=', b'>'] => (TokenKind::LessThanEqualGreaterThan, 3),
408 [b'<', b'<', b'='] => (TokenKind::LeftShiftEqual, 3),
409 [b'>', b'>', b'='] => (TokenKind::RightShiftEqual, 3),
410 [b'*', b'*', b'='] => (TokenKind::AsteriskAsteriskEqual, 3),
411 [b'<', b'<', b'<'] if matches_start_of_heredoc_document(&self.input, 0) => {
412 let (length, whitespaces, label_length) = read_start_of_heredoc_document(&self.input, false, 0);
413
414 document_label = self.input.peek(3 + whitespaces, label_length);
415
416 (TokenKind::DocumentStart(DocumentKind::Heredoc), length)
417 }
418 [b'<', b'<', b'<'] if matches_start_of_double_quote_heredoc_document(&self.input, 0) => {
419 let (length, whitespaces, label_length) = read_start_of_heredoc_document(&self.input, true, 0);
420
421 document_label = self.input.peek(4 + whitespaces, label_length);
422
423 (TokenKind::DocumentStart(DocumentKind::Heredoc), length)
424 }
425 [b'<', b'<', b'<'] if matches_start_of_nowdoc_document(&self.input, 0) => {
426 let (length, whitespaces, label_length) = read_start_of_nowdoc_document(&self.input, 0);
427
428 document_label = self.input.peek(4 + whitespaces, label_length);
429
430 (TokenKind::DocumentStart(DocumentKind::Nowdoc), length)
431 }
432 [b'!', b'=', ..] => (TokenKind::BangEqual, 2),
433 [b'&', b'&', ..] => (TokenKind::AmpersandAmpersand, 2),
434 [b'&', b'=', ..] => (TokenKind::AmpersandEqual, 2),
435 [b'.', b'=', ..] => (TokenKind::DotEqual, 2),
436 [b'?', b'?', ..] => (TokenKind::QuestionQuestion, 2),
437 [b'?', b'>', ..] => (TokenKind::CloseTag, 2),
438 [b'=', b'>', ..] => (TokenKind::EqualGreaterThan, 2),
439 [b'=', b'=', ..] => (TokenKind::EqualEqual, 2),
440 [b'+', b'+', ..] => (TokenKind::PlusPlus, 2),
441 [b'+', b'=', ..] => (TokenKind::PlusEqual, 2),
442 [b'%', b'=', ..] => (TokenKind::PercentEqual, 2),
443 [b'-', b'-', ..] => (TokenKind::MinusMinus, 2),
444 [b'-', b'>', ..] => (TokenKind::MinusGreaterThan, 2),
445 [b'-', b'=', ..] => (TokenKind::MinusEqual, 2),
446 [b'<', b'<', ..] => (TokenKind::LeftShift, 2),
447 [b'<', b'=', ..] => (TokenKind::LessThanEqual, 2),
448 [b'<', b'>', ..] => (TokenKind::LessThanGreaterThan, 2),
449 [b'>', b'>', ..] => (TokenKind::RightShift, 2),
450 [b'>', b'=', ..] => (TokenKind::GreaterThanEqual, 2),
451 [b':', b':', ..] => (TokenKind::ColonColon, 2),
452 [b'#', b'[', ..] => (TokenKind::HashLeftBracket, 2),
453 [b'|', b'=', ..] => (TokenKind::PipeEqual, 2),
454 [b'|', b'|', ..] => (TokenKind::PipePipe, 2),
455 [b'/', b'=', ..] => (TokenKind::SlashEqual, 2),
456 [b'^', b'=', ..] => (TokenKind::CaretEqual, 2),
457 [b'*', b'*', ..] => (TokenKind::AsteriskAsterisk, 2),
458 [b'*', b'=', ..] => (TokenKind::AsteriskEqual, 2),
459 [b'|', b'>', ..] => (TokenKind::PipeGreaterThan, 2),
460 [b'/', b'/', ..] => {
461 let remaining = self.input.peek(2, self.input.len() - self.input.current_offset());
462 let comment_len = scan_single_line_comment(remaining);
463 (TokenKind::SingleLineComment, 2 + comment_len)
464 }
465 [b'/', b'*', asterisk] => {
466 let remaining = self.input.peek(2, self.input.len() - self.input.current_offset());
467 match scan_multi_line_comment(remaining) {
468 Some(len) => {
469 let is_docblock = asterisk == &b'*' && len > 2;
470 if is_docblock {
471 (TokenKind::DocBlockComment, len + 2)
472 } else {
473 (TokenKind::MultiLineComment, len + 2)
474 }
475 }
476 None => {
477 self.input.consume(remaining.len() + 2);
478 return Some(Err(SyntaxError::UnexpectedEndOfFile(
479 self.file_id(),
480 self.input.current_position(),
481 )));
482 }
483 }
484 }
485 [b'\\', start_of_identifier!(), ..] => {
486 let mut length = 1;
487 loop {
488 let (ident_len, ends_with_ns) = self.input.scan_identifier(length);
489 length += ident_len;
490 if ends_with_ns {
491 length += 1; } else {
493 break;
494 }
495 }
496
497 (TokenKind::FullyQualifiedIdentifier, length)
498 }
499 [b'$', b'{', ..] => (TokenKind::DollarLeftBrace, 2),
500 [b'$', ..] => (TokenKind::Dollar, 1),
501 [b'!', ..] => (TokenKind::Bang, 1),
502 [b'&', ..] => (TokenKind::Ampersand, 1),
503 [b'?', ..] => (TokenKind::Question, 1),
504 [b'=', ..] => (TokenKind::Equal, 1),
505 [b'`', ..] => (TokenKind::Backtick, 1),
506 [b'+', ..] => (TokenKind::Plus, 1),
507 [b'%', ..] => (TokenKind::Percent, 1),
508 [b'-', ..] => (TokenKind::Minus, 1),
509 [b'<', ..] => (TokenKind::LessThan, 1),
510 [b'>', ..] => (TokenKind::GreaterThan, 1),
511 [b':', ..] => (TokenKind::Colon, 1),
512 [b'|', ..] => (TokenKind::Pipe, 1),
513 [b'^', ..] => (TokenKind::Caret, 1),
514 [b'*', ..] => (TokenKind::Asterisk, 1),
515 [b'/', ..] => (TokenKind::Slash, 1),
516 [b'b' | b'B', b'\'', ..] => read_literal_string(&self.input, b'\'', 1),
517 [b'b' | b'B', b'"', ..] if matches_literal_double_quote_string(&self.input, 1) => {
518 read_literal_string(&self.input, b'"', 1)
519 }
520 [b'b' | b'B', b'"', ..] => (TokenKind::DoubleQuote, 2),
521 [b'b' | b'B', b'<', b'<']
522 if self.input.read(4).len() == 4
523 && self.input.read(4)[3] == b'<'
524 && matches_start_of_heredoc_document(&self.input, 1) =>
525 {
526 let (length, whitespaces, label_length) = read_start_of_heredoc_document(&self.input, false, 1);
527
528 document_label = self.input.peek(4 + whitespaces, label_length);
529
530 (TokenKind::DocumentStart(DocumentKind::Heredoc), length)
531 }
532 [b'b' | b'B', b'<', b'<']
533 if self.input.read(4).len() == 4
534 && self.input.read(4)[3] == b'<'
535 && matches_start_of_double_quote_heredoc_document(&self.input, 1) =>
536 {
537 let (length, whitespaces, label_length) = read_start_of_heredoc_document(&self.input, true, 1);
538
539 document_label = self.input.peek(5 + whitespaces, label_length);
540
541 (TokenKind::DocumentStart(DocumentKind::Heredoc), length)
542 }
543 [b'b' | b'B', b'<', b'<']
544 if self.input.read(4).len() == 4
545 && self.input.read(4)[3] == b'<'
546 && matches_start_of_nowdoc_document(&self.input, 1) =>
547 {
548 let (length, whitespaces, label_length) = read_start_of_nowdoc_document(&self.input, 1);
549
550 document_label = self.input.peek(5 + whitespaces, label_length);
551
552 (TokenKind::DocumentStart(DocumentKind::Nowdoc), length)
553 }
554 [quote @ b'\'', ..] => read_literal_string(&self.input, *quote, 0),
556 [quote @ b'"', ..] if matches_literal_double_quote_string(&self.input, 0) => {
557 read_literal_string(&self.input, *quote, 0)
558 }
559 [b'"', ..] => (TokenKind::DoubleQuote, 1),
560 [b'(', ..] => 'parenthesis: {
561 let mut peek_offset = 1;
562 while let Some(&b) = self.input.read(peek_offset + 1).get(peek_offset) {
563 if b.is_ascii_whitespace() {
564 peek_offset += 1;
565 } else {
566 let lower = b | 0x20; if !matches!(lower, b'i' | b'b' | b'f' | b'd' | b'r' | b's' | b'a' | b'o' | b'u' | b'v')
569 {
570 break 'parenthesis (TokenKind::LeftParenthesis, 1);
571 }
572 break;
573 }
574 }
575
576 for (value, kind) in internal::consts::CAST_TYPES {
577 if let Some(length) = self.input.match_sequence_ignore_whitespace(value, true) {
578 break 'parenthesis (kind, length);
579 }
580 }
581
582 (TokenKind::LeftParenthesis, 1)
583 }
584 [b'#', ..] => {
585 let remaining = self.input.peek(1, self.input.len() - self.input.current_offset());
586 let comment_len = scan_single_line_comment(remaining);
587 (TokenKind::HashComment, 1 + comment_len)
588 }
589 [b'\\', ..] => (TokenKind::NamespaceSeparator, 1),
590 [start_of_number!(), ..] if self.var_offset_depth > 0 => {
591 let mut length = 1;
592 let base = match self.input.read(2) {
593 [b'0', b'x' | b'X'] => {
594 length = 2;
595 16
596 }
597 [b'0', b'b' | b'B'] => {
598 length = 2;
599 2
600 }
601 [b'0', b'o' | b'O'] => {
602 length = 2;
603 8
604 }
605 _ => 10,
606 };
607
608 (TokenKind::OffsetNumber, read_digits_of_base(&self.input, length, base))
609 }
610 [b'.', start_of_number!(), ..] if self.var_offset_depth == 0 => {
611 let mut length = read_digits_of_base(&self.input, 2, 10);
612 if let float_exponent!() = self.input.peek(length, 1) {
613 let mut exp_length = length + 1;
614 if let number_sign!() = self.input.peek(exp_length, 1) {
615 exp_length += 1;
616 }
617
618 let after_exp = read_digits_of_base(&self.input, exp_length, 10);
619 if after_exp > exp_length {
620 length = after_exp;
621 }
622 }
623
624 (TokenKind::LiteralFloat, length)
625 }
626 [start_of_number!(), ..] => 'number: {
627 let mut length = 1;
628
629 let (base, kind): (u8, NumberKind) = match self.input.read(3) {
630 start_of_binary_number!() => {
631 length += 1;
632
633 (2, NumberKind::Integer)
634 }
635 start_of_octal_number!() => {
636 length += 1;
637
638 (8, NumberKind::Integer)
639 }
640 start_of_hexadecimal_number!() => {
641 length += 1;
642
643 (16, NumberKind::Integer)
644 }
645 start_of_octal_or_float_number!() => (10, NumberKind::OctalOrFloat),
646 start_of_float_number!() => (10, NumberKind::Float),
647 _ => (10, NumberKind::IntegerOrFloat),
648 };
649
650 if kind != NumberKind::Float {
651 length = read_digits_of_base(&self.input, length, base);
652
653 if kind == NumberKind::Integer {
654 break 'number (TokenKind::LiteralInteger, length);
655 }
656 }
657
658 let is_float = matches!(self.input.peek(length, 3), float_separator!());
659
660 if !is_float {
661 if kind == NumberKind::OctalOrFloat
662 && let Some(invalid_idx) =
663 (1..length).find(|&i| matches!(self.input.peek(i, 1), [b'8' | b'9']))
664 {
665 let invalid_byte = self.input.peek(invalid_idx, 1)[0];
666 let start = self.input.current_position();
667 let invalid_position = Position { offset: start.offset + invalid_idx as u32 };
668 self.input.consume(length);
669 return Some(Err(SyntaxError::UnexpectedToken(
670 self.file_id(),
671 invalid_byte,
672 invalid_position,
673 )));
674 }
675 break 'number (TokenKind::LiteralInteger, length);
676 }
677
678 if let [b'.'] = self.input.peek(length, 1) {
679 length += 1;
680 length = read_digits_of_base(&self.input, length, 10);
681 }
682
683 if let float_exponent!() = self.input.peek(length, 1) {
684 let mut exp_length = length + 1;
686 if let number_sign!() = self.input.peek(exp_length, 1) {
687 exp_length += 1;
688 }
689 let after_exp = read_digits_of_base(&self.input, exp_length, 10);
690 if after_exp > exp_length {
691 length = after_exp;
693 }
694 }
695
696 (TokenKind::LiteralFloat, length)
697 }
698 [b'.', ..] => (TokenKind::Dot, 1),
699 [unknown_byte, ..] => {
700 let position = self.input.current_position();
701 self.input.consume(1);
702
703 return Some(Err(SyntaxError::UnrecognizedToken(self.file_id(), *unknown_byte, position)));
704 }
705 [] => {
706 return None;
710 }
711 };
712
713 self.mode = match token_kind {
714 TokenKind::DoubleQuote => LexerMode::DoubleQuoteString(Interpolation::None),
715 TokenKind::Backtick => LexerMode::ShellExecuteString(Interpolation::None),
716 TokenKind::CloseTag => LexerMode::Inline,
717 TokenKind::HaltCompiler => LexerMode::Halt(HaltStage::LookingForLeftParenthesis),
718 TokenKind::DocumentStart(document_kind) => {
719 let body_offset = self.input.current_offset() + len;
720 let interpolated = matches!(document_kind, DocumentKind::Heredoc);
721 let indent = read_document_indentation(&self.input, body_offset, document_label, interpolated);
722
723 LexerMode::DocumentString(document_kind, document_label, indent, Interpolation::None)
724 }
725 _ => LexerMode::Script,
726 };
727
728 let buffer = self.input.consume(len);
729 let end = self.input.current_position();
730
731 Some(Ok(self.token(token_kind, buffer, start, end)))
732 }
733 LexerMode::DoubleQuoteString(interpolation) => match &interpolation {
734 Interpolation::None => {
735 let start = self.input.current_position();
736
737 let mut length = 0;
738 let mut last_was_slash = false;
739 let mut token_kind = TokenKind::StringPart;
740 loop {
741 match self.input.peek(length, 2) {
742 [b'$', start_of_identifier!(), ..] if !last_was_slash => {
743 let until_offset = read_until_end_of_variable_interpolation(&self.input, length + 2);
744
745 self.mode =
746 LexerMode::DoubleQuoteString(Interpolation::Until(start.offset + until_offset));
747
748 break;
749 }
750 [b'{', b'$', ..] | [b'$', b'{', ..] if !last_was_slash => {
751 let until_offset = read_until_end_of_brace_interpolation(&self.input, length + 2);
752
753 self.mode = LexerMode::DoubleQuoteString(Interpolation::BraceUntil(
754 start.offset + until_offset,
755 ));
756
757 break;
758 }
759 [b'\\', ..] => {
760 length += 1;
761
762 last_was_slash = !last_was_slash;
763 }
764 [b'"', ..] if !last_was_slash => {
765 if length == 0 {
766 length += 1;
767 token_kind = TokenKind::DoubleQuote;
768
769 break;
770 }
771
772 break;
773 }
774 [_, ..] => {
775 length += 1;
776 last_was_slash = false;
777 }
778 [] => {
779 break;
780 }
781 }
782 }
783
784 let buffer = self.input.consume(length);
785 let end = self.input.current_position();
786
787 if TokenKind::DoubleQuote == token_kind {
788 self.mode = LexerMode::Script;
789 }
790
791 Some(Ok(self.token(token_kind, buffer, start, end)))
792 }
793 Interpolation::Until(offset) => {
794 self.interpolation(*offset, LexerMode::DoubleQuoteString(Interpolation::None), false)
795 }
796 Interpolation::BraceUntil(offset) => {
797 self.interpolation(*offset, LexerMode::DoubleQuoteString(Interpolation::None), true)
798 }
799 },
800 LexerMode::ShellExecuteString(interpolation) => match &interpolation {
801 Interpolation::None => {
802 let start = self.input.current_position();
803
804 let mut length = 0;
805 let mut last_was_slash = false;
806 let mut token_kind = TokenKind::StringPart;
807 loop {
808 match self.input.peek(length, 2) {
809 [b'$', start_of_identifier!(), ..] if !last_was_slash => {
810 let until_offset = read_until_end_of_variable_interpolation(&self.input, length + 2);
811
812 self.mode =
813 LexerMode::ShellExecuteString(Interpolation::Until(start.offset + until_offset));
814
815 break;
816 }
817 [b'{', b'$', ..] | [b'$', b'{', ..] if !last_was_slash => {
818 let until_offset = read_until_end_of_brace_interpolation(&self.input, length + 2);
819
820 self.mode = LexerMode::ShellExecuteString(Interpolation::BraceUntil(
821 start.offset + until_offset,
822 ));
823
824 break;
825 }
826 [b'\\', ..] => {
827 length += 1;
828 last_was_slash = !last_was_slash;
829 }
830 [b'`', ..] if !last_was_slash => {
831 if length == 0 {
832 length += 1;
833 token_kind = TokenKind::Backtick;
834
835 break;
836 }
837
838 break;
839 }
840 [_, ..] => {
841 length += 1;
842 last_was_slash = false;
843 }
844 [] => {
845 break;
846 }
847 }
848 }
849
850 let buffer = self.input.consume(length);
851 let end = self.input.current_position();
852
853 if TokenKind::Backtick == token_kind {
854 self.mode = LexerMode::Script;
855 }
856
857 Some(Ok(self.token(token_kind, buffer, start, end)))
858 }
859 Interpolation::Until(offset) => {
860 self.interpolation(*offset, LexerMode::ShellExecuteString(Interpolation::None), false)
861 }
862 Interpolation::BraceUntil(offset) => {
863 self.interpolation(*offset, LexerMode::ShellExecuteString(Interpolation::None), true)
864 }
865 },
866 LexerMode::DocumentString(kind, label, indent, interpolation) => match &kind {
867 DocumentKind::Heredoc => match &interpolation {
868 Interpolation::None => {
869 let start = self.input.current_position();
870
871 if indent > 0 && document_body_at_line_start(&self.input) {
872 let width = document_leading_indent_width(&self.input, indent);
873 if width > 0 && !document_line_is_closing_marker(&self.input, width, label) {
874 let buffer = self.input.consume(width);
875 let end = self.input.current_position();
876
877 return Some(Ok(self.token(TokenKind::Whitespace, buffer, start, end)));
878 }
879 }
880
881 let mut length = 0;
882 let mut last_was_slash = false;
883 let mut only_whitespaces = true;
884 let mut token_kind = TokenKind::StringPart;
885 loop {
886 match self.input.peek(length, 2) {
887 [b'\r', b'\n'] => {
888 if document_next_line_is_closing_marker(&self.input, length + 2, label) {
889 return self.document_segment_before_closing_marker(length, 2, start);
890 }
891
892 length += 2;
893
894 break;
895 }
896 [b'\n' | b'\r', ..] => {
897 if document_next_line_is_closing_marker(&self.input, length + 1, label) {
898 return self.document_segment_before_closing_marker(length, 1, start);
899 }
900
901 length += 1;
902
903 break;
904 }
905 [byte, ..] if byte.is_ascii_whitespace() => {
906 length += 1;
907 }
908 [b'$', start_of_identifier!(), ..] if !last_was_slash => {
909 let until_offset =
910 read_until_end_of_variable_interpolation(&self.input, length + 2);
911
912 self.mode = LexerMode::DocumentString(
913 kind,
914 label,
915 indent,
916 Interpolation::Until(start.offset + until_offset),
917 );
918
919 break;
920 }
921 [b'{', b'$', ..] | [b'$', b'{', ..] if !last_was_slash => {
922 let until_offset = read_until_end_of_brace_interpolation(&self.input, length + 2);
923
924 self.mode = LexerMode::DocumentString(
925 kind,
926 label,
927 indent,
928 Interpolation::BraceUntil(start.offset + until_offset),
929 );
930
931 break;
932 }
933 [b'\\', ..] => {
934 length += 1;
935 last_was_slash = !last_was_slash;
936 only_whitespaces = false;
937 }
938 [_, ..] => {
939 if only_whitespaces
940 && self.input.peek(length, label.len()) == label
941 && self
942 .input
943 .peek(length + label.len(), 1)
944 .first()
945 .is_none_or(|c| !is_part_of_identifier(c))
946 {
947 length += label.len();
948 token_kind = TokenKind::DocumentEnd;
949
950 break;
951 }
952
953 length += 1;
954 last_was_slash = false;
955 only_whitespaces = false;
956 }
957 [] => {
958 break;
959 }
960 }
961 }
962
963 let buffer = self.input.consume(length);
964 let end = self.input.current_position();
965
966 if TokenKind::DocumentEnd == token_kind {
967 self.mode = LexerMode::Script;
968 }
969
970 Some(Ok(self.token(token_kind, buffer, start, end)))
971 }
972 Interpolation::Until(offset) => self.interpolation(
973 *offset,
974 LexerMode::DocumentString(kind, label, indent, Interpolation::None),
975 false,
976 ),
977 Interpolation::BraceUntil(offset) => self.interpolation(
978 *offset,
979 LexerMode::DocumentString(kind, label, indent, Interpolation::None),
980 true,
981 ),
982 },
983 DocumentKind::Nowdoc => {
984 let start = self.input.current_position();
985
986 if indent > 0 && document_body_at_line_start(&self.input) {
987 let width = document_leading_indent_width(&self.input, indent);
988 if width > 0 && !document_line_is_closing_marker(&self.input, width, label) {
989 let buffer = self.input.consume(width);
990 let end = self.input.current_position();
991
992 return Some(Ok(self.token(TokenKind::Whitespace, buffer, start, end)));
993 }
994 }
995
996 let mut length = 0;
997 let mut terminated = false;
998 let mut only_whitespaces = true;
999
1000 loop {
1001 match self.input.peek(length, 2) {
1002 [b'\r', b'\n'] => {
1003 if document_next_line_is_closing_marker(&self.input, length + 2, label) {
1004 return self.document_segment_before_closing_marker(length, 2, start);
1005 }
1006
1007 length += 2;
1008
1009 break;
1010 }
1011 [b'\n' | b'\r', ..] => {
1012 if document_next_line_is_closing_marker(&self.input, length + 1, label) {
1013 return self.document_segment_before_closing_marker(length, 1, start);
1014 }
1015
1016 length += 1;
1017
1018 break;
1019 }
1020 [byte, ..] if byte.is_ascii_whitespace() => {
1021 length += 1;
1022 }
1023 [_, ..] => {
1024 if only_whitespaces
1025 && self.input.peek(length, label.len()) == label
1026 && self
1027 .input
1028 .peek(length + label.len(), 1)
1029 .first()
1030 .is_none_or(|c| !is_part_of_identifier(c))
1031 {
1032 length += label.len();
1033 terminated = true;
1034
1035 break;
1036 }
1037
1038 only_whitespaces = false;
1039 length += 1;
1040 }
1041 [] => {
1042 break;
1043 }
1044 }
1045 }
1046
1047 let buffer = self.input.consume(length);
1048 let end = self.input.current_position();
1049
1050 if terminated {
1051 self.mode = LexerMode::Script;
1052
1053 return Some(Ok(self.token(TokenKind::DocumentEnd, buffer, start, end)));
1054 }
1055
1056 Some(Ok(self.token(TokenKind::StringPart, buffer, start, end)))
1057 }
1058 },
1059 LexerMode::Halt(stage) => 'halt: {
1060 let start = self.input.current_position();
1061 if let HaltStage::End = stage {
1062 let buffer = self.input.consume_remaining();
1063 let end = self.input.current_position();
1064
1065 break 'halt Some(Ok(self.token(TokenKind::InlineText, buffer, start, end)));
1066 }
1067
1068 let whitespaces = self.input.consume_whitespaces();
1069 if !whitespaces.is_empty() {
1070 let end = self.input.current_position();
1071
1072 break 'halt Some(Ok(self.token(TokenKind::Whitespace, whitespaces, start, end)));
1073 }
1074
1075 match &stage {
1076 HaltStage::LookingForLeftParenthesis => {
1077 if self.input.is_at(b"(", false) {
1078 let buffer = self.input.consume(1);
1079 let end = self.input.current_position();
1080
1081 self.mode = LexerMode::Halt(HaltStage::LookingForRightParenthesis);
1082
1083 Some(Ok(self.token(TokenKind::LeftParenthesis, buffer, start, end)))
1084 } else {
1085 let byte = self.input.read(1)[0];
1086 let position = self.input.current_position();
1087 self.input.consume(1);
1089 Some(Err(SyntaxError::UnexpectedToken(self.file_id(), byte, position)))
1090 }
1091 }
1092 HaltStage::LookingForRightParenthesis => {
1093 if self.input.is_at(b")", false) {
1094 let buffer = self.input.consume(1);
1095 let end = self.input.current_position();
1096
1097 self.mode = LexerMode::Halt(HaltStage::LookingForTerminator);
1098
1099 Some(Ok(self.token(TokenKind::RightParenthesis, buffer, start, end)))
1100 } else {
1101 let byte = self.input.read(1)[0];
1102 let position = self.input.current_position();
1103 self.input.consume(1);
1104 Some(Err(SyntaxError::UnexpectedToken(self.file_id(), byte, position)))
1105 }
1106 }
1107 HaltStage::LookingForTerminator => {
1108 if self.input.is_at(b";", false) {
1109 let buffer = self.input.consume(1);
1110 let end = self.input.current_position();
1111
1112 self.mode = LexerMode::Halt(HaltStage::End);
1113
1114 Some(Ok(self.token(TokenKind::Semicolon, buffer, start, end)))
1115 } else if self.input.is_at(b"?>", false) {
1116 let buffer = self.input.consume(2);
1117 let end = self.input.current_position();
1118
1119 self.mode = LexerMode::Halt(HaltStage::End);
1120
1121 Some(Ok(self.token(TokenKind::CloseTag, buffer, start, end)))
1122 } else {
1123 let byte = self.input.read(1)[0];
1124 let position = self.input.current_position();
1125 self.input.consume(1);
1126 Some(Err(SyntaxError::UnexpectedToken(self.file_id(), byte, position)))
1127 }
1128 }
1129 HaltStage::End => {
1130 None
1134 }
1135 }
1136 }
1137 }
1138 }
1139
1140 #[inline]
1144 fn scan_identifier_or_keyword_info(&self) -> (TokenKind, usize) {
1145 let (mut length, ended_with_slash) = self.input.scan_identifier(0);
1146
1147 if !ended_with_slash {
1148 match length {
1149 6 if self.input.is_at(b"public(set)", true) => {
1150 return (TokenKind::PublicSet, 11);
1151 }
1152 7 if self.input.is_at(b"private(set)", true) => {
1153 return (TokenKind::PrivateSet, 12);
1154 }
1155 9 if self.input.is_at(b"protected(set)", true) => {
1156 return (TokenKind::ProtectedSet, 14);
1157 }
1158 _ => {}
1159 }
1160 }
1161
1162 if !ended_with_slash && let Some(kind) = internal::keyword::lookup_keyword(self.input.read(length)) {
1163 return (kind, length);
1164 }
1165
1166 let mut slashes = 0;
1167 let mut last_was_slash = false;
1168 loop {
1169 match self.input.peek(length, 1) {
1170 [b'a'..=b'z' | b'A'..=b'Z' | b'_' | 0x80..=0xFF] if last_was_slash => {
1171 length += 1;
1172 last_was_slash = false;
1173 }
1174 [b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | 0x80..=0xFF] if !last_was_slash => {
1175 length += 1;
1176 }
1177 [b'\\'] if !self.interpolating || self.brace_interpolating => {
1178 if last_was_slash {
1179 length -= 1;
1180 slashes -= 1;
1181 last_was_slash = false;
1182 break;
1183 }
1184
1185 length += 1;
1186 slashes += 1;
1187 last_was_slash = true;
1188 }
1189 _ => {
1190 break;
1191 }
1192 }
1193 }
1194
1195 if last_was_slash {
1196 length -= 1;
1197 slashes -= 1;
1198 }
1199
1200 let kind = if slashes > 0 { TokenKind::QualifiedIdentifier } else { TokenKind::Identifier };
1201
1202 (kind, length)
1203 }
1204
1205 #[inline]
1206 fn token(&self, kind: TokenKind, value: &'input [u8], start: Position, _end: Position) -> Token<'input> {
1207 Token { kind, start, value }
1208 }
1209
1210 fn document_segment_before_closing_marker(
1216 &mut self,
1217 content_length: usize,
1218 terminator_length: usize,
1219 start: Position,
1220 ) -> Option<Result<Token<'input>, SyntaxError>> {
1221 if content_length == 0 {
1222 let buffer = self.input.consume(terminator_length);
1223 let end = self.input.current_position();
1224
1225 return Some(Ok(self.token(TokenKind::Whitespace, buffer, start, end)));
1226 }
1227
1228 let content = self.input.consume(content_length);
1229 let content_end = self.input.current_position();
1230 let terminator = self.input.consume(terminator_length);
1231 let terminator_end = self.input.current_position();
1232
1233 if self.interpolating {
1234 self.buffer.push_back(self.token(TokenKind::StringPart, content, start, content_end));
1235
1236 return Some(Ok(self.token(TokenKind::Whitespace, terminator, content_end, terminator_end)));
1237 }
1238
1239 self.buffer.push_back(self.token(TokenKind::Whitespace, terminator, content_end, terminator_end));
1240
1241 Some(Ok(self.token(TokenKind::StringPart, content, start, content_end)))
1242 }
1243
1244 fn peek_string_varname_label(&self) -> bool {
1245 let Some(&first) = self.input.read(1).first() else {
1246 return false;
1247 };
1248
1249 if !IDENT_START_TABLE[first as usize] {
1250 return false;
1251 }
1252
1253 let (label_length, _) = self.input.scan_identifier(0);
1254
1255 matches!(self.input.read(label_length + 1).get(label_length), Some(b'[' | b'}'))
1256 }
1257
1258 #[inline]
1259 fn interpolation(
1260 &mut self,
1261 end_offset: u32,
1262 post_interpolation_mode: LexerMode<'input>,
1263 brace: bool,
1264 ) -> Option<Result<Token<'input>, SyntaxError>> {
1265 self.mode = LexerMode::Script;
1266
1267 let was_interpolating = self.interpolating;
1268 self.interpolating = true;
1269 let was_brace_interpolating = self.brace_interpolating;
1270 self.brace_interpolating = brace;
1272 let was_var_offset_depth = self.var_offset_depth;
1273 self.var_offset_depth = 0;
1274 let was_expect_string_varname = self.expect_string_varname;
1275 self.expect_string_varname = false;
1276
1277 let pending_error = loop {
1278 match self.advance() {
1279 Some(Ok(token)) => {
1280 let token_start = token.start.offset;
1281 let token_end = token_start + token.value.len() as u32;
1282 let is_final_token = token_start <= end_offset && end_offset <= token_end;
1283
1284 if brace {
1285 if token.kind == TokenKind::DollarLeftBrace && self.peek_string_varname_label() {
1286 self.expect_string_varname = true;
1287 }
1288 } else {
1289 match token.kind {
1290 TokenKind::LeftBracket => self.var_offset_depth += 1,
1291 TokenKind::RightBracket => {
1292 self.var_offset_depth = self.var_offset_depth.saturating_sub(1);
1293 }
1294 _ => {}
1295 }
1296 }
1297
1298 self.buffer.push_back(token);
1299
1300 if is_final_token {
1301 break None;
1302 }
1303 }
1304 Some(Err(error)) => break Some(error),
1305 None => break None,
1306 }
1307 };
1308
1309 self.mode = post_interpolation_mode;
1310 self.interpolating = was_interpolating;
1311 self.brace_interpolating = was_brace_interpolating;
1312 self.var_offset_depth = was_var_offset_depth;
1313 self.expect_string_varname = was_expect_string_varname;
1314
1315 if let Some(error) = pending_error {
1316 return Some(Err(error));
1317 }
1318
1319 self.advance()
1320 }
1321}
1322
1323impl HasFileId for Lexer<'_> {
1324 #[inline]
1325 fn file_id(&self) -> FileId {
1326 self.input.file_id()
1327 }
1328}
1329
1330#[inline]
1331fn matches_start_of_heredoc_document(input: &Input, prefix_len: usize) -> bool {
1332 let total = input.len();
1333 let base = input.current_offset();
1334
1335 let mut length = 3 + prefix_len;
1337 while base + length < total && input.read_at(base + length).is_ascii_whitespace() {
1339 length += 1;
1340 }
1341
1342 if base + length >= total || !is_start_of_identifier(input.read_at(base + length)) {
1344 return false;
1345 }
1346 length += 1; loop {
1350 let pos = base + length;
1351 if pos >= total {
1352 return false; }
1354
1355 let byte = *input.read_at(pos);
1356 if byte == b'\n' {
1357 return true; } else if byte == b'\r' {
1359 return pos + 1 < total && *input.read_at(pos + 1) == b'\n';
1361 } else if is_part_of_identifier(input.read_at(pos)) {
1362 length += 1;
1363 } else {
1364 return false; }
1366 }
1367}
1368
1369#[inline]
1370fn matches_start_of_double_quote_heredoc_document(input: &Input, prefix_len: usize) -> bool {
1371 let total = input.len();
1372 let base = input.current_offset();
1373
1374 let mut length = 3 + prefix_len;
1376 while base + length < total && input.read_at(base + length).is_ascii_whitespace() {
1377 length += 1;
1378 }
1379
1380 if base + length >= total || *input.read_at(base + length) != b'"' {
1382 return false;
1383 }
1384 length += 1;
1385
1386 if base + length >= total || !is_start_of_identifier(input.read_at(base + length)) {
1388 return false;
1389 }
1390 length += 1;
1391
1392 let mut terminated = false;
1394 loop {
1395 let pos = base + length;
1396 if pos >= total {
1397 return false;
1398 }
1399 let byte = input.read_at(pos);
1400 if *byte == b'\n' {
1401 return terminated;
1403 } else if *byte == b'\r' {
1404 return terminated && pos + 1 < total && *input.read_at(pos + 1) == b'\n';
1406 } else if !terminated && is_part_of_identifier(byte) {
1407 length += 1;
1408 } else if !terminated && *byte == b'"' {
1409 terminated = true;
1410 length += 1;
1411 } else {
1412 return false;
1413 }
1414 }
1415}
1416
1417#[inline]
1418fn matches_start_of_nowdoc_document(input: &Input, prefix_len: usize) -> bool {
1419 let total = input.len();
1420 let base = input.current_offset();
1421
1422 let mut length = 3 + prefix_len;
1424 while base + length < total && input.read_at(base + length).is_ascii_whitespace() {
1425 length += 1;
1426 }
1427
1428 if base + length >= total || *input.read_at(base + length) != b'\'' {
1430 return false;
1431 }
1432 length += 1;
1433
1434 if base + length >= total || !is_start_of_identifier(input.read_at(base + length)) {
1436 return false;
1437 }
1438 length += 1;
1439
1440 let mut terminated = false;
1442 loop {
1443 let pos = base + length;
1444 if pos >= total {
1445 return false;
1446 }
1447 let byte = *input.read_at(pos);
1448 if byte == b'\n' {
1449 return terminated;
1450 } else if byte == b'\r' {
1451 return terminated && pos + 1 < total && *input.read_at(pos + 1) == b'\n';
1452 } else if !terminated && is_part_of_identifier(&byte) {
1453 length += 1;
1454 } else if !terminated && byte == b'\'' {
1455 terminated = true;
1456 length += 1;
1457 } else {
1458 return false;
1459 }
1460 }
1461}
1462
1463#[inline]
1464fn matches_literal_double_quote_string(input: &Input, prefix_len: usize) -> bool {
1465 let total = input.len();
1466 let base = input.current_offset();
1467
1468 let mut pos = base + 1 + prefix_len;
1470 loop {
1471 if pos >= total {
1472 return true;
1474 }
1475 let byte = *input.read_at(pos);
1476 if byte == b'"' {
1477 return true;
1479 }
1480 if byte == b'\\' {
1481 pos += 2;
1483 continue;
1484 }
1485
1486 if pos + 1 < total {
1489 let next = *input.read_at(pos + 1);
1490 if (byte == b'$' && (is_start_of_identifier(&next) || next == b'{')) || (byte == b'{' && next == b'$') {
1491 return false;
1492 }
1493 }
1494 pos += 1;
1495 }
1496}
1497
1498fn read_document_indentation(input: &Input, body_offset: usize, label: &[u8], interpolated: bool) -> usize {
1504 let total = input.len();
1505 let base = input.current_offset();
1506 let mut position = body_offset;
1507 loop {
1508 let mut width = 0;
1509 while position < total && matches!(*input.read_at(position), b' ' | b'\t') {
1510 width += 1;
1511 position += 1;
1512 }
1513
1514 if label_matches_at(input, position, label, total) {
1515 return width;
1516 }
1517
1518 let mut last_was_slash = false;
1519 while position < total && !matches!(*input.read_at(position), b'\n' | b'\r') {
1520 let byte = *input.read_at(position);
1521 let next = (position + 1 < total).then(|| *input.read_at(position + 1));
1522
1523 if interpolated && !last_was_slash {
1524 if (byte == b'$' && next == Some(b'{')) || (byte == b'{' && next == Some(b'$')) {
1525 let from = (position - base) + 2;
1526 position = base + read_until_end_of_brace_interpolation(input, from) as usize;
1527
1528 continue;
1529 }
1530
1531 if byte == b'$' && next.is_some_and(|c| is_start_of_identifier(&c)) {
1532 let from = (position - base) + 2;
1533 position = base + read_until_end_of_variable_interpolation(input, from) as usize;
1534
1535 continue;
1536 }
1537 }
1538
1539 last_was_slash = byte == b'\\' && !last_was_slash;
1540 position += 1;
1541 }
1542
1543 if position >= total {
1544 return 0;
1545 }
1546
1547 if *input.read_at(position) == b'\r' && position + 1 < total && *input.read_at(position + 1) == b'\n' {
1548 position += 2;
1549 } else {
1550 position += 1;
1551 }
1552 }
1553}
1554
1555fn label_matches_at(input: &Input, position: usize, label: &[u8], total: usize) -> bool {
1558 if position + label.len() > total {
1559 return false;
1560 }
1561
1562 let mut index = 0;
1563 while index < label.len() {
1564 if *input.read_at(position + index) != label[index] {
1565 return false;
1566 }
1567 index += 1;
1568 }
1569
1570 let after = position + label.len();
1571 after >= total || !is_part_of_identifier(input.read_at(after))
1572}
1573
1574#[inline]
1577fn document_body_at_line_start(input: &Input) -> bool {
1578 let offset = input.current_offset();
1579 offset == 0 || matches!(*input.read_at(offset - 1), b'\n' | b'\r')
1580}
1581
1582#[inline]
1584fn document_leading_indent_width(input: &Input, indent: usize) -> usize {
1585 let mut width = 0;
1586 while width < indent && matches!(input.peek(width, 1), [b' ' | b'\t']) {
1587 width += 1;
1588 }
1589
1590 width
1591}
1592
1593#[inline]
1597fn document_line_is_closing_marker(input: &Input, width: usize, label: &[u8]) -> bool {
1598 input.peek(width, label.len()) == label
1599 && input.peek(width + label.len(), 1).first().is_none_or(|byte| !is_part_of_identifier(byte))
1600}
1601
1602#[inline]
1606fn document_next_line_is_closing_marker(input: &Input, offset: usize, label: &[u8]) -> bool {
1607 let mut width = 0;
1608 while matches!(input.peek(offset + width, 1), [b' ' | b'\t']) {
1609 width += 1;
1610 }
1611
1612 input.peek(offset + width, label.len()) == label
1613 && input.peek(offset + width + label.len(), 1).first().is_none_or(|byte| !is_part_of_identifier(byte))
1614}
1615
1616#[inline]
1617fn read_start_of_heredoc_document(input: &Input, double_quoted: bool, prefix_len: usize) -> (usize, usize, usize) {
1618 let total = input.len();
1619 let base = input.current_offset();
1620
1621 let mut pos = base + 3 + prefix_len;
1623 let mut whitespaces = 0;
1624 while pos < total && input.read_at(pos).is_ascii_whitespace() {
1625 whitespaces += 1;
1626 pos += 1;
1627 }
1628
1629 let mut length = 3 + prefix_len + whitespaces + if double_quoted { 2 } else { 1 };
1634
1635 let mut label_length = 1; let mut terminated = false; loop {
1638 let pos = base + length;
1639 if pos >= total {
1644 return (length, whitespaces, label_length);
1645 }
1646
1647 let byte = *input.read_at(pos);
1648 if byte == b'\n' {
1649 length += 1;
1651 return (length, whitespaces, label_length);
1652 } else if byte == b'\r' {
1653 if pos + 1 < total && *input.read_at(pos + 1) == b'\n' {
1655 length += 2;
1656 } else {
1657 length += 1;
1658 }
1659 return (length, whitespaces, label_length);
1660 } else if is_part_of_identifier(&byte) && (!double_quoted || !terminated) {
1661 length += 1;
1664 label_length += 1;
1665 } else if double_quoted && !terminated && byte == b'"' {
1666 length += 1;
1668 terminated = true;
1669 } else {
1670 return (length, whitespaces, label_length);
1672 }
1673 }
1674}
1675
1676#[inline]
1677fn read_start_of_nowdoc_document(input: &Input, prefix_len: usize) -> (usize, usize, usize) {
1678 let total = input.len();
1679 let base = input.current_offset();
1680
1681 let mut pos = base + 3 + prefix_len;
1682 let mut whitespaces = 0;
1683 while pos < total && input.read_at(pos).is_ascii_whitespace() {
1684 whitespaces += 1;
1685 pos += 1;
1686 }
1687
1688 let mut length = 3 + prefix_len + whitespaces + 2;
1690
1691 let mut label_length = 1;
1692 let mut terminated = false;
1693 loop {
1694 let pos = base + length;
1695 if pos >= total {
1696 return (length, whitespaces, label_length);
1699 }
1700 let byte = *input.read_at(pos);
1701
1702 if byte == b'\n' {
1703 length += 1;
1705 return (length, whitespaces, label_length);
1706 } else if byte == b'\r' {
1707 if pos + 1 < total && *input.read_at(pos + 1) == b'\n' {
1709 length += 2;
1710 } else {
1711 length += 1;
1712 }
1713 return (length, whitespaces, label_length);
1714 } else if is_part_of_identifier(&byte) && !terminated {
1715 length += 1;
1717 label_length += 1;
1718 } else if !terminated && byte == b'\'' {
1719 length += 1;
1721 terminated = true;
1722 } else {
1723 return (length, whitespaces, label_length);
1725 }
1726 }
1727}
1728
1729#[inline]
1730fn read_literal_string(input: &Input, quote: u8, prefix_len: usize) -> (TokenKind, usize) {
1731 let total = input.len();
1732 let start = input.current_offset();
1733 let skip = prefix_len + 1; let mut length = skip;
1735
1736 let bytes = input.peek(skip, total - start - skip);
1737 loop {
1738 let scan_start = length - skip;
1739 match memchr2(quote, b'\\', &bytes[scan_start..]) {
1740 Some(pos) => {
1741 let abs_pos = scan_start + pos;
1742 let byte = bytes[abs_pos];
1743
1744 if byte == b'\\' {
1745 length = skip + abs_pos + 2;
1746 if length > total - start {
1747 return (TokenKind::PartialLiteralString, total - start);
1748 }
1749 } else {
1750 length = skip + abs_pos + 1; return (TokenKind::LiteralString, length);
1752 }
1753 }
1754 None => {
1755 return (TokenKind::PartialLiteralString, total - start);
1757 }
1758 }
1759 }
1760}
1761
1762#[inline]
1763fn read_until_end_of_variable_interpolation(input: &Input, from: usize) -> u32 {
1764 let total = input.len();
1765 let base = input.current_offset();
1766 let mut offset = from;
1768
1769 loop {
1770 let abs = base + offset;
1771 if abs >= total {
1772 break;
1774 }
1775
1776 if is_part_of_identifier(input.read_at(abs)) {
1778 offset += 1;
1779 continue;
1780 }
1781
1782 if *input.read_at(abs) == b'[' {
1784 offset += 1;
1785 let mut nesting = 0;
1786 loop {
1787 let abs_inner = base + offset;
1788 if abs_inner >= total {
1789 break;
1790 }
1791 let b = input.read_at(abs_inner);
1792 if *b == b']' {
1793 offset += 1;
1794 if nesting == 0 {
1795 break;
1796 }
1797
1798 nesting -= 1;
1799 } else if *b == b'[' {
1800 offset += 1;
1801 nesting += 1;
1802 } else if b.is_ascii_whitespace() {
1803 break;
1805 } else {
1806 offset += 1;
1807 }
1808 }
1809 break;
1811 }
1812
1813 if base + offset + 2 < total
1815 && *input.read_at(abs) == b'-'
1816 && *input.read_at(base + offset + 1) == b'>'
1817 && is_start_of_identifier(input.read_at(base + offset + 2))
1818 {
1819 offset += 3;
1820 while base + offset < total && is_part_of_identifier(input.read_at(base + offset)) {
1822 offset += 1;
1823 }
1824 break;
1825 }
1826
1827 if base + offset + 3 < total
1829 && *input.read_at(abs) == b'?'
1830 && *input.read_at(base + offset + 1) == b'-'
1831 && *input.read_at(base + offset + 2) == b'>'
1832 && is_start_of_identifier(input.read_at(base + offset + 3))
1833 {
1834 offset += 4;
1835 while base + offset < total && is_part_of_identifier(input.read_at(base + offset)) {
1836 offset += 1;
1837 }
1838 break;
1839 }
1840
1841 break;
1843 }
1844
1845 offset as u32
1846}
1847
1848#[inline]
1859fn read_until_end_of_brace_interpolation(input: &Input, from: usize) -> u32 {
1860 let total = input.len();
1861 let base = input.current_offset();
1862 let mut offset = from;
1863 let mut nesting = 0;
1864
1865 loop {
1866 let abs = base + offset;
1867 if abs >= total {
1868 break;
1869 }
1870 match *input.read_at(abs) {
1871 b'}' => {
1872 offset += 1;
1873 if nesting == 0 {
1874 break;
1875 }
1876
1877 nesting -= 1;
1878 }
1879 b'{' => {
1880 offset += 1;
1881 nesting += 1;
1882 }
1883 quote @ (b'\'' | b'"' | b'`') => {
1884 offset += 1;
1885 loop {
1886 let abs = base + offset;
1887 if abs >= total {
1888 break;
1889 }
1890 match *input.read_at(abs) {
1891 b'\\' => offset += 2,
1892 b if b == quote => {
1893 offset += 1;
1894 break;
1895 }
1896 _ => offset += 1,
1897 }
1898 }
1899 }
1900 _ => {
1901 offset += 1;
1902 }
1903 }
1904 }
1905
1906 offset as u32
1907}
1908
1909#[inline]
1912fn scan_multi_line_comment(bytes: &[u8]) -> Option<usize> {
1913 memmem::find(bytes, b"*/").map(|pos| pos + 2)
1915}
1916
1917#[inline]
1921fn scan_single_line_comment(bytes: &[u8]) -> usize {
1922 let mut pos = 0;
1923 while pos < bytes.len() {
1924 match memchr::memchr3(b'\n', b'\r', b'?', &bytes[pos..]) {
1925 Some(offset) => {
1926 let found_pos = pos + offset;
1927 match bytes[found_pos] {
1928 b'\n' | b'\r' => return found_pos,
1929 b'?' => {
1930 if found_pos + 1 < bytes.len() && bytes[found_pos + 1] == b'>' {
1932 if found_pos > 0 && bytes[found_pos - 1].is_ascii_whitespace() {
1934 return found_pos - 1;
1935 }
1936 return found_pos;
1937 }
1938 pos = found_pos + 1;
1940 }
1941 _ => return found_pos,
1944 }
1945 }
1946 None => return bytes.len(),
1947 }
1948 }
1949
1950 bytes.len()
1951}