Skip to main content

mago_syntax/cst/cst/control_flow/
switch.rs

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