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