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