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    #[inline(always)]
459    pub fn is_assignment_op(self) -> bool {
460        // The assignment operators are contiguous in the enum definition:
461        // Equals..=CoalesceEquals. With #[repr(u8)], a single range check suffices.
462        (self as u8).wrapping_sub(TokenKind::Equals as u8)
463            <= (TokenKind::CoalesceEquals as u8 - TokenKind::Equals as u8)
464    }
465}
466
467/// Resolve a keyword from an identifier string. Returns the keyword TokenKind
468/// if the string is a keyword, or None if it's a plain identifier.
469pub fn resolve_keyword(text: &str) -> Option<TokenKind> {
470    // PHP keywords are case-insensitive; use eq_ignore_ascii_case to avoid allocation.
471    // Dispatch on length first to reduce comparisons.
472    let t = text;
473    match text.len() {
474        2 => {
475            if t.eq_ignore_ascii_case("if") {
476                return Some(TokenKind::If);
477            }
478            if t.eq_ignore_ascii_case("do") {
479                return Some(TokenKind::Do);
480            }
481            if t.eq_ignore_ascii_case("or") {
482                return Some(TokenKind::Or);
483            }
484            if t.eq_ignore_ascii_case("as") {
485                return Some(TokenKind::As);
486            }
487            if t.eq_ignore_ascii_case("fn") {
488                return Some(TokenKind::Fn_);
489            }
490        }
491        3 => {
492            if t.eq_ignore_ascii_case("for") {
493                return Some(TokenKind::For);
494            }
495            if t.eq_ignore_ascii_case("xor") {
496                return Some(TokenKind::Xor);
497            }
498            if t.eq_ignore_ascii_case("and") {
499                return Some(TokenKind::And);
500            }
501            if t.eq_ignore_ascii_case("new") {
502                return Some(TokenKind::New);
503            }
504            if t.eq_ignore_ascii_case("use") {
505                return Some(TokenKind::Use);
506            }
507            if t.eq_ignore_ascii_case("try") {
508                return Some(TokenKind::Try);
509            }
510            if t.eq_ignore_ascii_case("die") {
511                return Some(TokenKind::Die);
512            }
513        }
514        4 => {
515            if t.eq_ignore_ascii_case("else") {
516                return Some(TokenKind::Else);
517            }
518            if t.eq_ignore_ascii_case("echo") {
519                return Some(TokenKind::Echo);
520            }
521            if t.eq_ignore_ascii_case("true") {
522                return Some(TokenKind::True);
523            }
524            if t.eq_ignore_ascii_case("null") {
525                return Some(TokenKind::Null);
526            }
527            if t.eq_ignore_ascii_case("list") {
528                return Some(TokenKind::List);
529            }
530            if t.eq_ignore_ascii_case("goto") {
531                return Some(TokenKind::Goto);
532            }
533            if t.eq_ignore_ascii_case("case") {
534                return Some(TokenKind::Case);
535            }
536            if t.eq_ignore_ascii_case("self") {
537                return Some(TokenKind::Self_);
538            }
539            if t.eq_ignore_ascii_case("from") {
540                return Some(TokenKind::From);
541            }
542            if t.eq_ignore_ascii_case("enum") {
543                return Some(TokenKind::Enum_);
544            }
545            if t.eq_ignore_ascii_case("eval") {
546                return Some(TokenKind::Eval);
547            }
548            if t.eq_ignore_ascii_case("exit") {
549                return Some(TokenKind::Exit);
550            }
551        }
552        5 => {
553            if t.eq_ignore_ascii_case("while") {
554                return Some(TokenKind::While);
555            }
556            if t.eq_ignore_ascii_case("false") {
557                return Some(TokenKind::False);
558            }
559            if t.eq_ignore_ascii_case("array") {
560                return Some(TokenKind::Array);
561            }
562            if t.eq_ignore_ascii_case("unset") {
563                return Some(TokenKind::Unset);
564            }
565            if t.eq_ignore_ascii_case("isset") {
566                return Some(TokenKind::Isset);
567            }
568            if t.eq_ignore_ascii_case("empty") {
569                return Some(TokenKind::Empty);
570            }
571            if t.eq_ignore_ascii_case("print") {
572                return Some(TokenKind::Print);
573            }
574            if t.eq_ignore_ascii_case("throw") {
575                return Some(TokenKind::Throw);
576            }
577            if t.eq_ignore_ascii_case("catch") {
578                return Some(TokenKind::Catch);
579            }
580            if t.eq_ignore_ascii_case("break") {
581                return Some(TokenKind::Break);
582            }
583            if t.eq_ignore_ascii_case("yield") {
584                return Some(TokenKind::Yield_);
585            }
586            if t.eq_ignore_ascii_case("class") {
587                return Some(TokenKind::Class);
588            }
589            if t.eq_ignore_ascii_case("const") {
590                return Some(TokenKind::Const);
591            }
592            if t.eq_ignore_ascii_case("final") {
593                return Some(TokenKind::Final);
594            }
595            if t.eq_ignore_ascii_case("match") {
596                return Some(TokenKind::Match_);
597            }
598            if t.eq_ignore_ascii_case("trait") {
599                return Some(TokenKind::Trait);
600            }
601            if t.eq_ignore_ascii_case("clone") {
602                return Some(TokenKind::Clone);
603            }
604            if t.eq_ignore_ascii_case("endif") {
605                return Some(TokenKind::EndIf);
606            }
607        }
608        6 => {
609            if t.eq_ignore_ascii_case("elseif") {
610                return Some(TokenKind::ElseIf);
611            }
612            if t.eq_ignore_ascii_case("return") {
613                return Some(TokenKind::Return);
614            }
615            if t.eq_ignore_ascii_case("switch") {
616                return Some(TokenKind::Switch);
617            }
618            if t.eq_ignore_ascii_case("global") {
619                return Some(TokenKind::Global);
620            }
621            if t.eq_ignore_ascii_case("static") {
622                return Some(TokenKind::Static);
623            }
624            if t.eq_ignore_ascii_case("public") {
625                return Some(TokenKind::Public);
626            }
627            if t.eq_ignore_ascii_case("parent") {
628                return Some(TokenKind::Parent_);
629            }
630            if t.eq_ignore_ascii_case("endfor") {
631                return Some(TokenKind::EndFor);
632            }
633        }
634        7 => {
635            if t.eq_ignore_ascii_case("foreach") {
636                return Some(TokenKind::Foreach);
637            }
638            if t.eq_ignore_ascii_case("default") {
639                return Some(TokenKind::Default);
640            }
641            if t.eq_ignore_ascii_case("finally") {
642                return Some(TokenKind::Finally);
643            }
644            if t.eq_ignore_ascii_case("include") {
645                return Some(TokenKind::Include);
646            }
647            if t.eq_ignore_ascii_case("declare") {
648                return Some(TokenKind::Declare);
649            }
650            if t.eq_ignore_ascii_case("extends") {
651                return Some(TokenKind::Extends);
652            }
653            if t.eq_ignore_ascii_case("require") {
654                return Some(TokenKind::Require);
655            }
656            if t.eq_ignore_ascii_case("private") {
657                return Some(TokenKind::Private);
658            }
659            if t.eq_ignore_ascii_case("__dir__") {
660                return Some(TokenKind::MagicDir);
661            }
662        }
663        8 => {
664            if t.eq_ignore_ascii_case("function") {
665                return Some(TokenKind::Function);
666            }
667            if t.eq_ignore_ascii_case("abstract") {
668                return Some(TokenKind::Abstract);
669            }
670            if t.eq_ignore_ascii_case("readonly") {
671                return Some(TokenKind::Readonly);
672            }
673            if t.eq_ignore_ascii_case("continue") {
674                return Some(TokenKind::Continue);
675            }
676            if t.eq_ignore_ascii_case("endwhile") {
677                return Some(TokenKind::EndWhile);
678            }
679            if t.eq_ignore_ascii_case("__file__") {
680                return Some(TokenKind::MagicFile);
681            }
682            if t.eq_ignore_ascii_case("__line__") {
683                return Some(TokenKind::MagicLine);
684            }
685        }
686        9 => {
687            if t.eq_ignore_ascii_case("namespace") {
688                return Some(TokenKind::Namespace);
689            }
690            if t.eq_ignore_ascii_case("interface") {
691                return Some(TokenKind::Interface);
692            }
693            if t.eq_ignore_ascii_case("protected") {
694                return Some(TokenKind::Protected);
695            }
696            if t.eq_ignore_ascii_case("endswitch") {
697                return Some(TokenKind::EndSwitch);
698            }
699            if t.eq_ignore_ascii_case("__class__") {
700                return Some(TokenKind::MagicClass);
701            }
702            if t.eq_ignore_ascii_case("__trait__") {
703                return Some(TokenKind::MagicTrait);
704            }
705        }
706        10 => {
707            if t.eq_ignore_ascii_case("implements") {
708                return Some(TokenKind::Implements);
709            }
710            if t.eq_ignore_ascii_case("instanceof") {
711                return Some(TokenKind::Instanceof);
712            }
713            if t.eq_ignore_ascii_case("endforeach") {
714                return Some(TokenKind::EndForeach);
715            }
716            if t.eq_ignore_ascii_case("enddeclare") {
717                return Some(TokenKind::EndDeclare);
718            }
719            if t.eq_ignore_ascii_case("__method__") {
720                return Some(TokenKind::MagicMethod);
721            }
722        }
723        12 => {
724            if t.eq_ignore_ascii_case("include_once") {
725                return Some(TokenKind::IncludeOnce);
726            }
727            if t.eq_ignore_ascii_case("require_once") {
728                return Some(TokenKind::RequireOnce);
729            }
730            if t.eq_ignore_ascii_case("__function__") {
731                return Some(TokenKind::MagicFunction);
732            }
733            if t.eq_ignore_ascii_case("__property__") {
734                return Some(TokenKind::MagicProperty);
735            }
736        }
737        13 if t.eq_ignore_ascii_case("__namespace__") => {
738            return Some(TokenKind::MagicNamespace);
739        }
740        15 if t.eq_ignore_ascii_case("__halt_compiler") => {
741            return Some(TokenKind::HaltCompiler);
742        }
743        _ => {}
744    }
745    None
746}
747
748impl std::fmt::Display for TokenKind {
749    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
750        match self {
751            TokenKind::IntLiteral => write!(f, "integer"),
752            TokenKind::HexIntLiteral => write!(f, "hex integer"),
753            TokenKind::BinIntLiteral => write!(f, "binary integer"),
754            TokenKind::OctIntLiteral | TokenKind::OctIntLiteralNew => write!(f, "octal integer"),
755            TokenKind::FloatLiteral
756            | TokenKind::FloatLiteralSimple
757            | TokenKind::FloatLiteralLeadingDot => write!(f, "float"),
758            TokenKind::SingleQuotedString | TokenKind::DoubleQuotedString => write!(f, "string"),
759            TokenKind::BacktickString => write!(f, "backtick string"),
760            TokenKind::Variable => write!(f, "variable"),
761            TokenKind::Dollar => write!(f, "'$'"),
762            TokenKind::Identifier => write!(f, "identifier"),
763            TokenKind::Plus => write!(f, "'+'"),
764            TokenKind::Minus => write!(f, "'-'"),
765            TokenKind::Star => write!(f, "'*'"),
766            TokenKind::Slash => write!(f, "'/'"),
767            TokenKind::Percent => write!(f, "'%'"),
768            TokenKind::StarStar => write!(f, "'**'"),
769            TokenKind::Dot => write!(f, "'.'"),
770            TokenKind::Equals => write!(f, "'='"),
771            TokenKind::PlusEquals => write!(f, "'+='"),
772            TokenKind::MinusEquals => write!(f, "'-='"),
773            TokenKind::StarEquals => write!(f, "'*='"),
774            TokenKind::SlashEquals => write!(f, "'/='"),
775            TokenKind::PercentEquals => write!(f, "'%='"),
776            TokenKind::StarStarEquals => write!(f, "'**='"),
777            TokenKind::DotEquals => write!(f, "'.='"),
778            TokenKind::AmpersandEquals => write!(f, "'&='"),
779            TokenKind::PipeEquals => write!(f, "'|='"),
780            TokenKind::CaretEquals => write!(f, "'^='"),
781            TokenKind::ShiftLeftEquals => write!(f, "'<<='"),
782            TokenKind::ShiftRightEquals => write!(f, "'>>='"),
783            TokenKind::CoalesceEquals => write!(f, "'??='"),
784            TokenKind::EqualsEquals => write!(f, "'=='"),
785            TokenKind::BangEquals => write!(f, "'!='"),
786            TokenKind::EqualsEqualsEquals => write!(f, "'==='"),
787            TokenKind::BangEqualsEquals => write!(f, "'!=='"),
788            TokenKind::LessThan => write!(f, "'<'"),
789            TokenKind::GreaterThan => write!(f, "'>'"),
790            TokenKind::LessThanEquals => write!(f, "'<='"),
791            TokenKind::GreaterThanEquals => write!(f, "'>='"),
792            TokenKind::Spaceship => write!(f, "'<=>'"),
793            TokenKind::AmpersandAmpersand => write!(f, "'&&'"),
794            TokenKind::PipePipe => write!(f, "'||'"),
795            TokenKind::Bang => write!(f, "'!'"),
796            TokenKind::Ampersand => write!(f, "'&'"),
797            TokenKind::Pipe => write!(f, "'|'"),
798            TokenKind::Caret => write!(f, "'^'"),
799            TokenKind::Tilde => write!(f, "'~'"),
800            TokenKind::ShiftLeft => write!(f, "'<<'"),
801            TokenKind::ShiftRight => write!(f, "'>>'"),
802            TokenKind::PlusPlus => write!(f, "'++'"),
803            TokenKind::MinusMinus => write!(f, "'--'"),
804            TokenKind::Question => write!(f, "'?'"),
805            TokenKind::QuestionQuestion => write!(f, "'??'"),
806            TokenKind::Colon => write!(f, "':'"),
807            TokenKind::FatArrow => write!(f, "'=>'"),
808            TokenKind::PipeArrow => write!(f, "'|>'"),
809            TokenKind::LeftParen => write!(f, "'('"),
810            TokenKind::RightParen => write!(f, "')'"),
811            TokenKind::LeftBracket => write!(f, "'['"),
812            TokenKind::RightBracket => write!(f, "']'"),
813            TokenKind::LeftBrace => write!(f, "'{{'"),
814            TokenKind::RightBrace => write!(f, "'}}'"),
815            TokenKind::Semicolon => write!(f, "';'"),
816            TokenKind::Comma => write!(f, "','"),
817            TokenKind::DoubleColon => write!(f, "'::'"),
818            TokenKind::Arrow => write!(f, "'->'"),
819            TokenKind::NullsafeArrow => write!(f, "'?->'"),
820            TokenKind::Backslash => write!(f, "'\\'"),
821            TokenKind::At => write!(f, "'@'"),
822            TokenKind::HashBracket => write!(f, "'#['"),
823            TokenKind::Ellipsis => write!(f, "'...'"),
824            TokenKind::If => write!(f, "'if'"),
825            TokenKind::Else => write!(f, "'else'"),
826            TokenKind::ElseIf => write!(f, "'elseif'"),
827            TokenKind::While => write!(f, "'while'"),
828            TokenKind::Do => write!(f, "'do'"),
829            TokenKind::For => write!(f, "'for'"),
830            TokenKind::Foreach => write!(f, "'foreach'"),
831            TokenKind::As => write!(f, "'as'"),
832            TokenKind::Function => write!(f, "'function'"),
833            TokenKind::Return => write!(f, "'return'"),
834            TokenKind::Echo => write!(f, "'echo'"),
835            TokenKind::Print => write!(f, "'print'"),
836            TokenKind::True => write!(f, "'true'"),
837            TokenKind::False => write!(f, "'false'"),
838            TokenKind::Null => write!(f, "'null'"),
839            TokenKind::And => write!(f, "'and'"),
840            TokenKind::Or => write!(f, "'or'"),
841            TokenKind::Xor => write!(f, "'xor'"),
842            TokenKind::Break => write!(f, "'break'"),
843            TokenKind::Continue => write!(f, "'continue'"),
844            TokenKind::Switch => write!(f, "'switch'"),
845            TokenKind::Case => write!(f, "'case'"),
846            TokenKind::Default => write!(f, "'default'"),
847            TokenKind::EndIf => write!(f, "'endif'"),
848            TokenKind::EndWhile => write!(f, "'endwhile'"),
849            TokenKind::EndFor => write!(f, "'endfor'"),
850            TokenKind::EndForeach => write!(f, "'endforeach'"),
851            TokenKind::Throw => write!(f, "'throw'"),
852            TokenKind::Try => write!(f, "'try'"),
853            TokenKind::Catch => write!(f, "'catch'"),
854            TokenKind::Finally => write!(f, "'finally'"),
855            TokenKind::Instanceof => write!(f, "'instanceof'"),
856            TokenKind::Array => write!(f, "'array'"),
857            TokenKind::List => write!(f, "'list'"),
858            TokenKind::Goto => write!(f, "'goto'"),
859            TokenKind::Declare => write!(f, "'declare'"),
860            TokenKind::Unset => write!(f, "'unset'"),
861            TokenKind::Global => write!(f, "'global'"),
862            TokenKind::EndDeclare => write!(f, "'enddeclare'"),
863            TokenKind::EndSwitch => write!(f, "'endswitch'"),
864            TokenKind::Isset => write!(f, "'isset'"),
865            TokenKind::Empty => write!(f, "'empty'"),
866            TokenKind::Include => write!(f, "'include'"),
867            TokenKind::IncludeOnce => write!(f, "'include_once'"),
868            TokenKind::Require => write!(f, "'require'"),
869            TokenKind::RequireOnce => write!(f, "'require_once'"),
870            TokenKind::Eval => write!(f, "'eval'"),
871            TokenKind::Exit => write!(f, "'exit'"),
872            TokenKind::Die => write!(f, "'die'"),
873            TokenKind::Clone => write!(f, "'clone'"),
874            TokenKind::New => write!(f, "'new'"),
875            TokenKind::Class => write!(f, "'class'"),
876            TokenKind::Abstract => write!(f, "'abstract'"),
877            TokenKind::Final => write!(f, "'final'"),
878            TokenKind::Interface => write!(f, "'interface'"),
879            TokenKind::Trait => write!(f, "'trait'"),
880            TokenKind::Extends => write!(f, "'extends'"),
881            TokenKind::Implements => write!(f, "'implements'"),
882            TokenKind::Public => write!(f, "'public'"),
883            TokenKind::Protected => write!(f, "'protected'"),
884            TokenKind::Private => write!(f, "'private'"),
885            TokenKind::Static => write!(f, "'static'"),
886            TokenKind::Const => write!(f, "'const'"),
887            TokenKind::Fn_ => write!(f, "'fn'"),
888            TokenKind::Match_ => write!(f, "'match'"),
889            TokenKind::Namespace => write!(f, "'namespace'"),
890            TokenKind::Use => write!(f, "'use'"),
891            TokenKind::Readonly => write!(f, "'readonly'"),
892            TokenKind::Enum_ => write!(f, "'enum'"),
893            TokenKind::Yield_ => write!(f, "'yield'"),
894            TokenKind::From => write!(f, "'from'"),
895            TokenKind::Self_ => write!(f, "'self'"),
896            TokenKind::Parent_ => write!(f, "'parent'"),
897            TokenKind::MagicClass => write!(f, "'__CLASS__'"),
898            TokenKind::MagicDir => write!(f, "'__DIR__'"),
899            TokenKind::MagicFile => write!(f, "'__FILE__'"),
900            TokenKind::MagicFunction => write!(f, "'__FUNCTION__'"),
901            TokenKind::MagicLine => write!(f, "'__LINE__'"),
902            TokenKind::MagicMethod => write!(f, "'__METHOD__'"),
903            TokenKind::MagicNamespace => write!(f, "'__NAMESPACE__'"),
904            TokenKind::MagicTrait => write!(f, "'__TRAIT__'"),
905            TokenKind::MagicProperty => write!(f, "'__PROPERTY__'"),
906            TokenKind::HaltCompiler => write!(f, "'__halt_compiler'"),
907            TokenKind::OpenTag => write!(f, "'<?php'"),
908            TokenKind::CloseTag => write!(f, "'?>'"),
909            TokenKind::InlineHtml => write!(f, "inline HTML"),
910            TokenKind::Heredoc => write!(f, "heredoc"),
911            TokenKind::Nowdoc => write!(f, "nowdoc"),
912            TokenKind::InvalidNumericLiteral => write!(f, "invalid numeric literal"),
913            TokenKind::LineComment => write!(f, "line comment"),
914            TokenKind::HashComment => write!(f, "hash comment"),
915            TokenKind::BlockComment => write!(f, "block comment"),
916            TokenKind::DocComment => write!(f, "doc comment"),
917            TokenKind::Eof => write!(f, "end of file"),
918        }
919    }
920}
921
922#[cfg(test)]
923mod tests {
924    use super::*;
925
926    #[test]
927    fn test_resolve_keyword() {
928        assert_eq!(resolve_keyword("if"), Some(TokenKind::If));
929        assert_eq!(resolve_keyword("IF"), Some(TokenKind::If));
930        assert_eq!(resolve_keyword("If"), Some(TokenKind::If));
931        assert_eq!(resolve_keyword("function"), Some(TokenKind::Function));
932        assert_eq!(resolve_keyword("myFunc"), None);
933        assert_eq!(resolve_keyword("true"), Some(TokenKind::True));
934        assert_eq!(resolve_keyword("TRUE"), Some(TokenKind::True));
935        assert_eq!(resolve_keyword("null"), Some(TokenKind::Null));
936    }
937
938    #[test]
939    fn test_is_assignment_op() {
940        assert!(TokenKind::Equals.is_assignment_op());
941        assert!(TokenKind::PlusEquals.is_assignment_op());
942        assert!(TokenKind::DotEquals.is_assignment_op());
943        assert!(!TokenKind::Plus.is_assignment_op());
944        assert!(!TokenKind::EqualsEquals.is_assignment_op());
945    }
946}