1use serde::Serialize;
2use strum::Display;
3
4use mago_span::HasSpan;
5use mago_span::Span;
6
7use crate::ast::ast::block::Block;
8use crate::ast::ast::class_like::Class;
9use crate::ast::ast::class_like::Enum;
10use crate::ast::ast::class_like::Interface;
11use crate::ast::ast::class_like::Trait;
12use crate::ast::ast::constant::Constant;
13use crate::ast::ast::control_flow::r#if::If;
14use crate::ast::ast::control_flow::switch::Switch;
15use crate::ast::ast::declare::Declare;
16use crate::ast::ast::echo::Echo;
17use crate::ast::ast::echo::EchoTag;
18use crate::ast::ast::expression::Expression;
19use crate::ast::ast::function_like::function::Function;
20use crate::ast::ast::global::Global;
21use crate::ast::ast::goto::Goto;
22use crate::ast::ast::goto::Label;
23use crate::ast::ast::halt_compiler::HaltCompiler;
24use crate::ast::ast::inline::Inline;
25use crate::ast::ast::r#loop::Break;
26use crate::ast::ast::r#loop::Continue;
27use crate::ast::ast::r#loop::do_while::DoWhile;
28use crate::ast::ast::r#loop::r#for::For;
29use crate::ast::ast::r#loop::foreach::Foreach;
30use crate::ast::ast::r#loop::r#while::While;
31use crate::ast::ast::namespace::Namespace;
32use crate::ast::ast::r#return::Return;
33use crate::ast::ast::r#static::Static;
34use crate::ast::ast::tag::ClosingTag;
35use crate::ast::ast::tag::OpeningTag;
36use crate::ast::ast::terminator::Terminator;
37use crate::ast::ast::r#try::Try;
38use crate::ast::ast::unset::Unset;
39use crate::ast::ast::r#use::Use;
40
41use super::DeclareBody;
42use super::ForBody;
43use super::ForeachBody;
44use super::IfBody;
45use super::NamespaceBody;
46use super::WhileBody;
47
48#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord)]
49pub struct ExpressionStatement<'arena> {
50 pub expression: &'arena Expression<'arena>,
51 pub terminator: Terminator<'arena>,
52}
53
54#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, PartialOrd, Ord, Display)]
56#[serde(tag = "type", content = "value")]
57#[non_exhaustive]
58pub enum Statement<'arena> {
59 OpeningTag(OpeningTag<'arena>),
60 ClosingTag(ClosingTag),
61 Inline(Inline<'arena>),
62 Namespace(Namespace<'arena>),
63 Use(Use<'arena>),
64 Class(Class<'arena>),
65 Interface(Interface<'arena>),
66 Trait(Trait<'arena>),
67 Enum(Enum<'arena>),
68 Block(Block<'arena>),
69 Constant(Constant<'arena>),
70 Function(Function<'arena>),
71 Declare(Declare<'arena>),
72 Goto(Goto<'arena>),
73 Label(Label<'arena>),
74 Try(Try<'arena>),
75 Foreach(Foreach<'arena>),
76 For(For<'arena>),
77 While(While<'arena>),
78 DoWhile(DoWhile<'arena>),
79 Continue(Continue<'arena>),
80 Break(Break<'arena>),
81 Switch(Switch<'arena>),
82 If(If<'arena>),
83 Return(Return<'arena>),
84 Expression(ExpressionStatement<'arena>),
85 Echo(Echo<'arena>),
86 EchoTag(EchoTag<'arena>),
87 Global(Global<'arena>),
88 Static(Static<'arena>),
89 HaltCompiler(HaltCompiler<'arena>),
90 Unset(Unset<'arena>),
91 Noop(Span),
92}
93
94impl Statement<'_> {
95 #[inline]
96 #[must_use]
97 pub const fn is_closing_tag(&self) -> bool {
98 matches!(self, Statement::ClosingTag(_))
99 }
100
101 #[inline]
102 #[must_use]
103 pub fn terminates_scripting(&self) -> bool {
104 match self {
105 Statement::ClosingTag(_) => true,
106 Statement::Namespace(Namespace { body: NamespaceBody::Implicit(implicit), .. }) => {
107 implicit.statements.last().map_or(implicit.terminator.is_closing_tag(), Statement::terminates_scripting)
108 }
109 Statement::Use(r#use) => r#use.terminator.is_closing_tag(),
110 Statement::Goto(goto) => goto.terminator.is_closing_tag(),
111 Statement::Declare(Declare { body: DeclareBody::Statement(b), .. }) => b.terminates_scripting(),
112 Statement::Declare(Declare { body: DeclareBody::ColonDelimited(b), .. }) => b.terminator.is_closing_tag(),
113 Statement::For(For { body: ForBody::Statement(b), .. }) => b.terminates_scripting(),
114 Statement::For(For { body: ForBody::ColonDelimited(b), .. }) => b.terminator.is_closing_tag(),
115 Statement::Foreach(Foreach { body: ForeachBody::Statement(b), .. }) => b.terminates_scripting(),
116 Statement::Foreach(Foreach { body: ForeachBody::ColonDelimited(b), .. }) => b.terminator.is_closing_tag(),
117 Statement::While(While { body: WhileBody::Statement(b), .. }) => b.terminates_scripting(),
118 Statement::While(While { body: WhileBody::ColonDelimited(b), .. }) => b.terminator.is_closing_tag(),
119 Statement::DoWhile(do_while) => do_while.terminator.is_closing_tag(),
120 Statement::Continue(cont) => cont.terminator.is_closing_tag(),
121 Statement::Break(brk) => brk.terminator.is_closing_tag(),
122 Statement::If(If { body: IfBody::Statement(stmt), .. }) => match &stmt.else_clause {
123 Some(else_clause) => else_clause.statement.terminates_scripting(),
124 None => stmt
125 .else_if_clauses
126 .iter()
127 .last()
128 .map_or(stmt.statement.terminates_scripting(), |clause| clause.statement.terminates_scripting()),
129 },
130 Statement::If(If { body: IfBody::ColonDelimited(body), .. }) => body.terminator.is_closing_tag(),
131 Statement::Return(ret) => ret.terminator.is_closing_tag(),
132 Statement::Expression(expression_statement) => expression_statement.terminator.is_closing_tag(),
133 Statement::Echo(echo) => echo.terminator.is_closing_tag(),
134 Statement::EchoTag(echo) => echo.terminator.is_closing_tag(),
135 Statement::Global(global) => global.terminator.is_closing_tag(),
136 Statement::Static(r#static) => r#static.terminator.is_closing_tag(),
137 Statement::Unset(unset) => unset.terminator.is_closing_tag(),
138 Statement::HaltCompiler(_) => true,
139 _ => false,
140 }
141 }
142
143 #[inline]
144 #[must_use]
145 pub const fn is_loop(&self) -> bool {
146 matches!(self, Statement::For(_) | Statement::Foreach(_) | Statement::While(_) | Statement::DoWhile(_))
147 }
148
149 #[inline]
150 #[must_use]
151 pub const fn is_control_flow(&self) -> bool {
152 matches!(
153 self,
154 Statement::If(_)
155 | Statement::Switch(_)
156 | Statement::Try(_)
157 | Statement::Continue(_)
158 | Statement::Break(_)
159 | Statement::Goto(_)
160 )
161 }
162
163 #[inline]
164 #[must_use]
165 pub const fn is_declaration(&self) -> bool {
166 matches!(
167 self,
168 Statement::Declare(_)
169 | Statement::Namespace(_)
170 | Statement::Class(_)
171 | Statement::Interface(_)
172 | Statement::Trait(_)
173 | Statement::Enum(_)
174 | Statement::Constant(_)
175 | Statement::Function(_)
176 )
177 }
178
179 #[inline]
180 #[must_use]
181 pub const fn is_noop(&self) -> bool {
182 matches!(self, Statement::Noop(_))
183 }
184}
185
186impl HasSpan for ExpressionStatement<'_> {
187 fn span(&self) -> Span {
188 self.expression.span().join(self.terminator.span())
189 }
190}
191
192impl HasSpan for Statement<'_> {
193 fn span(&self) -> Span {
194 match self {
195 Statement::OpeningTag(statement) => statement.span(),
196 Statement::ClosingTag(statement) => statement.span(),
197 Statement::Inline(statement) => statement.span(),
198 Statement::Namespace(statement) => statement.span(),
199 Statement::Use(statement) => statement.span(),
200 Statement::Class(statement) => statement.span(),
201 Statement::Interface(statement) => statement.span(),
202 Statement::Trait(statement) => statement.span(),
203 Statement::Enum(statement) => statement.span(),
204 Statement::Block(statement) => statement.span(),
205 Statement::Constant(statement) => statement.span(),
206 Statement::Function(statement) => statement.span(),
207 Statement::Declare(statement) => statement.span(),
208 Statement::Goto(statement) => statement.span(),
209 Statement::Label(statement) => statement.span(),
210 Statement::Try(statement) => statement.span(),
211 Statement::Foreach(statement) => statement.span(),
212 Statement::For(statement) => statement.span(),
213 Statement::While(statement) => statement.span(),
214 Statement::DoWhile(statement) => statement.span(),
215 Statement::Continue(statement) => statement.span(),
216 Statement::Break(statement) => statement.span(),
217 Statement::Switch(statement) => statement.span(),
218 Statement::If(statement) => statement.span(),
219 Statement::Return(statement) => statement.span(),
220 Statement::Expression(statement) => statement.span(),
221 Statement::Echo(statement) => statement.span(),
222 Statement::EchoTag(statement) => statement.span(),
223 Statement::Global(statement) => statement.span(),
224 Statement::Static(statement) => statement.span(),
225 Statement::Unset(statement) => statement.span(),
226 Statement::HaltCompiler(statement) => statement.span(),
227 Statement::Noop(span) => *span,
228 }
229 }
230}