1use serde::Serialize;
2use strum::Display;
3
4use mago_span::Span;
5
6use crate::T;
7
8#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord, Display)]
9#[serde(tag = "type", content = "value")]
10pub enum DocumentKind {
11 Heredoc,
12 Nowdoc,
13}
14
15#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord, Display)]
16#[serde(tag = "type", content = "value")]
17pub enum Associativity {
18 NonAssociative,
19 Left,
20 Right,
21}
22
23#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord, Display)]
24#[serde(tag = "type", content = "value")]
25pub enum Precedence {
26 Lowest,
27 LowLogicalOr,
28 LowLogicalXor,
29 LowLogicalAnd,
30 Print,
31 Yield,
32 YieldFrom,
33 IncDec,
34 KeyOr,
35 KeyXor,
36 KeyAnd,
37 Assignment,
38 ElvisOrConditional,
39 NullCoalesce,
40 Or,
41 And,
42 BitwiseOr,
43 BitwiseXor,
44 BitwiseAnd,
45 Equality,
46 Comparison,
47 Concat,
48 Pipe,
55 BitShift,
56 AddSub,
57 MulDivMod,
58 Bang,
59 Instanceof,
60 Prefix,
61 Pow,
62 Clone,
63 CallDim,
64 New,
65 ArrayDim,
66 ObjectAccess,
67}
68
69pub trait GetPrecedence {
70 fn precedence(&self) -> Precedence;
71}
72
73#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord, Display)]
74#[serde(tag = "type", content = "value")]
75pub enum TokenKind {
76 Whitespace, Eval, Die, Self_, Parent, Backtick, DocumentStart(DocumentKind), DocumentEnd, From, Print, Dollar, HaltCompiler, Readonly, Global, Abstract, Ampersand, AmpersandEqual, AmpersandAmpersand, AmpersandAmpersandEqual, Array, ArrayCast, MinusGreaterThan, QuestionMinusGreaterThan, At, As, Asterisk, HashLeftBracket, Bang, BangEqual, LessThanGreaterThan, BangEqualEqual, LessThanEqualGreaterThan, BoolCast, BooleanCast, And, Or, Break, Callable, Caret, CaretEqual, Case, Catch, Class, ClassConstant, TraitConstant, FunctionConstant, MethodConstant, LineConstant, FileConstant, Clone, MinusEqual, CloseTag, QuestionQuestion, QuestionQuestionEqual, AsteriskEqual, Colon, Comma, SingleLineComment, HashComment, MultiLineComment, DocBlockComment, Const, PartialLiteralString, LiteralString, Continue, Declare, MinusMinus, Default, DirConstant, SlashEqual, Do, DollarLeftBrace, Dot, DotEqual, EqualGreaterThan, DoubleCast, RealCast, FloatCast, ColonColon, EqualEqual, DoubleQuote, Else, Echo, DotDotDot, ElseIf, Empty, EndDeclare, EndFor, EndForeach, EndIf, EndSwitch, EndWhile, Enum, Equal, Extends, False, Final, Finally, LiteralFloat, Fn, For, Foreach, FullyQualifiedIdentifier, Function, Goto, GreaterThan, GreaterThanEqual, Identifier, If, Implements, Include, IncludeOnce, PlusPlus, InlineText, InlineShebang, Instanceof, Insteadof, Exit, Unset, Isset, List, LiteralInteger, IntCast, IntegerCast, Interface, LeftBrace, LeftBracket, LeftParenthesis, LeftShift, LeftShiftEqual, RightShift, RightShiftEqual, LessThan, LessThanEqual, Match, Minus, Namespace, NamespaceSeparator, NamespaceConstant, New, Null, ObjectCast, UnsetCast, OpenTag, EchoTag, ShortOpenTag, Percent, PercentEqual, Pipe, PipeEqual, Plus, PlusEqual, AsteriskAsterisk, AsteriskAsteriskEqual, Private, PrivateSet, Protected, ProtectedSet, Public, PublicSet, QualifiedIdentifier, Question, QuestionColon, Require, RequireOnce, Return, RightBrace, RightBracket, RightParenthesis, Semicolon, Slash, Static, StringCast, BinaryCast, VoidCast, StringPart, Switch, Throw, Trait, EqualEqualEqual, True, Try, Use, Var, Variable, Yield, While, Tilde, PipePipe, Xor, PipeGreaterThan, }
268
269#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
270pub struct Token<'arena> {
271 pub kind: TokenKind,
272 pub value: &'arena str,
273 pub span: Span,
274}
275
276impl Precedence {
277 #[inline]
278 pub const fn infix(kind: &TokenKind) -> Precedence {
279 match kind {
280 T!["**"] => Precedence::Pow,
281 T!["instanceof"] => Precedence::Instanceof,
282 T!["*" | "/" | "%"] => Precedence::MulDivMod,
283 T!["+" | "-"] => Precedence::AddSub,
284 T!["<<"] | T![">>"] => Precedence::BitShift,
285 T!["."] => Precedence::Concat,
286 T!["<" | "<=" | ">" | ">="] => Precedence::Comparison,
287 T!["==" | "!=" | "===" | "!==" | "<>" | "<=>"] => Precedence::Equality,
288 T!["&"] => Precedence::BitwiseAnd,
289 T!["^"] => Precedence::BitwiseXor,
290 T!["|"] => Precedence::BitwiseOr,
291 T!["&&"] => Precedence::And,
292 T!["||"] => Precedence::Or,
293 T!["??"] => Precedence::NullCoalesce,
294 T!["?" | "?:"] => Precedence::ElvisOrConditional,
295 T!["="
296 | "+="
297 | "-="
298 | "*="
299 | "**="
300 | "/="
301 | ".="
302 | "&&="
303 | "??="
304 | "%="
305 | "&="
306 | "|="
307 | "^="
308 | "<<="
309 | ">>="] => Precedence::Assignment,
310 T!["yield"] => Precedence::Yield,
311 T!["and"] => Precedence::KeyAnd,
312 T!["or"] => Precedence::KeyOr,
313 T!["xor"] => Precedence::KeyXor,
314 T!["|>"] => Precedence::Pipe,
315 _ => Precedence::Lowest,
316 }
317 }
318
319 #[inline]
320 pub const fn postfix(kind: &TokenKind) -> Self {
321 match kind {
322 T!["++" | "--"] => Self::Prefix,
323 T!["("] => Self::CallDim,
324 T!["["] => Self::ArrayDim,
325 T!["->" | "?->" | "::"] => Self::ObjectAccess,
326 _ => Self::Lowest,
327 }
328 }
329
330 #[inline]
331 pub const fn associativity(&self) -> Option<Associativity> {
332 Some(match self {
333 Self::Instanceof
334 | Self::MulDivMod
335 | Self::AddSub
336 | Self::BitShift
337 | Self::Concat
338 | Self::BitwiseAnd
339 | Self::BitwiseOr
340 | Self::BitwiseXor
341 | Self::And
342 | Self::Or
343 | Self::KeyAnd
344 | Self::KeyOr
345 | Self::KeyXor
346 | Self::Pipe => Associativity::Left,
347 Self::Pow | Self::NullCoalesce | Self::Assignment => Associativity::Right,
348 Self::ElvisOrConditional | Self::Equality | Self::Comparison => Associativity::NonAssociative,
349 _ => return None,
350 })
351 }
352
353 #[inline]
354 pub const fn is_associative(&self) -> bool {
355 self.associativity().is_some()
356 }
357
358 #[inline]
359 pub const fn is_right_associative(&self) -> bool {
360 matches!(self.associativity(), Some(Associativity::Right))
361 }
362
363 #[inline]
364 pub const fn is_left_associative(&self) -> bool {
365 matches!(self.associativity(), Some(Associativity::Left))
366 }
367
368 #[inline]
369 pub const fn is_non_associative(&self) -> bool {
370 matches!(self.associativity(), Some(Associativity::NonAssociative))
371 }
372}
373
374impl TokenKind {
375 #[inline]
376 pub const fn is_keyword(&self) -> bool {
377 matches!(
378 self,
379 TokenKind::Eval
380 | TokenKind::Die
381 | TokenKind::Empty
382 | TokenKind::Isset
383 | TokenKind::Unset
384 | TokenKind::Exit
385 | TokenKind::EndDeclare
386 | TokenKind::EndSwitch
387 | TokenKind::EndWhile
388 | TokenKind::EndForeach
389 | TokenKind::EndFor
390 | TokenKind::EndIf
391 | TokenKind::From
392 | TokenKind::And
393 | TokenKind::Or
394 | TokenKind::Xor
395 | TokenKind::Print
396 | TokenKind::Readonly
397 | TokenKind::Global
398 | TokenKind::Match
399 | TokenKind::Abstract
400 | TokenKind::Array
401 | TokenKind::As
402 | TokenKind::Break
403 | TokenKind::Case
404 | TokenKind::Catch
405 | TokenKind::Class
406 | TokenKind::Clone
407 | TokenKind::Continue
408 | TokenKind::Const
409 | TokenKind::Declare
410 | TokenKind::Default
411 | TokenKind::Do
412 | TokenKind::Echo
413 | TokenKind::ElseIf
414 | TokenKind::Else
415 | TokenKind::Enum
416 | TokenKind::Extends
417 | TokenKind::False
418 | TokenKind::Finally
419 | TokenKind::Final
420 | TokenKind::Fn
421 | TokenKind::Foreach
422 | TokenKind::For
423 | TokenKind::Function
424 | TokenKind::Goto
425 | TokenKind::If
426 | TokenKind::IncludeOnce
427 | TokenKind::Include
428 | TokenKind::Implements
429 | TokenKind::Interface
430 | TokenKind::Instanceof
431 | TokenKind::Namespace
432 | TokenKind::New
433 | TokenKind::Null
434 | TokenKind::Private
435 | TokenKind::PrivateSet
436 | TokenKind::Protected
437 | TokenKind::Public
438 | TokenKind::RequireOnce
439 | TokenKind::Require
440 | TokenKind::Return
441 | TokenKind::Static
442 | TokenKind::Switch
443 | TokenKind::Throw
444 | TokenKind::Trait
445 | TokenKind::True
446 | TokenKind::Try
447 | TokenKind::Use
448 | TokenKind::Var
449 | TokenKind::Yield
450 | TokenKind::While
451 | TokenKind::Insteadof
452 | TokenKind::List
453 | TokenKind::Self_
454 | TokenKind::Parent
455 | TokenKind::DirConstant
456 | TokenKind::FileConstant
457 | TokenKind::LineConstant
458 | TokenKind::FunctionConstant
459 | TokenKind::ClassConstant
460 | TokenKind::MethodConstant
461 | TokenKind::TraitConstant
462 | TokenKind::NamespaceConstant
463 | TokenKind::HaltCompiler
464 )
465 }
466
467 #[inline]
468 pub const fn is_infix(&self) -> bool {
469 matches!(
470 self,
471 T!["**"
472 | ">>="
473 | "<<="
474 | "^="
475 | "&="
476 | "|="
477 | "%="
478 | "**="
479 | "and"
480 | "or"
481 | "xor"
482 | "<=>"
483 | "<<"
484 | ">>"
485 | "&"
486 | "|"
487 | "^"
488 | "%"
489 | "instanceof"
490 | "*"
491 | "/"
492 | "+"
493 | "-"
494 | "."
495 | "<"
496 | ">"
497 | "<="
498 | ">="
499 | "=="
500 | "==="
501 | "!="
502 | "!=="
503 | "<>"
504 | "?"
505 | "?:"
506 | "&&"
507 | "||"
508 | "="
509 | "+="
510 | "-="
511 | ".="
512 | "??="
513 | "/="
514 | "*="
515 | "??"
516 | "|>"]
517 )
518 }
519
520 #[inline]
521 pub const fn is_postfix(&self) -> bool {
522 matches!(self, T!["++" | "--" | "(" | "[" | "->" | "?->" | "::"])
523 }
524
525 #[inline]
526 pub const fn is_visibility_modifier(&self) -> bool {
527 matches!(self, T!["public" | "protected" | "private" | "private(set)" | "protected(set)" | "public(set)"])
528 }
529
530 #[inline]
531 pub const fn is_modifier(&self) -> bool {
532 matches!(
533 self,
534 T!["public"
535 | "protected"
536 | "private"
537 | "private(set)"
538 | "protected(set)"
539 | "public(set)"
540 | "static"
541 | "final"
542 | "abstract"
543 | "readonly"]
544 )
545 }
546
547 #[inline]
548 pub const fn is_identifier_maybe_soft_reserved(&self) -> bool {
549 if let TokenKind::Identifier = self { true } else { self.is_soft_reserved_identifier() }
550 }
551
552 #[inline]
553 pub const fn is_identifier_maybe_reserved(&self) -> bool {
554 if let TokenKind::Identifier = self { true } else { self.is_reserved_identifier() }
555 }
556
557 #[inline]
558 pub const fn is_soft_reserved_identifier(&self) -> bool {
559 matches!(
560 self,
561 T!["parent" | "self" | "true" | "false" | "list" | "null" | "enum" | "from" | "readonly" | "match"]
562 )
563 }
564
565 #[inline]
566 pub const fn is_reserved_identifier(&self) -> bool {
567 if self.is_soft_reserved_identifier() {
568 return true;
569 }
570
571 matches!(
572 self,
573 T!["static"
574 | "abstract"
575 | "final"
576 | "for"
577 | "private"
578 | "private(set)"
579 | "protected"
580 | "protected(set)"
581 | "public"
582 | "public(set)"
583 | "include"
584 | "include_once"
585 | "eval"
586 | "require"
587 | "require_once"
588 | "or"
589 | "xor"
590 | "and"
591 | "instanceof"
592 | "new"
593 | "clone"
594 | "exit"
595 | "die"
596 | "if"
597 | "elseif"
598 | "else"
599 | "endif"
600 | "echo"
601 | "do"
602 | "while"
603 | "endwhile"
604 | "endfor"
605 | "foreach"
606 | "endforeach"
607 | "declare"
608 | "enddeclare"
609 | "as"
610 | "try"
611 | "catch"
612 | "finally"
613 | "throw"
614 | "use"
615 | "insteadof"
616 | "global"
617 | "var"
618 | "unset"
619 | "isset"
620 | "empty"
621 | "continue"
622 | "goto"
623 | "function"
624 | "const"
625 | "return"
626 | "print"
627 | "yield"
628 | "list"
629 | "switch"
630 | "endswitch"
631 | "case"
632 | "default"
633 | "break"
634 | "array"
635 | "callable"
636 | "extends"
637 | "implements"
638 | "namespace"
639 | "trait"
640 | "interface"
641 | "class"
642 | "__CLASS__"
643 | "__TRAIT__"
644 | "__FUNCTION__"
645 | "__METHOD__"
646 | "__LINE__"
647 | "__FILE__"
648 | "__DIR__"
649 | "__NAMESPACE__"
650 | "__halt_compiler"
651 | "fn"
652 | "match"]
653 )
654 }
655
656 #[inline]
657 pub const fn is_literal(&self) -> bool {
658 matches!(
659 self,
660 T!["true" | "false" | "null" | LiteralFloat | LiteralInteger | LiteralString | PartialLiteralString]
661 )
662 }
663
664 #[inline]
665 pub const fn is_magic_constant(&self) -> bool {
666 matches!(
667 self,
668 T!["__CLASS__"
669 | "__DIR__"
670 | "__FILE__"
671 | "__FUNCTION__"
672 | "__LINE__"
673 | "__METHOD__"
674 | "__NAMESPACE__"
675 | "__TRAIT__"]
676 )
677 }
678
679 #[inline]
680 pub const fn is_cast(&self) -> bool {
681 matches!(
682 self,
683 T!["(string)"
684 | "(binary)"
685 | "(int)"
686 | "(integer)"
687 | "(float)"
688 | "(double)"
689 | "(real)"
690 | "(bool)"
691 | "(boolean)"
692 | "(array)"
693 | "(object)"
694 | "(unset)"
695 | "(void)"]
696 )
697 }
698
699 #[inline]
700 pub const fn is_unary_prefix(&self) -> bool {
701 if self.is_cast() {
702 return true;
703 }
704
705 matches!(self, T!["@" | "!" | "~" | "-" | "+" | "++" | "--" | "&"])
706 }
707
708 #[inline]
709 pub const fn is_trivia(&self) -> bool {
710 matches!(self, T![SingleLineComment | MultiLineComment | DocBlockComment | HashComment | Whitespace])
711 }
712
713 #[inline]
714 pub const fn is_comment(&self) -> bool {
715 matches!(self, T![SingleLineComment | MultiLineComment | DocBlockComment | HashComment])
716 }
717
718 #[inline]
719 pub const fn is_comma(&self) -> bool {
720 matches!(self, T![","])
721 }
722
723 #[inline]
724 pub const fn is_construct(&self) -> bool {
725 matches!(
726 self,
727 T!["isset"
728 | "empty"
729 | "eval"
730 | "include"
731 | "include_once"
732 | "require"
733 | "require_once"
734 | "print"
735 | "unset"
736 | "exit"
737 | "die"]
738 )
739 }
740}
741
742impl<'arena> Token<'arena> {
743 pub const fn new(kind: TokenKind, value: &'arena str, span: Span) -> Self {
744 Self { kind, value, span }
745 }
746}
747
748impl<'arena> std::fmt::Display for Token<'arena> {
749 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
750 write!(f, "{}({})", self.kind, self.value)
751 }
752}