Skip to main content

mago_syntax/ast/ast/control_flow/
if.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 an `if` statement.
14///
15/// # Examples
16///
17/// ```php
18/// if ($a) {
19///   echo "a is true";
20/// } elseif ($b) {
21///   echo "b is true";
22/// } else {
23///   echo "a and b are false";
24/// }
25/// ```
26#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
27pub struct If<'arena> {
28    pub r#if: Keyword<'arena>,
29    pub left_parenthesis: Span,
30    pub condition: &'arena Expression<'arena>,
31    pub right_parenthesis: Span,
32    pub body: IfBody<'arena>,
33}
34
35/// Represents the body of an `if` statement.
36///
37/// This can be either a statement body or a colon-delimited body.
38#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord, Display)]
39#[serde(tag = "type", content = "value")]
40pub enum IfBody<'arena> {
41    Statement(IfStatementBody<'arena>),
42    ColonDelimited(IfColonDelimitedBody<'arena>),
43}
44
45/// Represents the body of an `if` statement when it is a statement body.
46#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
47pub struct IfStatementBody<'arena> {
48    pub statement: &'arena Statement<'arena>,
49    pub else_if_clauses: Sequence<'arena, IfStatementBodyElseIfClause<'arena>>,
50    pub else_clause: Option<IfStatementBodyElseClause<'arena>>,
51}
52
53/// Represents an `elseif` clause in a statement body of an `if` statement.
54#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
55pub struct IfStatementBodyElseIfClause<'arena> {
56    pub elseif: Keyword<'arena>,
57    pub left_parenthesis: Span,
58    pub condition: &'arena Expression<'arena>,
59    pub right_parenthesis: Span,
60    pub statement: &'arena Statement<'arena>,
61}
62
63/// Represents an `else` clause in a statement body of an `if` statement.
64#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
65pub struct IfStatementBodyElseClause<'arena> {
66    pub r#else: Keyword<'arena>,
67    pub statement: &'arena Statement<'arena>,
68}
69
70/// Represents a colon-delimited body of an `if` statement.
71#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
72pub struct IfColonDelimitedBody<'arena> {
73    pub colon: Span,
74    pub statements: Sequence<'arena, Statement<'arena>>,
75    pub else_if_clauses: Sequence<'arena, IfColonDelimitedBodyElseIfClause<'arena>>,
76    pub else_clause: Option<IfColonDelimitedBodyElseClause<'arena>>,
77    pub endif: Keyword<'arena>,
78    pub terminator: Terminator<'arena>,
79}
80
81/// Represents an `elseif` clause in a colon-delimited body of an `if` statement.
82#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
83pub struct IfColonDelimitedBodyElseIfClause<'arena> {
84    pub elseif: Keyword<'arena>,
85    pub left_parenthesis: Span,
86    pub condition: &'arena Expression<'arena>,
87    pub right_parenthesis: Span,
88    pub colon: Span,
89    pub statements: Sequence<'arena, Statement<'arena>>,
90}
91
92/// Represents an `else` clause in a colon-delimited body of an `if` statement.
93#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
94pub struct IfColonDelimitedBodyElseClause<'arena> {
95    pub r#else: Keyword<'arena>,
96    pub colon: Span,
97    pub statements: Sequence<'arena, Statement<'arena>>,
98}
99
100impl<'arena> IfBody<'arena> {
101    #[must_use]
102    pub const fn has_else_clause(&self) -> bool {
103        match &self {
104            IfBody::Statement(if_statement_body) => if_statement_body.else_clause.is_some(),
105            IfBody::ColonDelimited(if_colon_delimited_body) => if_colon_delimited_body.else_clause.is_some(),
106        }
107    }
108
109    #[must_use]
110    pub fn has_else_if_clauses(&self) -> bool {
111        match &self {
112            IfBody::Statement(if_statement_body) => !if_statement_body.else_if_clauses.is_empty(),
113            IfBody::ColonDelimited(if_colon_delimited_body) => !if_colon_delimited_body.else_if_clauses.is_empty(),
114        }
115    }
116
117    #[must_use]
118    pub fn statements(&self) -> &[Statement<'arena>] {
119        match &self {
120            IfBody::Statement(if_statement_body) => std::slice::from_ref(if_statement_body.statement),
121            IfBody::ColonDelimited(if_colon_delimited_body) => if_colon_delimited_body.statements.as_slice(),
122        }
123    }
124
125    #[must_use]
126    pub fn else_statements(&self) -> Option<&[Statement<'arena>]> {
127        match &self {
128            IfBody::Statement(if_statement_body) => {
129                if_statement_body.else_clause.as_ref().map(|e| std::slice::from_ref(e.statement))
130            }
131            IfBody::ColonDelimited(if_colon_delimited_body) => {
132                if_colon_delimited_body.else_clause.as_ref().map(|e| e.statements.as_slice())
133            }
134        }
135    }
136
137    #[must_use]
138    pub fn else_if_statements(&self) -> Vec<&[Statement<'arena>]> {
139        match &self {
140            IfBody::Statement(if_statement_body) => {
141                if_statement_body.else_if_clauses.iter().map(|e| std::slice::from_ref(e.statement)).collect()
142            }
143            IfBody::ColonDelimited(if_colon_delimited_body) => {
144                if_colon_delimited_body.else_if_clauses.iter().map(|e| e.statements.as_slice()).collect()
145            }
146        }
147    }
148
149    #[must_use]
150    pub fn else_if_clauses(&self) -> Vec<(&Expression<'arena>, &[Statement<'arena>])> {
151        match &self {
152            IfBody::Statement(if_statement_body) => if_statement_body
153                .else_if_clauses
154                .iter()
155                .map(|e| (e.condition, std::slice::from_ref(e.statement)))
156                .collect(),
157            IfBody::ColonDelimited(if_colon_delimited_body) => {
158                if_colon_delimited_body.else_if_clauses.iter().map(|e| (e.condition, e.statements.as_slice())).collect()
159            }
160        }
161    }
162}
163
164impl HasSpan for If<'_> {
165    fn span(&self) -> Span {
166        Span::between(self.r#if.span(), self.body.span())
167    }
168}
169
170impl HasSpan for IfBody<'_> {
171    fn span(&self) -> Span {
172        match self {
173            IfBody::Statement(body) => body.span(),
174            IfBody::ColonDelimited(body) => body.span(),
175        }
176    }
177}
178
179impl HasSpan for IfStatementBody<'_> {
180    fn span(&self) -> Span {
181        let span = self.statement.span();
182
183        Span::between(
184            span,
185            self.else_clause.as_ref().map_or_else(|| self.else_if_clauses.span(span.file_id, span.end), HasSpan::span),
186        )
187    }
188}
189
190impl HasSpan for IfStatementBodyElseIfClause<'_> {
191    fn span(&self) -> Span {
192        Span::between(self.elseif.span(), self.statement.span())
193    }
194}
195
196impl HasSpan for IfStatementBodyElseClause<'_> {
197    fn span(&self) -> Span {
198        Span::between(self.r#else.span(), self.statement.span())
199    }
200}
201
202impl HasSpan for IfColonDelimitedBody<'_> {
203    fn span(&self) -> Span {
204        Span::between(self.colon, self.terminator.span())
205    }
206}
207
208impl HasSpan for IfColonDelimitedBodyElseIfClause<'_> {
209    fn span(&self) -> Span {
210        Span::between(self.elseif.span(), self.statements.span(self.colon.file_id, self.colon.end))
211    }
212}
213
214impl HasSpan for IfColonDelimitedBodyElseClause<'_> {
215    fn span(&self) -> Span {
216        Span::between(self.r#else.span(), self.statements.span(self.colon.file_id, self.colon.end))
217    }
218}