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    // End of file
221    Eof,
222}
223
224impl TokenKind {
225    #[inline(always)]
226    pub fn is_assignment_op(self) -> bool {
227        // The assignment operators are contiguous in the enum definition:
228        // Equals..=CoalesceEquals. With #[repr(u8)], a single range check suffices.
229        (self as u8).wrapping_sub(TokenKind::Equals as u8)
230            <= (TokenKind::CoalesceEquals as u8 - TokenKind::Equals as u8)
231    }
232}
233
234/// Resolve a keyword from an identifier string. Returns the keyword TokenKind
235/// if the string is a keyword, or None if it's a plain identifier.
236pub fn resolve_keyword(text: &str) -> Option<TokenKind> {
237    // PHP keywords are case-insensitive; use eq_ignore_ascii_case to avoid allocation.
238    // Dispatch on length first to reduce comparisons.
239    let t = text;
240    match text.len() {
241        2 => {
242            if t.eq_ignore_ascii_case("if") {
243                return Some(TokenKind::If);
244            }
245            if t.eq_ignore_ascii_case("do") {
246                return Some(TokenKind::Do);
247            }
248            if t.eq_ignore_ascii_case("or") {
249                return Some(TokenKind::Or);
250            }
251            if t.eq_ignore_ascii_case("as") {
252                return Some(TokenKind::As);
253            }
254            if t.eq_ignore_ascii_case("fn") {
255                return Some(TokenKind::Fn_);
256            }
257        }
258        3 => {
259            if t.eq_ignore_ascii_case("for") {
260                return Some(TokenKind::For);
261            }
262            if t.eq_ignore_ascii_case("xor") {
263                return Some(TokenKind::Xor);
264            }
265            if t.eq_ignore_ascii_case("and") {
266                return Some(TokenKind::And);
267            }
268            if t.eq_ignore_ascii_case("new") {
269                return Some(TokenKind::New);
270            }
271            if t.eq_ignore_ascii_case("use") {
272                return Some(TokenKind::Use);
273            }
274            if t.eq_ignore_ascii_case("try") {
275                return Some(TokenKind::Try);
276            }
277            if t.eq_ignore_ascii_case("die") {
278                return Some(TokenKind::Die);
279            }
280        }
281        4 => {
282            if t.eq_ignore_ascii_case("else") {
283                return Some(TokenKind::Else);
284            }
285            if t.eq_ignore_ascii_case("echo") {
286                return Some(TokenKind::Echo);
287            }
288            if t.eq_ignore_ascii_case("true") {
289                return Some(TokenKind::True);
290            }
291            if t.eq_ignore_ascii_case("null") {
292                return Some(TokenKind::Null);
293            }
294            if t.eq_ignore_ascii_case("list") {
295                return Some(TokenKind::List);
296            }
297            if t.eq_ignore_ascii_case("goto") {
298                return Some(TokenKind::Goto);
299            }
300            if t.eq_ignore_ascii_case("case") {
301                return Some(TokenKind::Case);
302            }
303            if t.eq_ignore_ascii_case("self") {
304                return Some(TokenKind::Self_);
305            }
306            if t.eq_ignore_ascii_case("from") {
307                return Some(TokenKind::From);
308            }
309            if t.eq_ignore_ascii_case("enum") {
310                return Some(TokenKind::Enum_);
311            }
312            if t.eq_ignore_ascii_case("eval") {
313                return Some(TokenKind::Eval);
314            }
315            if t.eq_ignore_ascii_case("exit") {
316                return Some(TokenKind::Exit);
317            }
318        }
319        5 => {
320            if t.eq_ignore_ascii_case("while") {
321                return Some(TokenKind::While);
322            }
323            if t.eq_ignore_ascii_case("false") {
324                return Some(TokenKind::False);
325            }
326            if t.eq_ignore_ascii_case("array") {
327                return Some(TokenKind::Array);
328            }
329            if t.eq_ignore_ascii_case("unset") {
330                return Some(TokenKind::Unset);
331            }
332            if t.eq_ignore_ascii_case("isset") {
333                return Some(TokenKind::Isset);
334            }
335            if t.eq_ignore_ascii_case("empty") {
336                return Some(TokenKind::Empty);
337            }
338            if t.eq_ignore_ascii_case("print") {
339                return Some(TokenKind::Print);
340            }
341            if t.eq_ignore_ascii_case("throw") {
342                return Some(TokenKind::Throw);
343            }
344            if t.eq_ignore_ascii_case("catch") {
345                return Some(TokenKind::Catch);
346            }
347            if t.eq_ignore_ascii_case("break") {
348                return Some(TokenKind::Break);
349            }
350            if t.eq_ignore_ascii_case("yield") {
351                return Some(TokenKind::Yield_);
352            }
353            if t.eq_ignore_ascii_case("class") {
354                return Some(TokenKind::Class);
355            }
356            if t.eq_ignore_ascii_case("const") {
357                return Some(TokenKind::Const);
358            }
359            if t.eq_ignore_ascii_case("final") {
360                return Some(TokenKind::Final);
361            }
362            if t.eq_ignore_ascii_case("match") {
363                return Some(TokenKind::Match_);
364            }
365            if t.eq_ignore_ascii_case("trait") {
366                return Some(TokenKind::Trait);
367            }
368            if t.eq_ignore_ascii_case("clone") {
369                return Some(TokenKind::Clone);
370            }
371            if t.eq_ignore_ascii_case("endif") {
372                return Some(TokenKind::EndIf);
373            }
374        }
375        6 => {
376            if t.eq_ignore_ascii_case("elseif") {
377                return Some(TokenKind::ElseIf);
378            }
379            if t.eq_ignore_ascii_case("return") {
380                return Some(TokenKind::Return);
381            }
382            if t.eq_ignore_ascii_case("switch") {
383                return Some(TokenKind::Switch);
384            }
385            if t.eq_ignore_ascii_case("global") {
386                return Some(TokenKind::Global);
387            }
388            if t.eq_ignore_ascii_case("static") {
389                return Some(TokenKind::Static);
390            }
391            if t.eq_ignore_ascii_case("public") {
392                return Some(TokenKind::Public);
393            }
394            if t.eq_ignore_ascii_case("parent") {
395                return Some(TokenKind::Parent_);
396            }
397            if t.eq_ignore_ascii_case("endfor") {
398                return Some(TokenKind::EndFor);
399            }
400        }
401        7 => {
402            if t.eq_ignore_ascii_case("foreach") {
403                return Some(TokenKind::Foreach);
404            }
405            if t.eq_ignore_ascii_case("default") {
406                return Some(TokenKind::Default);
407            }
408            if t.eq_ignore_ascii_case("finally") {
409                return Some(TokenKind::Finally);
410            }
411            if t.eq_ignore_ascii_case("include") {
412                return Some(TokenKind::Include);
413            }
414            if t.eq_ignore_ascii_case("declare") {
415                return Some(TokenKind::Declare);
416            }
417            if t.eq_ignore_ascii_case("extends") {
418                return Some(TokenKind::Extends);
419            }
420            if t.eq_ignore_ascii_case("require") {
421                return Some(TokenKind::Require);
422            }
423            if t.eq_ignore_ascii_case("private") {
424                return Some(TokenKind::Private);
425            }
426            if t.eq_ignore_ascii_case("__dir__") {
427                return Some(TokenKind::MagicDir);
428            }
429        }
430        8 => {
431            if t.eq_ignore_ascii_case("function") {
432                return Some(TokenKind::Function);
433            }
434            if t.eq_ignore_ascii_case("abstract") {
435                return Some(TokenKind::Abstract);
436            }
437            if t.eq_ignore_ascii_case("readonly") {
438                return Some(TokenKind::Readonly);
439            }
440            if t.eq_ignore_ascii_case("continue") {
441                return Some(TokenKind::Continue);
442            }
443            if t.eq_ignore_ascii_case("endwhile") {
444                return Some(TokenKind::EndWhile);
445            }
446            if t.eq_ignore_ascii_case("__file__") {
447                return Some(TokenKind::MagicFile);
448            }
449            if t.eq_ignore_ascii_case("__line__") {
450                return Some(TokenKind::MagicLine);
451            }
452        }
453        9 => {
454            if t.eq_ignore_ascii_case("namespace") {
455                return Some(TokenKind::Namespace);
456            }
457            if t.eq_ignore_ascii_case("interface") {
458                return Some(TokenKind::Interface);
459            }
460            if t.eq_ignore_ascii_case("protected") {
461                return Some(TokenKind::Protected);
462            }
463            if t.eq_ignore_ascii_case("endswitch") {
464                return Some(TokenKind::EndSwitch);
465            }
466            if t.eq_ignore_ascii_case("__class__") {
467                return Some(TokenKind::MagicClass);
468            }
469            if t.eq_ignore_ascii_case("__trait__") {
470                return Some(TokenKind::MagicTrait);
471            }
472        }
473        10 => {
474            if t.eq_ignore_ascii_case("implements") {
475                return Some(TokenKind::Implements);
476            }
477            if t.eq_ignore_ascii_case("instanceof") {
478                return Some(TokenKind::Instanceof);
479            }
480            if t.eq_ignore_ascii_case("endforeach") {
481                return Some(TokenKind::EndForeach);
482            }
483            if t.eq_ignore_ascii_case("enddeclare") {
484                return Some(TokenKind::EndDeclare);
485            }
486            if t.eq_ignore_ascii_case("__method__") {
487                return Some(TokenKind::MagicMethod);
488            }
489        }
490        12 => {
491            if t.eq_ignore_ascii_case("include_once") {
492                return Some(TokenKind::IncludeOnce);
493            }
494            if t.eq_ignore_ascii_case("require_once") {
495                return Some(TokenKind::RequireOnce);
496            }
497            if t.eq_ignore_ascii_case("__function__") {
498                return Some(TokenKind::MagicFunction);
499            }
500            if t.eq_ignore_ascii_case("__property__") {
501                return Some(TokenKind::MagicProperty);
502            }
503        }
504        13 => {
505            if t.eq_ignore_ascii_case("__namespace__") {
506                return Some(TokenKind::MagicNamespace);
507            }
508        }
509        15 => {
510            if t.eq_ignore_ascii_case("__halt_compiler") {
511                return Some(TokenKind::HaltCompiler);
512            }
513        }
514        _ => {}
515    }
516    None
517}
518
519impl std::fmt::Display for TokenKind {
520    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521        match self {
522            TokenKind::IntLiteral => write!(f, "integer"),
523            TokenKind::HexIntLiteral => write!(f, "hex integer"),
524            TokenKind::BinIntLiteral => write!(f, "binary integer"),
525            TokenKind::OctIntLiteral | TokenKind::OctIntLiteralNew => write!(f, "octal integer"),
526            TokenKind::FloatLiteral
527            | TokenKind::FloatLiteralSimple
528            | TokenKind::FloatLiteralLeadingDot => write!(f, "float"),
529            TokenKind::SingleQuotedString | TokenKind::DoubleQuotedString => write!(f, "string"),
530            TokenKind::BacktickString => write!(f, "backtick string"),
531            TokenKind::Variable => write!(f, "variable"),
532            TokenKind::Dollar => write!(f, "'$'"),
533            TokenKind::Identifier => write!(f, "identifier"),
534            TokenKind::Plus => write!(f, "'+'"),
535            TokenKind::Minus => write!(f, "'-'"),
536            TokenKind::Star => write!(f, "'*'"),
537            TokenKind::Slash => write!(f, "'/'"),
538            TokenKind::Percent => write!(f, "'%'"),
539            TokenKind::StarStar => write!(f, "'**'"),
540            TokenKind::Dot => write!(f, "'.'"),
541            TokenKind::Equals => write!(f, "'='"),
542            TokenKind::PlusEquals => write!(f, "'+='"),
543            TokenKind::MinusEquals => write!(f, "'-='"),
544            TokenKind::StarEquals => write!(f, "'*='"),
545            TokenKind::SlashEquals => write!(f, "'/='"),
546            TokenKind::PercentEquals => write!(f, "'%='"),
547            TokenKind::StarStarEquals => write!(f, "'**='"),
548            TokenKind::DotEquals => write!(f, "'.='"),
549            TokenKind::AmpersandEquals => write!(f, "'&='"),
550            TokenKind::PipeEquals => write!(f, "'|='"),
551            TokenKind::CaretEquals => write!(f, "'^='"),
552            TokenKind::ShiftLeftEquals => write!(f, "'<<='"),
553            TokenKind::ShiftRightEquals => write!(f, "'>>='"),
554            TokenKind::CoalesceEquals => write!(f, "'??='"),
555            TokenKind::EqualsEquals => write!(f, "'=='"),
556            TokenKind::BangEquals => write!(f, "'!='"),
557            TokenKind::EqualsEqualsEquals => write!(f, "'==='"),
558            TokenKind::BangEqualsEquals => write!(f, "'!=='"),
559            TokenKind::LessThan => write!(f, "'<'"),
560            TokenKind::GreaterThan => write!(f, "'>'"),
561            TokenKind::LessThanEquals => write!(f, "'<='"),
562            TokenKind::GreaterThanEquals => write!(f, "'>='"),
563            TokenKind::Spaceship => write!(f, "'<=>'"),
564            TokenKind::AmpersandAmpersand => write!(f, "'&&'"),
565            TokenKind::PipePipe => write!(f, "'||'"),
566            TokenKind::Bang => write!(f, "'!'"),
567            TokenKind::Ampersand => write!(f, "'&'"),
568            TokenKind::Pipe => write!(f, "'|'"),
569            TokenKind::Caret => write!(f, "'^'"),
570            TokenKind::Tilde => write!(f, "'~'"),
571            TokenKind::ShiftLeft => write!(f, "'<<'"),
572            TokenKind::ShiftRight => write!(f, "'>>'"),
573            TokenKind::PlusPlus => write!(f, "'++'"),
574            TokenKind::MinusMinus => write!(f, "'--'"),
575            TokenKind::Question => write!(f, "'?'"),
576            TokenKind::QuestionQuestion => write!(f, "'??'"),
577            TokenKind::Colon => write!(f, "':'"),
578            TokenKind::FatArrow => write!(f, "'=>'"),
579            TokenKind::PipeArrow => write!(f, "'|>'"),
580            TokenKind::LeftParen => write!(f, "'('"),
581            TokenKind::RightParen => write!(f, "')'"),
582            TokenKind::LeftBracket => write!(f, "'['"),
583            TokenKind::RightBracket => write!(f, "']'"),
584            TokenKind::LeftBrace => write!(f, "'{{'"),
585            TokenKind::RightBrace => write!(f, "'}}'"),
586            TokenKind::Semicolon => write!(f, "';'"),
587            TokenKind::Comma => write!(f, "','"),
588            TokenKind::DoubleColon => write!(f, "'::'"),
589            TokenKind::Arrow => write!(f, "'->'"),
590            TokenKind::NullsafeArrow => write!(f, "'?->'"),
591            TokenKind::Backslash => write!(f, "'\\'"),
592            TokenKind::At => write!(f, "'@'"),
593            TokenKind::HashBracket => write!(f, "'#['"),
594            TokenKind::Ellipsis => write!(f, "'...'"),
595            TokenKind::If => write!(f, "'if'"),
596            TokenKind::Else => write!(f, "'else'"),
597            TokenKind::ElseIf => write!(f, "'elseif'"),
598            TokenKind::While => write!(f, "'while'"),
599            TokenKind::Do => write!(f, "'do'"),
600            TokenKind::For => write!(f, "'for'"),
601            TokenKind::Foreach => write!(f, "'foreach'"),
602            TokenKind::As => write!(f, "'as'"),
603            TokenKind::Function => write!(f, "'function'"),
604            TokenKind::Return => write!(f, "'return'"),
605            TokenKind::Echo => write!(f, "'echo'"),
606            TokenKind::Print => write!(f, "'print'"),
607            TokenKind::True => write!(f, "'true'"),
608            TokenKind::False => write!(f, "'false'"),
609            TokenKind::Null => write!(f, "'null'"),
610            TokenKind::And => write!(f, "'and'"),
611            TokenKind::Or => write!(f, "'or'"),
612            TokenKind::Xor => write!(f, "'xor'"),
613            TokenKind::Break => write!(f, "'break'"),
614            TokenKind::Continue => write!(f, "'continue'"),
615            TokenKind::Switch => write!(f, "'switch'"),
616            TokenKind::Case => write!(f, "'case'"),
617            TokenKind::Default => write!(f, "'default'"),
618            TokenKind::EndIf => write!(f, "'endif'"),
619            TokenKind::EndWhile => write!(f, "'endwhile'"),
620            TokenKind::EndFor => write!(f, "'endfor'"),
621            TokenKind::EndForeach => write!(f, "'endforeach'"),
622            TokenKind::Throw => write!(f, "'throw'"),
623            TokenKind::Try => write!(f, "'try'"),
624            TokenKind::Catch => write!(f, "'catch'"),
625            TokenKind::Finally => write!(f, "'finally'"),
626            TokenKind::Instanceof => write!(f, "'instanceof'"),
627            TokenKind::Array => write!(f, "'array'"),
628            TokenKind::List => write!(f, "'list'"),
629            TokenKind::Goto => write!(f, "'goto'"),
630            TokenKind::Declare => write!(f, "'declare'"),
631            TokenKind::Unset => write!(f, "'unset'"),
632            TokenKind::Global => write!(f, "'global'"),
633            TokenKind::EndDeclare => write!(f, "'enddeclare'"),
634            TokenKind::EndSwitch => write!(f, "'endswitch'"),
635            TokenKind::Isset => write!(f, "'isset'"),
636            TokenKind::Empty => write!(f, "'empty'"),
637            TokenKind::Include => write!(f, "'include'"),
638            TokenKind::IncludeOnce => write!(f, "'include_once'"),
639            TokenKind::Require => write!(f, "'require'"),
640            TokenKind::RequireOnce => write!(f, "'require_once'"),
641            TokenKind::Eval => write!(f, "'eval'"),
642            TokenKind::Exit => write!(f, "'exit'"),
643            TokenKind::Die => write!(f, "'die'"),
644            TokenKind::Clone => write!(f, "'clone'"),
645            TokenKind::New => write!(f, "'new'"),
646            TokenKind::Class => write!(f, "'class'"),
647            TokenKind::Abstract => write!(f, "'abstract'"),
648            TokenKind::Final => write!(f, "'final'"),
649            TokenKind::Interface => write!(f, "'interface'"),
650            TokenKind::Trait => write!(f, "'trait'"),
651            TokenKind::Extends => write!(f, "'extends'"),
652            TokenKind::Implements => write!(f, "'implements'"),
653            TokenKind::Public => write!(f, "'public'"),
654            TokenKind::Protected => write!(f, "'protected'"),
655            TokenKind::Private => write!(f, "'private'"),
656            TokenKind::Static => write!(f, "'static'"),
657            TokenKind::Const => write!(f, "'const'"),
658            TokenKind::Fn_ => write!(f, "'fn'"),
659            TokenKind::Match_ => write!(f, "'match'"),
660            TokenKind::Namespace => write!(f, "'namespace'"),
661            TokenKind::Use => write!(f, "'use'"),
662            TokenKind::Readonly => write!(f, "'readonly'"),
663            TokenKind::Enum_ => write!(f, "'enum'"),
664            TokenKind::Yield_ => write!(f, "'yield'"),
665            TokenKind::From => write!(f, "'from'"),
666            TokenKind::Self_ => write!(f, "'self'"),
667            TokenKind::Parent_ => write!(f, "'parent'"),
668            TokenKind::MagicClass => write!(f, "'__CLASS__'"),
669            TokenKind::MagicDir => write!(f, "'__DIR__'"),
670            TokenKind::MagicFile => write!(f, "'__FILE__'"),
671            TokenKind::MagicFunction => write!(f, "'__FUNCTION__'"),
672            TokenKind::MagicLine => write!(f, "'__LINE__'"),
673            TokenKind::MagicMethod => write!(f, "'__METHOD__'"),
674            TokenKind::MagicNamespace => write!(f, "'__NAMESPACE__'"),
675            TokenKind::MagicTrait => write!(f, "'__TRAIT__'"),
676            TokenKind::MagicProperty => write!(f, "'__PROPERTY__'"),
677            TokenKind::HaltCompiler => write!(f, "'__halt_compiler'"),
678            TokenKind::OpenTag => write!(f, "'<?php'"),
679            TokenKind::CloseTag => write!(f, "'?>'"),
680            TokenKind::InlineHtml => write!(f, "inline HTML"),
681            TokenKind::Heredoc => write!(f, "heredoc"),
682            TokenKind::Nowdoc => write!(f, "nowdoc"),
683            TokenKind::InvalidNumericLiteral => write!(f, "invalid numeric literal"),
684            TokenKind::Eof => write!(f, "end of file"),
685        }
686    }
687}
688
689#[cfg(test)]
690mod tests {
691    use super::*;
692
693    #[test]
694    fn test_resolve_keyword() {
695        assert_eq!(resolve_keyword("if"), Some(TokenKind::If));
696        assert_eq!(resolve_keyword("IF"), Some(TokenKind::If));
697        assert_eq!(resolve_keyword("If"), Some(TokenKind::If));
698        assert_eq!(resolve_keyword("function"), Some(TokenKind::Function));
699        assert_eq!(resolve_keyword("myFunc"), None);
700        assert_eq!(resolve_keyword("true"), Some(TokenKind::True));
701        assert_eq!(resolve_keyword("TRUE"), Some(TokenKind::True));
702        assert_eq!(resolve_keyword("null"), Some(TokenKind::Null));
703    }
704
705    #[test]
706    fn test_is_assignment_op() {
707        assert!(TokenKind::Equals.is_assignment_op());
708        assert!(TokenKind::PlusEquals.is_assignment_op());
709        assert!(TokenKind::DotEquals.is_assignment_op());
710        assert!(!TokenKind::Plus.is_assignment_op());
711        assert!(!TokenKind::EqualsEquals.is_assignment_op());
712    }
713}