mago_syntax/ast/ast/control_flow/
switch.rs

1use serde::Deserialize;
2use serde::Serialize;
3use strum::Display;
4
5use mago_span::HasSpan;
6use mago_span::Span;
7
8use crate::ast::ast::expression::Expression;
9use crate::ast::ast::keyword::Keyword;
10use crate::ast::ast::statement::Statement;
11use crate::ast::ast::terminator::Terminator;
12use crate::ast::sequence::Sequence;
13
14/// Represents a `switch` statement in PHP.
15#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
16#[repr(C)]
17pub struct Switch {
18    pub switch: Keyword,
19    pub left_parenthesis: Span,
20    pub expression: Box<Expression>,
21    pub right_parenthesis: Span,
22    pub body: SwitchBody,
23}
24
25/// Represents the body of a switch statement.
26#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord, Display)]
27#[serde(tag = "type", content = "value")]
28#[repr(C, u8)]
29pub enum SwitchBody {
30    BraceDelimited(SwitchBraceDelimitedBody),
31    ColonDelimited(SwitchColonDelimitedBody),
32}
33
34/// Represents a brace-delimited body of a switch statement.
35#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
36#[repr(C)]
37pub struct SwitchBraceDelimitedBody {
38    pub left_brace: Span,
39    pub optional_terminator: Option<Terminator>,
40    pub cases: Sequence<SwitchCase>,
41    pub right_brace: Span,
42}
43
44/// Represents a colon-delimited body of a switch statement.
45#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
46#[repr(C)]
47pub struct SwitchColonDelimitedBody {
48    pub colon: Span,
49    pub optional_terminator: Option<Terminator>,
50    pub cases: Sequence<SwitchCase>,
51    pub end_switch: Keyword,
52    pub terminator: Terminator,
53}
54
55/// Represents a single case within a switch statement.
56#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord, Display)]
57#[serde(tag = "type", content = "value")]
58#[repr(C, u8)]
59pub enum SwitchCase {
60    Expression(SwitchExpressionCase),
61    Default(SwitchDefaultCase),
62}
63
64/// Represents a single case within a switch statement.
65///
66/// Example: `case 1: echo "One";`
67#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
68#[repr(C)]
69pub struct SwitchExpressionCase {
70    pub case: Keyword,
71    pub expression: Box<Expression>,
72    pub separator: SwitchCaseSeparator,
73    pub statements: Sequence<Statement>,
74}
75
76/// Represents the default case within a switch statement.
77///
78/// Example: `default: echo "Default";`
79#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
80#[repr(C)]
81pub struct SwitchDefaultCase {
82    pub default: Keyword,
83    pub separator: SwitchCaseSeparator,
84    pub statements: Sequence<Statement>,
85}
86
87/// Represents the separator between a case and its statements.
88#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, PartialOrd, Ord, Display)]
89#[serde(tag = "type", content = "value")]
90#[repr(C, u8)]
91pub enum SwitchCaseSeparator {
92    Colon(Span),
93    SemiColon(Span),
94}
95
96impl SwitchBody {
97    pub fn cases(&self) -> &[SwitchCase] {
98        match self {
99            SwitchBody::BraceDelimited(body) => body.cases.as_slice(),
100            SwitchBody::ColonDelimited(body) => body.cases.as_slice(),
101        }
102    }
103}
104
105impl SwitchCase {
106    /// Returns the statements within the case.
107    pub fn statements(&self) -> &[Statement] {
108        match self {
109            SwitchCase::Expression(case) => case.statements.as_slice(),
110            SwitchCase::Default(case) => case.statements.as_slice(),
111        }
112    }
113
114    /// Returns `true` if the case is a default case.
115    pub fn is_default(&self) -> bool {
116        match self {
117            SwitchCase::Expression(_) => false,
118            SwitchCase::Default(_) => true,
119        }
120    }
121
122    /// Returns `true` if the case is empty.
123    pub fn is_empty(&self) -> bool {
124        match self {
125            SwitchCase::Expression(case) => case.statements.is_empty(),
126            SwitchCase::Default(case) => case.statements.is_empty(),
127        }
128    }
129
130    /// Returns the case is fall-through.
131    ///
132    /// A case is considered fall-through if it is not empty and
133    /// does not end with a `break` statement.
134    pub fn is_fall_through(&self) -> bool {
135        let Some(last_statement) = self.statements().last() else {
136            return false;
137        };
138
139        !matches!(last_statement, Statement::Break(_))
140    }
141}
142
143impl HasSpan for Switch {
144    fn span(&self) -> Span {
145        Span::between(self.switch.span(), self.body.span())
146    }
147}
148
149impl HasSpan for SwitchBody {
150    fn span(&self) -> Span {
151        match self {
152            SwitchBody::BraceDelimited(body) => body.span(),
153            SwitchBody::ColonDelimited(body) => body.span(),
154        }
155    }
156}
157
158impl HasSpan for SwitchBraceDelimitedBody {
159    fn span(&self) -> Span {
160        Span::between(self.left_brace, self.right_brace)
161    }
162}
163
164impl HasSpan for SwitchColonDelimitedBody {
165    fn span(&self) -> Span {
166        Span::between(self.colon, self.terminator.span())
167    }
168}
169
170impl HasSpan for SwitchCase {
171    fn span(&self) -> Span {
172        match self {
173            SwitchCase::Expression(case) => case.span(),
174            SwitchCase::Default(case) => case.span(),
175        }
176    }
177}
178
179impl HasSpan for SwitchExpressionCase {
180    fn span(&self) -> Span {
181        Span::between(
182            self.case.span(),
183            self.statements.last().map(|statement| statement.span()).unwrap_or(self.separator.span()),
184        )
185    }
186}
187
188impl HasSpan for SwitchDefaultCase {
189    fn span(&self) -> Span {
190        Span::between(
191            self.default.span(),
192            self.statements.last().map(|statement| statement.span()).unwrap_or(self.separator.span()),
193        )
194    }
195}
196
197impl HasSpan for SwitchCaseSeparator {
198    fn span(&self) -> Span {
199        match self {
200            SwitchCaseSeparator::Colon(span) => *span,
201            SwitchCaseSeparator::SemiColon(span) => *span,
202        }
203    }
204}