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