Skip to main content

mago_syntax/ast/ast/control_flow/
switch.rs

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