Skip to main content

php_lexer/
token.rs

1#[repr(u8)]
2#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
3pub enum TokenKind {
4    // --- Literals ---
5    // Float: scientific notation (with or without decimal)
6    FloatLiteral,
7
8    // Float: decimal with digits on both sides
9    FloatLiteralSimple,
10
11    // Float: decimal starting with dot (.0)
12    FloatLiteralLeadingDot,
13
14    HexIntLiteral,
15
16    BinIntLiteral,
17
18    OctIntLiteralNew,
19
20    OctIntLiteral,
21
22    IntLiteral,
23
24    // String literals (with optional binary prefix b/B)
25    SingleQuotedString,
26
27    DoubleQuotedString,
28
29    BacktickString,
30
31    // --- Variables ---
32    Variable,
33    Dollar,
34
35    // --- Identifiers (keywords resolved from these) ---
36    Identifier,
37
38    // --- Operators ---
39    Plus,
40    Minus,
41    Star,
42    Slash,
43    Percent,
44    StarStar,
45    Dot,
46
47    Equals,
48    PlusEquals,
49    MinusEquals,
50    StarEquals,
51    SlashEquals,
52    PercentEquals,
53    StarStarEquals,
54    DotEquals,
55    AmpersandEquals,
56    PipeEquals,
57    CaretEquals,
58    ShiftLeftEquals,
59    ShiftRightEquals,
60    CoalesceEquals,
61
62    EqualsEquals,
63    BangEquals,
64    EqualsEqualsEquals,
65    BangEqualsEquals,
66    LessThan,
67    GreaterThan,
68    LessThanEquals,
69    GreaterThanEquals,
70    Spaceship,
71
72    AmpersandAmpersand,
73    PipePipe,
74    Bang,
75
76    Ampersand,
77    Pipe,
78    Caret,
79    Tilde,
80    ShiftLeft,
81    ShiftRight,
82
83    PlusPlus,
84    MinusMinus,
85
86    Question,
87    QuestionQuestion,
88    Colon,
89
90    FatArrow,
91
92    PipeArrow,
93
94    // --- Delimiters ---
95    LeftParen,
96    RightParen,
97    LeftBracket,
98    RightBracket,
99    LeftBrace,
100    RightBrace,
101    Semicolon,
102    Comma,
103
104    DoubleColon,
105
106    Arrow,
107
108    NullsafeArrow,
109
110    Backslash,
111
112    At,
113
114    HashBracket,
115
116    Ellipsis,
117
118    // --- Keywords (resolved from Identifier) ---
119    If,
120    Else,
121    ElseIf,
122    While,
123    Do,
124    For,
125    Foreach,
126    As,
127    Function,
128    Return,
129    Echo,
130    Print,
131    True,
132    False,
133    Null,
134    And,
135    Or,
136    Xor,
137    Break,
138    Continue,
139    Switch,
140    Case,
141    Default,
142    EndIf,
143    EndWhile,
144    EndFor,
145    EndForeach,
146    Throw,
147    Try,
148    Catch,
149    Finally,
150    Instanceof,
151    Array,
152    List,
153    Goto,
154    Declare,
155    Unset,
156    Global,
157    EndDeclare,
158    EndSwitch,
159    Isset,
160    Empty,
161    Include,
162    IncludeOnce,
163    Require,
164    RequireOnce,
165    Eval,
166    Exit,
167    Die,
168    Clone,
169    // OOP keywords
170    New,
171    Class,
172    Abstract,
173    Final,
174    Interface,
175    Trait,
176    Extends,
177    Implements,
178    Public,
179    Protected,
180    Private,
181    Static,
182    Const,
183    Fn_,
184    Match_,
185    Namespace,
186    Use,
187    Readonly,
188    Enum_,
189    Yield_,
190    From,
191    Self_,
192    Parent_,
193    // Magic constants
194    MagicClass,
195    MagicDir,
196    MagicFile,
197    MagicFunction,
198    MagicLine,
199    MagicMethod,
200    MagicNamespace,
201    MagicTrait,
202    MagicProperty,
203    HaltCompiler,
204
205    // --- PHP tags ---
206    OpenTag,
207
208    CloseTag,
209
210    // Inline HTML (produced by Lexer wrapper)
211    InlineHtml,
212
213    // Heredoc/Nowdoc (produced by Lexer wrapper)
214    Heredoc,
215    Nowdoc,
216
217    // Invalid numeric literal (e.g. 1_0_0_ with trailing underscore)
218    InvalidNumericLiteral,
219
220    // Comments (yielded by the lexer; filtered out by the parser into a side-table)
221    /// `// …` single-line slash comment
222    LineComment,
223    /// `# …` single-line hash comment
224    HashComment,
225    /// `/* … */` block comment
226    BlockComment,
227    /// `/** … */` doc-block comment
228    DocComment,
229
230    // End of file
231    Eof,
232}
233
234impl TokenKind {
235    /// Returns `true` for the four comment-token variants.
236    #[inline]
237    pub fn is_comment(self) -> bool {
238        matches!(
239            self,
240            TokenKind::LineComment
241                | TokenKind::HashComment
242                | TokenKind::BlockComment
243                | TokenKind::DocComment
244        )
245    }
246}
247
248impl TokenKind {
249    #[inline(always)]
250    pub fn is_assignment_op(self) -> bool {
251        // The assignment operators are contiguous in the enum definition:
252        // Equals..=CoalesceEquals. With #[repr(u8)], a single range check suffices.
253        (self as u8).wrapping_sub(TokenKind::Equals as u8)
254            <= (TokenKind::CoalesceEquals as u8 - TokenKind::Equals as u8)
255    }
256}
257
258/// Resolve a keyword from an identifier string. Returns the keyword TokenKind
259/// if the string is a keyword, or None if it's a plain identifier.
260pub fn resolve_keyword(text: &str) -> Option<TokenKind> {
261    // PHP keywords are case-insensitive; use eq_ignore_ascii_case to avoid allocation.
262    // Dispatch on length first to reduce comparisons.
263    let t = text;
264    match text.len() {
265        2 => {
266            if t.eq_ignore_ascii_case("if") {
267                return Some(TokenKind::If);
268            }
269            if t.eq_ignore_ascii_case("do") {
270                return Some(TokenKind::Do);
271            }
272            if t.eq_ignore_ascii_case("or") {
273                return Some(TokenKind::Or);
274            }
275            if t.eq_ignore_ascii_case("as") {
276                return Some(TokenKind::As);
277            }
278            if t.eq_ignore_ascii_case("fn") {
279                return Some(TokenKind::Fn_);
280            }
281        }
282        3 => {
283            if t.eq_ignore_ascii_case("for") {
284                return Some(TokenKind::For);
285            }
286            if t.eq_ignore_ascii_case("xor") {
287                return Some(TokenKind::Xor);
288            }
289            if t.eq_ignore_ascii_case("and") {
290                return Some(TokenKind::And);
291            }
292            if t.eq_ignore_ascii_case("new") {
293                return Some(TokenKind::New);
294            }
295            if t.eq_ignore_ascii_case("use") {
296                return Some(TokenKind::Use);
297            }
298            if t.eq_ignore_ascii_case("try") {
299                return Some(TokenKind::Try);
300            }
301            if t.eq_ignore_ascii_case("die") {
302                return Some(TokenKind::Die);
303            }
304        }
305        4 => {
306            if t.eq_ignore_ascii_case("else") {
307                return Some(TokenKind::Else);
308            }
309            if t.eq_ignore_ascii_case("echo") {
310                return Some(TokenKind::Echo);
311            }
312            if t.eq_ignore_ascii_case("true") {
313                return Some(TokenKind::True);
314            }
315            if t.eq_ignore_ascii_case("null") {
316                return Some(TokenKind::Null);
317            }
318            if t.eq_ignore_ascii_case("list") {
319                return Some(TokenKind::List);
320            }
321            if t.eq_ignore_ascii_case("goto") {
322                return Some(TokenKind::Goto);
323            }
324            if t.eq_ignore_ascii_case("case") {
325                return Some(TokenKind::Case);
326            }
327            if t.eq_ignore_ascii_case("self") {
328                return Some(TokenKind::Self_);
329            }
330            if t.eq_ignore_ascii_case("from") {
331                return Some(TokenKind::From);
332            }
333            if t.eq_ignore_ascii_case("enum") {
334                return Some(TokenKind::Enum_);
335            }
336            if t.eq_ignore_ascii_case("eval") {
337                return Some(TokenKind::Eval);
338            }
339            if t.eq_ignore_ascii_case("exit") {
340                return Some(TokenKind::Exit);
341            }
342        }
343        5 => {
344            if t.eq_ignore_ascii_case("while") {
345                return Some(TokenKind::While);
346            }
347            if t.eq_ignore_ascii_case("false") {
348                return Some(TokenKind::False);
349            }
350            if t.eq_ignore_ascii_case("array") {
351                return Some(TokenKind::Array);
352            }
353            if t.eq_ignore_ascii_case("unset") {
354                return Some(TokenKind::Unset);
355            }
356            if t.eq_ignore_ascii_case("isset") {
357                return Some(TokenKind::Isset);
358            }
359            if t.eq_ignore_ascii_case("empty") {
360                return Some(TokenKind::Empty);
361            }
362            if t.eq_ignore_ascii_case("print") {
363                return Some(TokenKind::Print);
364            }
365            if t.eq_ignore_ascii_case("throw") {
366                return Some(TokenKind::Throw);
367            }
368            if t.eq_ignore_ascii_case("catch") {
369                return Some(TokenKind::Catch);
370            }
371            if t.eq_ignore_ascii_case("break") {
372                return Some(TokenKind::Break);
373            }
374            if t.eq_ignore_ascii_case("yield") {
375                return Some(TokenKind::Yield_);
376            }
377            if t.eq_ignore_ascii_case("class") {
378                return Some(TokenKind::Class);
379            }
380            if t.eq_ignore_ascii_case("const") {
381                return Some(TokenKind::Const);
382            }
383            if t.eq_ignore_ascii_case("final") {
384                return Some(TokenKind::Final);
385            }
386            if t.eq_ignore_ascii_case("match") {
387                return Some(TokenKind::Match_);
388            }
389            if t.eq_ignore_ascii_case("trait") {
390                return Some(TokenKind::Trait);
391            }
392            if t.eq_ignore_ascii_case("clone") {
393                return Some(TokenKind::Clone);
394            }
395            if t.eq_ignore_ascii_case("endif") {
396                return Some(TokenKind::EndIf);
397            }
398        }
399        6 => {
400            if t.eq_ignore_ascii_case("elseif") {
401                return Some(TokenKind::ElseIf);
402            }
403            if t.eq_ignore_ascii_case("return") {
404                return Some(TokenKind::Return);
405            }
406            if t.eq_ignore_ascii_case("switch") {
407                return Some(TokenKind::Switch);
408            }
409            if t.eq_ignore_ascii_case("global") {
410                return Some(TokenKind::Global);
411            }
412            if t.eq_ignore_ascii_case("static") {
413                return Some(TokenKind::Static);
414            }
415            if t.eq_ignore_ascii_case("public") {
416                return Some(TokenKind::Public);
417            }
418            if t.eq_ignore_ascii_case("parent") {
419                return Some(TokenKind::Parent_);
420            }
421            if t.eq_ignore_ascii_case("endfor") {
422                return Some(TokenKind::EndFor);
423            }
424        }
425        7 => {
426            if t.eq_ignore_ascii_case("foreach") {
427                return Some(TokenKind::Foreach);
428            }
429            if t.eq_ignore_ascii_case("default") {
430                return Some(TokenKind::Default);
431            }
432            if t.eq_ignore_ascii_case("finally") {
433                return Some(TokenKind::Finally);
434            }
435            if t.eq_ignore_ascii_case("include") {
436                return Some(TokenKind::Include);
437            }
438            if t.eq_ignore_ascii_case("declare") {
439                return Some(TokenKind::Declare);
440            }
441            if t.eq_ignore_ascii_case("extends") {
442                return Some(TokenKind::Extends);
443            }
444            if t.eq_ignore_ascii_case("require") {
445                return Some(TokenKind::Require);
446            }
447            if t.eq_ignore_ascii_case("private") {
448                return Some(TokenKind::Private);
449            }
450            if t.eq_ignore_ascii_case("__dir__") {
451                return Some(TokenKind::MagicDir);
452            }
453        }
454        8 => {
455            if t.eq_ignore_ascii_case("function") {
456                return Some(TokenKind::Function);
457            }
458            if t.eq_ignore_ascii_case("abstract") {
459                return Some(TokenKind::Abstract);
460            }
461            if t.eq_ignore_ascii_case("readonly") {
462                return Some(TokenKind::Readonly);
463            }
464            if t.eq_ignore_ascii_case("continue") {
465                return Some(TokenKind::Continue);
466            }
467            if t.eq_ignore_ascii_case("endwhile") {
468                return Some(TokenKind::EndWhile);
469            }
470            if t.eq_ignore_ascii_case("__file__") {
471                return Some(TokenKind::MagicFile);
472            }
473            if t.eq_ignore_ascii_case("__line__") {
474                return Some(TokenKind::MagicLine);
475            }
476        }
477        9 => {
478            if t.eq_ignore_ascii_case("namespace") {
479                return Some(TokenKind::Namespace);
480            }
481            if t.eq_ignore_ascii_case("interface") {
482                return Some(TokenKind::Interface);
483            }
484            if t.eq_ignore_ascii_case("protected") {
485                return Some(TokenKind::Protected);
486            }
487            if t.eq_ignore_ascii_case("endswitch") {
488                return Some(TokenKind::EndSwitch);
489            }
490            if t.eq_ignore_ascii_case("__class__") {
491                return Some(TokenKind::MagicClass);
492            }
493            if t.eq_ignore_ascii_case("__trait__") {
494                return Some(TokenKind::MagicTrait);
495            }
496        }
497        10 => {
498            if t.eq_ignore_ascii_case("implements") {
499                return Some(TokenKind::Implements);
500            }
501            if t.eq_ignore_ascii_case("instanceof") {
502                return Some(TokenKind::Instanceof);
503            }
504            if t.eq_ignore_ascii_case("endforeach") {
505                return Some(TokenKind::EndForeach);
506            }
507            if t.eq_ignore_ascii_case("enddeclare") {
508                return Some(TokenKind::EndDeclare);
509            }
510            if t.eq_ignore_ascii_case("__method__") {
511                return Some(TokenKind::MagicMethod);
512            }
513        }
514        12 => {
515            if t.eq_ignore_ascii_case("include_once") {
516                return Some(TokenKind::IncludeOnce);
517            }
518            if t.eq_ignore_ascii_case("require_once") {
519                return Some(TokenKind::RequireOnce);
520            }
521            if t.eq_ignore_ascii_case("__function__") {
522                return Some(TokenKind::MagicFunction);
523            }
524            if t.eq_ignore_ascii_case("__property__") {
525                return Some(TokenKind::MagicProperty);
526            }
527        }
528        13 => {
529            if t.eq_ignore_ascii_case("__namespace__") {
530                return Some(TokenKind::MagicNamespace);
531            }
532        }
533        15 => {
534            if t.eq_ignore_ascii_case("__halt_compiler") {
535                return Some(TokenKind::HaltCompiler);
536            }
537        }
538        _ => {}
539    }
540    None
541}
542
543impl std::fmt::Display for TokenKind {
544    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
545        match self {
546            TokenKind::IntLiteral => write!(f, "integer"),
547            TokenKind::HexIntLiteral => write!(f, "hex integer"),
548            TokenKind::BinIntLiteral => write!(f, "binary integer"),
549            TokenKind::OctIntLiteral | TokenKind::OctIntLiteralNew => write!(f, "octal integer"),
550            TokenKind::FloatLiteral
551            | TokenKind::FloatLiteralSimple
552            | TokenKind::FloatLiteralLeadingDot => write!(f, "float"),
553            TokenKind::SingleQuotedString | TokenKind::DoubleQuotedString => write!(f, "string"),
554            TokenKind::BacktickString => write!(f, "backtick string"),
555            TokenKind::Variable => write!(f, "variable"),
556            TokenKind::Dollar => write!(f, "'$'"),
557            TokenKind::Identifier => write!(f, "identifier"),
558            TokenKind::Plus => write!(f, "'+'"),
559            TokenKind::Minus => write!(f, "'-'"),
560            TokenKind::Star => write!(f, "'*'"),
561            TokenKind::Slash => write!(f, "'/'"),
562            TokenKind::Percent => write!(f, "'%'"),
563            TokenKind::StarStar => write!(f, "'**'"),
564            TokenKind::Dot => write!(f, "'.'"),
565            TokenKind::Equals => write!(f, "'='"),
566            TokenKind::PlusEquals => write!(f, "'+='"),
567            TokenKind::MinusEquals => write!(f, "'-='"),
568            TokenKind::StarEquals => write!(f, "'*='"),
569            TokenKind::SlashEquals => write!(f, "'/='"),
570            TokenKind::PercentEquals => write!(f, "'%='"),
571            TokenKind::StarStarEquals => write!(f, "'**='"),
572            TokenKind::DotEquals => write!(f, "'.='"),
573            TokenKind::AmpersandEquals => write!(f, "'&='"),
574            TokenKind::PipeEquals => write!(f, "'|='"),
575            TokenKind::CaretEquals => write!(f, "'^='"),
576            TokenKind::ShiftLeftEquals => write!(f, "'<<='"),
577            TokenKind::ShiftRightEquals => write!(f, "'>>='"),
578            TokenKind::CoalesceEquals => write!(f, "'??='"),
579            TokenKind::EqualsEquals => write!(f, "'=='"),
580            TokenKind::BangEquals => write!(f, "'!='"),
581            TokenKind::EqualsEqualsEquals => write!(f, "'==='"),
582            TokenKind::BangEqualsEquals => write!(f, "'!=='"),
583            TokenKind::LessThan => write!(f, "'<'"),
584            TokenKind::GreaterThan => write!(f, "'>'"),
585            TokenKind::LessThanEquals => write!(f, "'<='"),
586            TokenKind::GreaterThanEquals => write!(f, "'>='"),
587            TokenKind::Spaceship => write!(f, "'<=>'"),
588            TokenKind::AmpersandAmpersand => write!(f, "'&&'"),
589            TokenKind::PipePipe => write!(f, "'||'"),
590            TokenKind::Bang => write!(f, "'!'"),
591            TokenKind::Ampersand => write!(f, "'&'"),
592            TokenKind::Pipe => write!(f, "'|'"),
593            TokenKind::Caret => write!(f, "'^'"),
594            TokenKind::Tilde => write!(f, "'~'"),
595            TokenKind::ShiftLeft => write!(f, "'<<'"),
596            TokenKind::ShiftRight => write!(f, "'>>'"),
597            TokenKind::PlusPlus => write!(f, "'++'"),
598            TokenKind::MinusMinus => write!(f, "'--'"),
599            TokenKind::Question => write!(f, "'?'"),
600            TokenKind::QuestionQuestion => write!(f, "'??'"),
601            TokenKind::Colon => write!(f, "':'"),
602            TokenKind::FatArrow => write!(f, "'=>'"),
603            TokenKind::PipeArrow => write!(f, "'|>'"),
604            TokenKind::LeftParen => write!(f, "'('"),
605            TokenKind::RightParen => write!(f, "')'"),
606            TokenKind::LeftBracket => write!(f, "'['"),
607            TokenKind::RightBracket => write!(f, "']'"),
608            TokenKind::LeftBrace => write!(f, "'{{'"),
609            TokenKind::RightBrace => write!(f, "'}}'"),
610            TokenKind::Semicolon => write!(f, "';'"),
611            TokenKind::Comma => write!(f, "','"),
612            TokenKind::DoubleColon => write!(f, "'::'"),
613            TokenKind::Arrow => write!(f, "'->'"),
614            TokenKind::NullsafeArrow => write!(f, "'?->'"),
615            TokenKind::Backslash => write!(f, "'\\'"),
616            TokenKind::At => write!(f, "'@'"),
617            TokenKind::HashBracket => write!(f, "'#['"),
618            TokenKind::Ellipsis => write!(f, "'...'"),
619            TokenKind::If => write!(f, "'if'"),
620            TokenKind::Else => write!(f, "'else'"),
621            TokenKind::ElseIf => write!(f, "'elseif'"),
622            TokenKind::While => write!(f, "'while'"),
623            TokenKind::Do => write!(f, "'do'"),
624            TokenKind::For => write!(f, "'for'"),
625            TokenKind::Foreach => write!(f, "'foreach'"),
626            TokenKind::As => write!(f, "'as'"),
627            TokenKind::Function => write!(f, "'function'"),
628            TokenKind::Return => write!(f, "'return'"),
629            TokenKind::Echo => write!(f, "'echo'"),
630            TokenKind::Print => write!(f, "'print'"),
631            TokenKind::True => write!(f, "'true'"),
632            TokenKind::False => write!(f, "'false'"),
633            TokenKind::Null => write!(f, "'null'"),
634            TokenKind::And => write!(f, "'and'"),
635            TokenKind::Or => write!(f, "'or'"),
636            TokenKind::Xor => write!(f, "'xor'"),
637            TokenKind::Break => write!(f, "'break'"),
638            TokenKind::Continue => write!(f, "'continue'"),
639            TokenKind::Switch => write!(f, "'switch'"),
640            TokenKind::Case => write!(f, "'case'"),
641            TokenKind::Default => write!(f, "'default'"),
642            TokenKind::EndIf => write!(f, "'endif'"),
643            TokenKind::EndWhile => write!(f, "'endwhile'"),
644            TokenKind::EndFor => write!(f, "'endfor'"),
645            TokenKind::EndForeach => write!(f, "'endforeach'"),
646            TokenKind::Throw => write!(f, "'throw'"),
647            TokenKind::Try => write!(f, "'try'"),
648            TokenKind::Catch => write!(f, "'catch'"),
649            TokenKind::Finally => write!(f, "'finally'"),
650            TokenKind::Instanceof => write!(f, "'instanceof'"),
651            TokenKind::Array => write!(f, "'array'"),
652            TokenKind::List => write!(f, "'list'"),
653            TokenKind::Goto => write!(f, "'goto'"),
654            TokenKind::Declare => write!(f, "'declare'"),
655            TokenKind::Unset => write!(f, "'unset'"),
656            TokenKind::Global => write!(f, "'global'"),
657            TokenKind::EndDeclare => write!(f, "'enddeclare'"),
658            TokenKind::EndSwitch => write!(f, "'endswitch'"),
659            TokenKind::Isset => write!(f, "'isset'"),
660            TokenKind::Empty => write!(f, "'empty'"),
661            TokenKind::Include => write!(f, "'include'"),
662            TokenKind::IncludeOnce => write!(f, "'include_once'"),
663            TokenKind::Require => write!(f, "'require'"),
664            TokenKind::RequireOnce => write!(f, "'require_once'"),
665            TokenKind::Eval => write!(f, "'eval'"),
666            TokenKind::Exit => write!(f, "'exit'"),
667            TokenKind::Die => write!(f, "'die'"),
668            TokenKind::Clone => write!(f, "'clone'"),
669            TokenKind::New => write!(f, "'new'"),
670            TokenKind::Class => write!(f, "'class'"),
671            TokenKind::Abstract => write!(f, "'abstract'"),
672            TokenKind::Final => write!(f, "'final'"),
673            TokenKind::Interface => write!(f, "'interface'"),
674            TokenKind::Trait => write!(f, "'trait'"),
675            TokenKind::Extends => write!(f, "'extends'"),
676            TokenKind::Implements => write!(f, "'implements'"),
677            TokenKind::Public => write!(f, "'public'"),
678            TokenKind::Protected => write!(f, "'protected'"),
679            TokenKind::Private => write!(f, "'private'"),
680            TokenKind::Static => write!(f, "'static'"),
681            TokenKind::Const => write!(f, "'const'"),
682            TokenKind::Fn_ => write!(f, "'fn'"),
683            TokenKind::Match_ => write!(f, "'match'"),
684            TokenKind::Namespace => write!(f, "'namespace'"),
685            TokenKind::Use => write!(f, "'use'"),
686            TokenKind::Readonly => write!(f, "'readonly'"),
687            TokenKind::Enum_ => write!(f, "'enum'"),
688            TokenKind::Yield_ => write!(f, "'yield'"),
689            TokenKind::From => write!(f, "'from'"),
690            TokenKind::Self_ => write!(f, "'self'"),
691            TokenKind::Parent_ => write!(f, "'parent'"),
692            TokenKind::MagicClass => write!(f, "'__CLASS__'"),
693            TokenKind::MagicDir => write!(f, "'__DIR__'"),
694            TokenKind::MagicFile => write!(f, "'__FILE__'"),
695            TokenKind::MagicFunction => write!(f, "'__FUNCTION__'"),
696            TokenKind::MagicLine => write!(f, "'__LINE__'"),
697            TokenKind::MagicMethod => write!(f, "'__METHOD__'"),
698            TokenKind::MagicNamespace => write!(f, "'__NAMESPACE__'"),
699            TokenKind::MagicTrait => write!(f, "'__TRAIT__'"),
700            TokenKind::MagicProperty => write!(f, "'__PROPERTY__'"),
701            TokenKind::HaltCompiler => write!(f, "'__halt_compiler'"),
702            TokenKind::OpenTag => write!(f, "'<?php'"),
703            TokenKind::CloseTag => write!(f, "'?>'"),
704            TokenKind::InlineHtml => write!(f, "inline HTML"),
705            TokenKind::Heredoc => write!(f, "heredoc"),
706            TokenKind::Nowdoc => write!(f, "nowdoc"),
707            TokenKind::InvalidNumericLiteral => write!(f, "invalid numeric literal"),
708            TokenKind::LineComment => write!(f, "line comment"),
709            TokenKind::HashComment => write!(f, "hash comment"),
710            TokenKind::BlockComment => write!(f, "block comment"),
711            TokenKind::DocComment => write!(f, "doc comment"),
712            TokenKind::Eof => write!(f, "end of file"),
713        }
714    }
715}
716
717#[cfg(test)]
718mod tests {
719    use super::*;
720
721    #[test]
722    fn test_resolve_keyword() {
723        assert_eq!(resolve_keyword("if"), Some(TokenKind::If));
724        assert_eq!(resolve_keyword("IF"), Some(TokenKind::If));
725        assert_eq!(resolve_keyword("If"), Some(TokenKind::If));
726        assert_eq!(resolve_keyword("function"), Some(TokenKind::Function));
727        assert_eq!(resolve_keyword("myFunc"), None);
728        assert_eq!(resolve_keyword("true"), Some(TokenKind::True));
729        assert_eq!(resolve_keyword("TRUE"), Some(TokenKind::True));
730        assert_eq!(resolve_keyword("null"), Some(TokenKind::Null));
731    }
732
733    #[test]
734    fn test_is_assignment_op() {
735        assert!(TokenKind::Equals.is_assignment_op());
736        assert!(TokenKind::PlusEquals.is_assignment_op());
737        assert!(TokenKind::DotEquals.is_assignment_op());
738        assert!(!TokenKind::Plus.is_assignment_op());
739        assert!(!TokenKind::EqualsEquals.is_assignment_op());
740    }
741}