Skip to main content

php_lexer/
token.rs

1/// All token types produced by the PHP lexer.
2///
3/// The variants are grouped by category in source order; the `#[repr(u8)]`
4/// guarantees a compact discriminant layout used by [`TokenKind::is_assignment_op`].
5#[repr(u8)]
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub enum TokenKind {
8    // -------------------------------------------------------------------------
9    // Literals
10    // -------------------------------------------------------------------------
11    /// Float in scientific notation: `1e10`, `1.5e-3`, `1E+10`.
12    FloatLiteral,
13
14    /// Float with digits on both sides of the decimal point: `1.5`, `0.0`.
15    FloatLiteralSimple,
16
17    /// Float starting with a leading dot: `.5`, `.001`.
18    FloatLiteralLeadingDot,
19
20    /// Hexadecimal integer literal: `0x1A`, `0xFF`.
21    HexIntLiteral,
22
23    /// Binary integer literal: `0b1010`, `0B0011`.
24    BinIntLiteral,
25
26    /// Octal integer literal in the new `0o` prefix form: `0o17` (PHP 8.1+).
27    OctIntLiteralNew,
28
29    /// Octal integer literal in the legacy leading-zero form: `017`.
30    OctIntLiteral,
31
32    /// Decimal integer literal: `0`, `42`, `1_000_000`.
33    IntLiteral,
34
35    /// Single-quoted string literal: `'hello'`. Optional `b`/`B` prefix allowed.
36    SingleQuotedString,
37
38    /// Double-quoted string literal: `"hello $name"`. Triggers interpolation processing.
39    DoubleQuotedString,
40
41    /// Backtick string (shell execution): `` `ls -la` ``.
42    BacktickString,
43
44    // -------------------------------------------------------------------------
45    // Variables
46    // -------------------------------------------------------------------------
47    /// A `$`-prefixed variable name: `$foo`, `$myVar`.
48    Variable,
49
50    /// A bare `$` not followed by a valid identifier start (variable-variable prefix).
51    Dollar,
52
53    // -------------------------------------------------------------------------
54    // Identifiers
55    // -------------------------------------------------------------------------
56    /// A bare identifier or keyword that has not yet been resolved.
57    /// PHP keywords are case-insensitive and are resolved from this token.
58    Identifier,
59
60    // -------------------------------------------------------------------------
61    // Arithmetic operators
62    // -------------------------------------------------------------------------
63    /// `+`
64    Plus,
65    /// `-`
66    Minus,
67    /// `*`
68    Star,
69    /// `/`
70    Slash,
71    /// `%`
72    Percent,
73    /// `**` — exponentiation.
74    StarStar,
75    /// `.` — string concatenation.
76    Dot,
77
78    // -------------------------------------------------------------------------
79    // Assignment operators
80    // -------------------------------------------------------------------------
81    // NOTE: Equals..=CoalesceEquals must remain contiguous; is_assignment_op relies on this.
82    /// `=`
83    Equals,
84    /// `+=`
85    PlusEquals,
86    /// `-=`
87    MinusEquals,
88    /// `*=`
89    StarEquals,
90    /// `/=`
91    SlashEquals,
92    /// `%=`
93    PercentEquals,
94    /// `**=`
95    StarStarEquals,
96    /// `.=`
97    DotEquals,
98    /// `&=`
99    AmpersandEquals,
100    /// `|=`
101    PipeEquals,
102    /// `^=`
103    CaretEquals,
104    /// `<<=`
105    ShiftLeftEquals,
106    /// `>>=`
107    ShiftRightEquals,
108    /// `??=`
109    CoalesceEquals,
110
111    // -------------------------------------------------------------------------
112    // Comparison operators
113    // -------------------------------------------------------------------------
114    /// `==` — loose equality.
115    EqualsEquals,
116    /// `!=` or `<>` — loose inequality.
117    BangEquals,
118    /// `===` — strict equality.
119    EqualsEqualsEquals,
120    /// `!==` — strict inequality.
121    BangEqualsEquals,
122    /// `<`
123    LessThan,
124    /// `>`
125    GreaterThan,
126    /// `<=`
127    LessThanEquals,
128    /// `>=`
129    GreaterThanEquals,
130    /// `<=>` — spaceship / three-way comparison.
131    Spaceship,
132
133    // -------------------------------------------------------------------------
134    // Logical / bitwise operators
135    // -------------------------------------------------------------------------
136    /// `&&` — short-circuit boolean AND.
137    AmpersandAmpersand,
138    /// `||` — short-circuit boolean OR.
139    PipePipe,
140    /// `!` — boolean NOT.
141    Bang,
142
143    /// `&` — bitwise AND (also reference marker in declarations).
144    Ampersand,
145    /// `|` — bitwise OR.
146    Pipe,
147    /// `^` — bitwise XOR.
148    Caret,
149    /// `~` — bitwise NOT.
150    Tilde,
151    /// `<<` — left bit-shift.
152    ShiftLeft,
153    /// `>>` — right bit-shift.
154    ShiftRight,
155
156    // -------------------------------------------------------------------------
157    // Increment / decrement
158    // -------------------------------------------------------------------------
159    /// `++`
160    PlusPlus,
161    /// `--`
162    MinusMinus,
163
164    // -------------------------------------------------------------------------
165    // Other operators
166    // -------------------------------------------------------------------------
167    /// `?` — ternary operator or nullable type-hint prefix.
168    Question,
169    /// `??` — null coalescing.
170    QuestionQuestion,
171    /// `:` — ternary separator, named argument separator, or return type separator.
172    Colon,
173
174    /// `=>` — key-value separator in arrays and `match` arms.
175    FatArrow,
176
177    /// `|>` — pipe operator (PHP 8.5+).
178    PipeArrow,
179
180    // -------------------------------------------------------------------------
181    // Delimiters & punctuation
182    // -------------------------------------------------------------------------
183    /// `(`
184    LeftParen,
185    /// `)`
186    RightParen,
187    /// `[`
188    LeftBracket,
189    /// `]`
190    RightBracket,
191    /// `{`
192    LeftBrace,
193    /// `}`
194    RightBrace,
195    /// `;`
196    Semicolon,
197    /// `,`
198    Comma,
199
200    /// `::` — static access separator.
201    DoubleColon,
202
203    /// `->` — object property / method access.
204    Arrow,
205
206    /// `?->` — nullsafe property / method access.
207    NullsafeArrow,
208
209    /// `\` — namespace separator.
210    Backslash,
211
212    /// `@` — error-suppression operator.
213    At,
214
215    /// `#[` — attribute syntax opener.
216    HashBracket,
217
218    /// `...` — splat / variadic operator.
219    Ellipsis,
220
221    // -------------------------------------------------------------------------
222    // Keywords (resolved from Identifier at lex time)
223    // -------------------------------------------------------------------------
224    /// `if`
225    If,
226    /// `else`
227    Else,
228    /// `elseif`
229    ElseIf,
230    /// `while`
231    While,
232    /// `do`
233    Do,
234    /// `for`
235    For,
236    /// `foreach`
237    Foreach,
238    /// `as`
239    As,
240    /// `function`
241    Function,
242    /// `return`
243    Return,
244    /// `echo`
245    Echo,
246    /// `print`
247    Print,
248    /// `true`
249    True,
250    /// `false`
251    False,
252    /// `null`
253    Null,
254    /// `and` — low-precedence boolean AND.
255    And,
256    /// `or` — low-precedence boolean OR.
257    Or,
258    /// `xor` — low-precedence boolean XOR.
259    Xor,
260    /// `break`
261    Break,
262    /// `continue`
263    Continue,
264    /// `switch`
265    Switch,
266    /// `case`
267    Case,
268    /// `default`
269    Default,
270    /// `endif` — alternative `if` syntax terminator.
271    EndIf,
272    /// `endwhile` — alternative `while` syntax terminator.
273    EndWhile,
274    /// `endfor` — alternative `for` syntax terminator.
275    EndFor,
276    /// `endforeach` — alternative `foreach` syntax terminator.
277    EndForeach,
278    /// `throw`
279    Throw,
280    /// `try`
281    Try,
282    /// `catch`
283    Catch,
284    /// `finally`
285    Finally,
286    /// `instanceof`
287    Instanceof,
288    /// `array` — as a keyword (e.g. `array(1, 2)` or `array` type hint).
289    Array,
290    /// `list` — destructuring construct: `list($a, $b) = ...`.
291    List,
292    /// `goto`
293    Goto,
294    /// `declare`
295    Declare,
296    /// `unset`
297    Unset,
298    /// `global`
299    Global,
300    /// `enddeclare` — alternative `declare` syntax terminator.
301    EndDeclare,
302    /// `endswitch` — alternative `switch` syntax terminator.
303    EndSwitch,
304    /// `isset`
305    Isset,
306    /// `empty`
307    Empty,
308    /// `include`
309    Include,
310    /// `include_once`
311    IncludeOnce,
312    /// `require`
313    Require,
314    /// `require_once`
315    RequireOnce,
316    /// `eval`
317    Eval,
318    /// `exit`
319    Exit,
320    /// `die` — alias for `exit`.
321    Die,
322    /// `clone`
323    Clone,
324
325    // OOP keywords
326    /// `new`
327    New,
328    /// `class`
329    Class,
330    /// `abstract`
331    Abstract,
332    /// `final`
333    Final,
334    /// `interface`
335    Interface,
336    /// `trait`
337    Trait,
338    /// `extends`
339    Extends,
340    /// `implements`
341    Implements,
342    /// `public`
343    Public,
344    /// `protected`
345    Protected,
346    /// `private`
347    Private,
348    /// `static`
349    Static,
350    /// `const`
351    Const,
352    /// `fn` — arrow function keyword.
353    Fn_,
354    /// `match` — match expression keyword (PHP 8.0+).
355    Match_,
356    /// `namespace`
357    Namespace,
358    /// `use`
359    Use,
360    /// `readonly` (PHP 8.1+).
361    Readonly,
362    /// `enum` (PHP 8.1+).
363    Enum_,
364    /// `yield`
365    Yield_,
366    /// `from` — used in `yield from`.
367    From,
368    /// `self`
369    Self_,
370    /// `parent`
371    Parent_,
372
373    // -------------------------------------------------------------------------
374    // Magic constants (resolved from Identifier at lex time)
375    // -------------------------------------------------------------------------
376    /// `__CLASS__`
377    MagicClass,
378    /// `__DIR__`
379    MagicDir,
380    /// `__FILE__`
381    MagicFile,
382    /// `__FUNCTION__`
383    MagicFunction,
384    /// `__LINE__`
385    MagicLine,
386    /// `__METHOD__`
387    MagicMethod,
388    /// `__NAMESPACE__`
389    MagicNamespace,
390    /// `__TRAIT__`
391    MagicTrait,
392    /// `__PROPERTY__` (PHP 8.4+)
393    MagicProperty,
394    /// `__halt_compiler` — stops the parser; remaining bytes are raw data.
395    HaltCompiler,
396
397    // -------------------------------------------------------------------------
398    // PHP tags & template output
399    // -------------------------------------------------------------------------
400    /// `<?php` or `<?=` opening tag.
401    OpenTag,
402
403    /// `?>` closing tag; switches the lexer back to inline-HTML mode.
404    CloseTag,
405
406    /// Raw HTML text between PHP tags (produced by the `Lexer` wrapper, not the inner scanner).
407    InlineHtml,
408
409    // -------------------------------------------------------------------------
410    // Heredoc / Nowdoc (produced by Lexer wrapper)
411    // -------------------------------------------------------------------------
412    /// `<<<LABEL … LABEL;` — heredoc string (supports interpolation).
413    Heredoc,
414    /// `<<<'LABEL' … LABEL;` — nowdoc string (no interpolation).
415    Nowdoc,
416
417    // -------------------------------------------------------------------------
418    // Error tokens
419    // -------------------------------------------------------------------------
420    /// An invalid numeric literal, e.g. `1_000_` (trailing underscore).
421    /// The lexer emits this token and records a [`LexerError`](crate::LexerError) for it.
422    InvalidNumericLiteral,
423
424    // -------------------------------------------------------------------------
425    // Comments (filtered into a side-table by the parser)
426    // -------------------------------------------------------------------------
427    /// `// …` single-line slash comment.
428    LineComment,
429    /// `# …` single-line hash comment.
430    HashComment,
431    /// `/* … */` block comment.
432    BlockComment,
433    /// `/** … */` doc-block comment (first non-whitespace char after `/*` is `*`).
434    DocComment,
435
436    // -------------------------------------------------------------------------
437    // End of input
438    // -------------------------------------------------------------------------
439    /// Sentinel token emitted once all source bytes have been consumed.
440    Eof,
441}
442
443impl TokenKind {
444    /// Returns `true` for the four comment-token variants.
445    #[inline]
446    pub fn is_comment(self) -> bool {
447        matches!(
448            self,
449            TokenKind::LineComment
450                | TokenKind::HashComment
451                | TokenKind::BlockComment
452                | TokenKind::DocComment
453        )
454    }
455}
456
457impl TokenKind {
458    /// Whether this is `=` or a compound assignment operator.
459    #[inline(always)]
460    pub fn is_assignment_op(self) -> bool {
461        // The assignment operators are contiguous in the enum definition:
462        // Equals..=CoalesceEquals. With #[repr(u8)], a single range check suffices.
463        (self as u8).wrapping_sub(TokenKind::Equals as u8)
464            <= (TokenKind::CoalesceEquals as u8 - TokenKind::Equals as u8)
465    }
466}
467
468/// Resolve a keyword from an identifier string. Returns the keyword TokenKind
469/// if the string is a keyword, or None if it's a plain identifier.
470pub fn resolve_keyword(text: &str) -> Option<TokenKind> {
471    // PHP keywords are case-insensitive; use eq_ignore_ascii_case to avoid allocation.
472    // Dispatch on length first to reduce comparisons.
473    let t = text;
474    match text.len() {
475        2 => {
476            if t.eq_ignore_ascii_case("if") {
477                return Some(TokenKind::If);
478            }
479            if t.eq_ignore_ascii_case("do") {
480                return Some(TokenKind::Do);
481            }
482            if t.eq_ignore_ascii_case("or") {
483                return Some(TokenKind::Or);
484            }
485            if t.eq_ignore_ascii_case("as") {
486                return Some(TokenKind::As);
487            }
488            if t.eq_ignore_ascii_case("fn") {
489                return Some(TokenKind::Fn_);
490            }
491        }
492        3 => {
493            if t.eq_ignore_ascii_case("for") {
494                return Some(TokenKind::For);
495            }
496            if t.eq_ignore_ascii_case("xor") {
497                return Some(TokenKind::Xor);
498            }
499            if t.eq_ignore_ascii_case("and") {
500                return Some(TokenKind::And);
501            }
502            if t.eq_ignore_ascii_case("new") {
503                return Some(TokenKind::New);
504            }
505            if t.eq_ignore_ascii_case("use") {
506                return Some(TokenKind::Use);
507            }
508            if t.eq_ignore_ascii_case("try") {
509                return Some(TokenKind::Try);
510            }
511            if t.eq_ignore_ascii_case("die") {
512                return Some(TokenKind::Die);
513            }
514        }
515        4 => {
516            if t.eq_ignore_ascii_case("else") {
517                return Some(TokenKind::Else);
518            }
519            if t.eq_ignore_ascii_case("echo") {
520                return Some(TokenKind::Echo);
521            }
522            if t.eq_ignore_ascii_case("true") {
523                return Some(TokenKind::True);
524            }
525            if t.eq_ignore_ascii_case("null") {
526                return Some(TokenKind::Null);
527            }
528            if t.eq_ignore_ascii_case("list") {
529                return Some(TokenKind::List);
530            }
531            if t.eq_ignore_ascii_case("goto") {
532                return Some(TokenKind::Goto);
533            }
534            if t.eq_ignore_ascii_case("case") {
535                return Some(TokenKind::Case);
536            }
537            if t.eq_ignore_ascii_case("self") {
538                return Some(TokenKind::Self_);
539            }
540            if t.eq_ignore_ascii_case("from") {
541                return Some(TokenKind::From);
542            }
543            if t.eq_ignore_ascii_case("enum") {
544                return Some(TokenKind::Enum_);
545            }
546            if t.eq_ignore_ascii_case("eval") {
547                return Some(TokenKind::Eval);
548            }
549            if t.eq_ignore_ascii_case("exit") {
550                return Some(TokenKind::Exit);
551            }
552        }
553        5 => {
554            if t.eq_ignore_ascii_case("while") {
555                return Some(TokenKind::While);
556            }
557            if t.eq_ignore_ascii_case("false") {
558                return Some(TokenKind::False);
559            }
560            if t.eq_ignore_ascii_case("array") {
561                return Some(TokenKind::Array);
562            }
563            if t.eq_ignore_ascii_case("unset") {
564                return Some(TokenKind::Unset);
565            }
566            if t.eq_ignore_ascii_case("isset") {
567                return Some(TokenKind::Isset);
568            }
569            if t.eq_ignore_ascii_case("empty") {
570                return Some(TokenKind::Empty);
571            }
572            if t.eq_ignore_ascii_case("print") {
573                return Some(TokenKind::Print);
574            }
575            if t.eq_ignore_ascii_case("throw") {
576                return Some(TokenKind::Throw);
577            }
578            if t.eq_ignore_ascii_case("catch") {
579                return Some(TokenKind::Catch);
580            }
581            if t.eq_ignore_ascii_case("break") {
582                return Some(TokenKind::Break);
583            }
584            if t.eq_ignore_ascii_case("yield") {
585                return Some(TokenKind::Yield_);
586            }
587            if t.eq_ignore_ascii_case("class") {
588                return Some(TokenKind::Class);
589            }
590            if t.eq_ignore_ascii_case("const") {
591                return Some(TokenKind::Const);
592            }
593            if t.eq_ignore_ascii_case("final") {
594                return Some(TokenKind::Final);
595            }
596            if t.eq_ignore_ascii_case("match") {
597                return Some(TokenKind::Match_);
598            }
599            if t.eq_ignore_ascii_case("trait") {
600                return Some(TokenKind::Trait);
601            }
602            if t.eq_ignore_ascii_case("clone") {
603                return Some(TokenKind::Clone);
604            }
605            if t.eq_ignore_ascii_case("endif") {
606                return Some(TokenKind::EndIf);
607            }
608        }
609        6 => {
610            if t.eq_ignore_ascii_case("elseif") {
611                return Some(TokenKind::ElseIf);
612            }
613            if t.eq_ignore_ascii_case("return") {
614                return Some(TokenKind::Return);
615            }
616            if t.eq_ignore_ascii_case("switch") {
617                return Some(TokenKind::Switch);
618            }
619            if t.eq_ignore_ascii_case("global") {
620                return Some(TokenKind::Global);
621            }
622            if t.eq_ignore_ascii_case("static") {
623                return Some(TokenKind::Static);
624            }
625            if t.eq_ignore_ascii_case("public") {
626                return Some(TokenKind::Public);
627            }
628            if t.eq_ignore_ascii_case("parent") {
629                return Some(TokenKind::Parent_);
630            }
631            if t.eq_ignore_ascii_case("endfor") {
632                return Some(TokenKind::EndFor);
633            }
634        }
635        7 => {
636            if t.eq_ignore_ascii_case("foreach") {
637                return Some(TokenKind::Foreach);
638            }
639            if t.eq_ignore_ascii_case("default") {
640                return Some(TokenKind::Default);
641            }
642            if t.eq_ignore_ascii_case("finally") {
643                return Some(TokenKind::Finally);
644            }
645            if t.eq_ignore_ascii_case("include") {
646                return Some(TokenKind::Include);
647            }
648            if t.eq_ignore_ascii_case("declare") {
649                return Some(TokenKind::Declare);
650            }
651            if t.eq_ignore_ascii_case("extends") {
652                return Some(TokenKind::Extends);
653            }
654            if t.eq_ignore_ascii_case("require") {
655                return Some(TokenKind::Require);
656            }
657            if t.eq_ignore_ascii_case("private") {
658                return Some(TokenKind::Private);
659            }
660            if t.eq_ignore_ascii_case("__dir__") {
661                return Some(TokenKind::MagicDir);
662            }
663        }
664        8 => {
665            if t.eq_ignore_ascii_case("function") {
666                return Some(TokenKind::Function);
667            }
668            if t.eq_ignore_ascii_case("abstract") {
669                return Some(TokenKind::Abstract);
670            }
671            if t.eq_ignore_ascii_case("readonly") {
672                return Some(TokenKind::Readonly);
673            }
674            if t.eq_ignore_ascii_case("continue") {
675                return Some(TokenKind::Continue);
676            }
677            if t.eq_ignore_ascii_case("endwhile") {
678                return Some(TokenKind::EndWhile);
679            }
680            if t.eq_ignore_ascii_case("__file__") {
681                return Some(TokenKind::MagicFile);
682            }
683            if t.eq_ignore_ascii_case("__line__") {
684                return Some(TokenKind::MagicLine);
685            }
686        }
687        9 => {
688            if t.eq_ignore_ascii_case("namespace") {
689                return Some(TokenKind::Namespace);
690            }
691            if t.eq_ignore_ascii_case("interface") {
692                return Some(TokenKind::Interface);
693            }
694            if t.eq_ignore_ascii_case("protected") {
695                return Some(TokenKind::Protected);
696            }
697            if t.eq_ignore_ascii_case("endswitch") {
698                return Some(TokenKind::EndSwitch);
699            }
700            if t.eq_ignore_ascii_case("__class__") {
701                return Some(TokenKind::MagicClass);
702            }
703            if t.eq_ignore_ascii_case("__trait__") {
704                return Some(TokenKind::MagicTrait);
705            }
706        }
707        10 => {
708            if t.eq_ignore_ascii_case("implements") {
709                return Some(TokenKind::Implements);
710            }
711            if t.eq_ignore_ascii_case("instanceof") {
712                return Some(TokenKind::Instanceof);
713            }
714            if t.eq_ignore_ascii_case("endforeach") {
715                return Some(TokenKind::EndForeach);
716            }
717            if t.eq_ignore_ascii_case("enddeclare") {
718                return Some(TokenKind::EndDeclare);
719            }
720            if t.eq_ignore_ascii_case("__method__") {
721                return Some(TokenKind::MagicMethod);
722            }
723        }
724        12 => {
725            if t.eq_ignore_ascii_case("include_once") {
726                return Some(TokenKind::IncludeOnce);
727            }
728            if t.eq_ignore_ascii_case("require_once") {
729                return Some(TokenKind::RequireOnce);
730            }
731            if t.eq_ignore_ascii_case("__function__") {
732                return Some(TokenKind::MagicFunction);
733            }
734            if t.eq_ignore_ascii_case("__property__") {
735                return Some(TokenKind::MagicProperty);
736            }
737        }
738        13 if t.eq_ignore_ascii_case("__namespace__") => {
739            return Some(TokenKind::MagicNamespace);
740        }
741        15 if t.eq_ignore_ascii_case("__halt_compiler") => {
742            return Some(TokenKind::HaltCompiler);
743        }
744        _ => {}
745    }
746    None
747}
748
749impl std::fmt::Display for TokenKind {
750    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
751        match self {
752            TokenKind::IntLiteral => write!(f, "integer"),
753            TokenKind::HexIntLiteral => write!(f, "hex integer"),
754            TokenKind::BinIntLiteral => write!(f, "binary integer"),
755            TokenKind::OctIntLiteral | TokenKind::OctIntLiteralNew => write!(f, "octal integer"),
756            TokenKind::FloatLiteral
757            | TokenKind::FloatLiteralSimple
758            | TokenKind::FloatLiteralLeadingDot => write!(f, "float"),
759            TokenKind::SingleQuotedString | TokenKind::DoubleQuotedString => write!(f, "string"),
760            TokenKind::BacktickString => write!(f, "backtick string"),
761            TokenKind::Variable => write!(f, "variable"),
762            TokenKind::Dollar => write!(f, "'$'"),
763            TokenKind::Identifier => write!(f, "identifier"),
764            TokenKind::Plus => write!(f, "'+'"),
765            TokenKind::Minus => write!(f, "'-'"),
766            TokenKind::Star => write!(f, "'*'"),
767            TokenKind::Slash => write!(f, "'/'"),
768            TokenKind::Percent => write!(f, "'%'"),
769            TokenKind::StarStar => write!(f, "'**'"),
770            TokenKind::Dot => write!(f, "'.'"),
771            TokenKind::Equals => write!(f, "'='"),
772            TokenKind::PlusEquals => write!(f, "'+='"),
773            TokenKind::MinusEquals => write!(f, "'-='"),
774            TokenKind::StarEquals => write!(f, "'*='"),
775            TokenKind::SlashEquals => write!(f, "'/='"),
776            TokenKind::PercentEquals => write!(f, "'%='"),
777            TokenKind::StarStarEquals => write!(f, "'**='"),
778            TokenKind::DotEquals => write!(f, "'.='"),
779            TokenKind::AmpersandEquals => write!(f, "'&='"),
780            TokenKind::PipeEquals => write!(f, "'|='"),
781            TokenKind::CaretEquals => write!(f, "'^='"),
782            TokenKind::ShiftLeftEquals => write!(f, "'<<='"),
783            TokenKind::ShiftRightEquals => write!(f, "'>>='"),
784            TokenKind::CoalesceEquals => write!(f, "'??='"),
785            TokenKind::EqualsEquals => write!(f, "'=='"),
786            TokenKind::BangEquals => write!(f, "'!='"),
787            TokenKind::EqualsEqualsEquals => write!(f, "'==='"),
788            TokenKind::BangEqualsEquals => write!(f, "'!=='"),
789            TokenKind::LessThan => write!(f, "'<'"),
790            TokenKind::GreaterThan => write!(f, "'>'"),
791            TokenKind::LessThanEquals => write!(f, "'<='"),
792            TokenKind::GreaterThanEquals => write!(f, "'>='"),
793            TokenKind::Spaceship => write!(f, "'<=>'"),
794            TokenKind::AmpersandAmpersand => write!(f, "'&&'"),
795            TokenKind::PipePipe => write!(f, "'||'"),
796            TokenKind::Bang => write!(f, "'!'"),
797            TokenKind::Ampersand => write!(f, "'&'"),
798            TokenKind::Pipe => write!(f, "'|'"),
799            TokenKind::Caret => write!(f, "'^'"),
800            TokenKind::Tilde => write!(f, "'~'"),
801            TokenKind::ShiftLeft => write!(f, "'<<'"),
802            TokenKind::ShiftRight => write!(f, "'>>'"),
803            TokenKind::PlusPlus => write!(f, "'++'"),
804            TokenKind::MinusMinus => write!(f, "'--'"),
805            TokenKind::Question => write!(f, "'?'"),
806            TokenKind::QuestionQuestion => write!(f, "'??'"),
807            TokenKind::Colon => write!(f, "':'"),
808            TokenKind::FatArrow => write!(f, "'=>'"),
809            TokenKind::PipeArrow => write!(f, "'|>'"),
810            TokenKind::LeftParen => write!(f, "'('"),
811            TokenKind::RightParen => write!(f, "')'"),
812            TokenKind::LeftBracket => write!(f, "'['"),
813            TokenKind::RightBracket => write!(f, "']'"),
814            TokenKind::LeftBrace => write!(f, "'{{'"),
815            TokenKind::RightBrace => write!(f, "'}}'"),
816            TokenKind::Semicolon => write!(f, "';'"),
817            TokenKind::Comma => write!(f, "','"),
818            TokenKind::DoubleColon => write!(f, "'::'"),
819            TokenKind::Arrow => write!(f, "'->'"),
820            TokenKind::NullsafeArrow => write!(f, "'?->'"),
821            TokenKind::Backslash => write!(f, "'\\'"),
822            TokenKind::At => write!(f, "'@'"),
823            TokenKind::HashBracket => write!(f, "'#['"),
824            TokenKind::Ellipsis => write!(f, "'...'"),
825            TokenKind::If => write!(f, "'if'"),
826            TokenKind::Else => write!(f, "'else'"),
827            TokenKind::ElseIf => write!(f, "'elseif'"),
828            TokenKind::While => write!(f, "'while'"),
829            TokenKind::Do => write!(f, "'do'"),
830            TokenKind::For => write!(f, "'for'"),
831            TokenKind::Foreach => write!(f, "'foreach'"),
832            TokenKind::As => write!(f, "'as'"),
833            TokenKind::Function => write!(f, "'function'"),
834            TokenKind::Return => write!(f, "'return'"),
835            TokenKind::Echo => write!(f, "'echo'"),
836            TokenKind::Print => write!(f, "'print'"),
837            TokenKind::True => write!(f, "'true'"),
838            TokenKind::False => write!(f, "'false'"),
839            TokenKind::Null => write!(f, "'null'"),
840            TokenKind::And => write!(f, "'and'"),
841            TokenKind::Or => write!(f, "'or'"),
842            TokenKind::Xor => write!(f, "'xor'"),
843            TokenKind::Break => write!(f, "'break'"),
844            TokenKind::Continue => write!(f, "'continue'"),
845            TokenKind::Switch => write!(f, "'switch'"),
846            TokenKind::Case => write!(f, "'case'"),
847            TokenKind::Default => write!(f, "'default'"),
848            TokenKind::EndIf => write!(f, "'endif'"),
849            TokenKind::EndWhile => write!(f, "'endwhile'"),
850            TokenKind::EndFor => write!(f, "'endfor'"),
851            TokenKind::EndForeach => write!(f, "'endforeach'"),
852            TokenKind::Throw => write!(f, "'throw'"),
853            TokenKind::Try => write!(f, "'try'"),
854            TokenKind::Catch => write!(f, "'catch'"),
855            TokenKind::Finally => write!(f, "'finally'"),
856            TokenKind::Instanceof => write!(f, "'instanceof'"),
857            TokenKind::Array => write!(f, "'array'"),
858            TokenKind::List => write!(f, "'list'"),
859            TokenKind::Goto => write!(f, "'goto'"),
860            TokenKind::Declare => write!(f, "'declare'"),
861            TokenKind::Unset => write!(f, "'unset'"),
862            TokenKind::Global => write!(f, "'global'"),
863            TokenKind::EndDeclare => write!(f, "'enddeclare'"),
864            TokenKind::EndSwitch => write!(f, "'endswitch'"),
865            TokenKind::Isset => write!(f, "'isset'"),
866            TokenKind::Empty => write!(f, "'empty'"),
867            TokenKind::Include => write!(f, "'include'"),
868            TokenKind::IncludeOnce => write!(f, "'include_once'"),
869            TokenKind::Require => write!(f, "'require'"),
870            TokenKind::RequireOnce => write!(f, "'require_once'"),
871            TokenKind::Eval => write!(f, "'eval'"),
872            TokenKind::Exit => write!(f, "'exit'"),
873            TokenKind::Die => write!(f, "'die'"),
874            TokenKind::Clone => write!(f, "'clone'"),
875            TokenKind::New => write!(f, "'new'"),
876            TokenKind::Class => write!(f, "'class'"),
877            TokenKind::Abstract => write!(f, "'abstract'"),
878            TokenKind::Final => write!(f, "'final'"),
879            TokenKind::Interface => write!(f, "'interface'"),
880            TokenKind::Trait => write!(f, "'trait'"),
881            TokenKind::Extends => write!(f, "'extends'"),
882            TokenKind::Implements => write!(f, "'implements'"),
883            TokenKind::Public => write!(f, "'public'"),
884            TokenKind::Protected => write!(f, "'protected'"),
885            TokenKind::Private => write!(f, "'private'"),
886            TokenKind::Static => write!(f, "'static'"),
887            TokenKind::Const => write!(f, "'const'"),
888            TokenKind::Fn_ => write!(f, "'fn'"),
889            TokenKind::Match_ => write!(f, "'match'"),
890            TokenKind::Namespace => write!(f, "'namespace'"),
891            TokenKind::Use => write!(f, "'use'"),
892            TokenKind::Readonly => write!(f, "'readonly'"),
893            TokenKind::Enum_ => write!(f, "'enum'"),
894            TokenKind::Yield_ => write!(f, "'yield'"),
895            TokenKind::From => write!(f, "'from'"),
896            TokenKind::Self_ => write!(f, "'self'"),
897            TokenKind::Parent_ => write!(f, "'parent'"),
898            TokenKind::MagicClass => write!(f, "'__CLASS__'"),
899            TokenKind::MagicDir => write!(f, "'__DIR__'"),
900            TokenKind::MagicFile => write!(f, "'__FILE__'"),
901            TokenKind::MagicFunction => write!(f, "'__FUNCTION__'"),
902            TokenKind::MagicLine => write!(f, "'__LINE__'"),
903            TokenKind::MagicMethod => write!(f, "'__METHOD__'"),
904            TokenKind::MagicNamespace => write!(f, "'__NAMESPACE__'"),
905            TokenKind::MagicTrait => write!(f, "'__TRAIT__'"),
906            TokenKind::MagicProperty => write!(f, "'__PROPERTY__'"),
907            TokenKind::HaltCompiler => write!(f, "'__halt_compiler'"),
908            TokenKind::OpenTag => write!(f, "'<?php'"),
909            TokenKind::CloseTag => write!(f, "'?>'"),
910            TokenKind::InlineHtml => write!(f, "inline HTML"),
911            TokenKind::Heredoc => write!(f, "heredoc"),
912            TokenKind::Nowdoc => write!(f, "nowdoc"),
913            TokenKind::InvalidNumericLiteral => write!(f, "invalid numeric literal"),
914            TokenKind::LineComment => write!(f, "line comment"),
915            TokenKind::HashComment => write!(f, "hash comment"),
916            TokenKind::BlockComment => write!(f, "block comment"),
917            TokenKind::DocComment => write!(f, "doc comment"),
918            TokenKind::Eof => write!(f, "end of file"),
919        }
920    }
921}
922
923#[cfg(test)]
924mod tests {
925    use super::*;
926
927    #[test]
928    fn test_resolve_keyword() {
929        assert_eq!(resolve_keyword("if"), Some(TokenKind::If));
930        assert_eq!(resolve_keyword("IF"), Some(TokenKind::If));
931        assert_eq!(resolve_keyword("If"), Some(TokenKind::If));
932        assert_eq!(resolve_keyword("function"), Some(TokenKind::Function));
933        assert_eq!(resolve_keyword("myFunc"), None);
934        assert_eq!(resolve_keyword("true"), Some(TokenKind::True));
935        assert_eq!(resolve_keyword("TRUE"), Some(TokenKind::True));
936        assert_eq!(resolve_keyword("null"), Some(TokenKind::Null));
937    }
938
939    #[test]
940    fn test_is_assignment_op() {
941        assert!(TokenKind::Equals.is_assignment_op());
942        assert!(TokenKind::PlusEquals.is_assignment_op());
943        assert!(TokenKind::DotEquals.is_assignment_op());
944        assert!(!TokenKind::Plus.is_assignment_op());
945        assert!(!TokenKind::EqualsEquals.is_assignment_op());
946    }
947}