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