Skip to main content

php_lexer/
token.rs

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