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#[repr(u8)]
58#[non_exhaustive]
59pub enum Statement<'arena> {
60 OpeningTag(OpeningTag<'arena>),
61 ClosingTag(ClosingTag),
62 Inline(Inline<'arena>),
63 Namespace(Namespace<'arena>),
64 Use(Use<'arena>),
65 Class(Class<'arena>),
66 Interface(Interface<'arena>),
67 Trait(Trait<'arena>),
68 Enum(Enum<'arena>),
69 Block(Block<'arena>),
70 Constant(Constant<'arena>),
71 Function(Function<'arena>),
72 Declare(Declare<'arena>),
73 Goto(Goto<'arena>),
74 Label(Label<'arena>),
75 Try(Try<'arena>),
76 Foreach(Foreach<'arena>),
77 For(For<'arena>),
78 While(While<'arena>),
79 DoWhile(DoWhile<'arena>),
80 Continue(Continue<'arena>),
81 Break(Break<'arena>),
82 Switch(Switch<'arena>),
83 If(If<'arena>),
84 Return(Return<'arena>),
85 Expression(ExpressionStatement<'arena>),
86 Echo(Echo<'arena>),
87 EchoTag(EchoTag<'arena>),
88 Global(Global<'arena>),
89 Static(Static<'arena>),
90 HaltCompiler(HaltCompiler<'arena>),
91 Unset(Unset<'arena>),
92 Noop(Span),
93}
94
95impl Statement<'_> {
96 #[inline]
97 #[must_use]
98 pub const fn is_closing_tag(&self) -> bool {
99 matches!(self, Statement::ClosingTag(_))
100 }
101
102 #[inline]
103 #[must_use]
104 pub fn terminates_scripting(&self) -> bool {
105 match self {
106 Statement::ClosingTag(_) => true,
107 Statement::Namespace(Namespace { body: NamespaceBody::Implicit(implicit), .. }) => {
108 implicit.statements.last().map_or(implicit.terminator.is_closing_tag(), Statement::terminates_scripting)
109 }
110 Statement::Use(r#use) => r#use.terminator.is_closing_tag(),
111 Statement::Goto(goto) => goto.terminator.is_closing_tag(),
112 Statement::Declare(Declare { body: DeclareBody::Statement(b), .. }) => b.terminates_scripting(),
113 Statement::Declare(Declare { body: DeclareBody::ColonDelimited(b), .. }) => b.terminator.is_closing_tag(),
114 Statement::For(For { body: ForBody::Statement(b), .. }) => b.terminates_scripting(),
115 Statement::For(For { body: ForBody::ColonDelimited(b), .. }) => b.terminator.is_closing_tag(),
116 Statement::Foreach(Foreach { body: ForeachBody::Statement(b), .. }) => b.terminates_scripting(),
117 Statement::Foreach(Foreach { body: ForeachBody::ColonDelimited(b), .. }) => b.terminator.is_closing_tag(),
118 Statement::While(While { body: WhileBody::Statement(b), .. }) => b.terminates_scripting(),
119 Statement::While(While { body: WhileBody::ColonDelimited(b), .. }) => b.terminator.is_closing_tag(),
120 Statement::DoWhile(do_while) => do_while.terminator.is_closing_tag(),
121 Statement::Continue(cont) => cont.terminator.is_closing_tag(),
122 Statement::Break(brk) => brk.terminator.is_closing_tag(),
123 Statement::If(If { body: IfBody::Statement(stmt), .. }) => match &stmt.else_clause {
124 Some(else_clause) => else_clause.statement.terminates_scripting(),
125 None => stmt
126 .else_if_clauses
127 .iter()
128 .last()
129 .map_or(stmt.statement.terminates_scripting(), |clause| clause.statement.terminates_scripting()),
130 },
131 Statement::If(If { body: IfBody::ColonDelimited(body), .. }) => body.terminator.is_closing_tag(),
132 Statement::Return(ret) => ret.terminator.is_closing_tag(),
133 Statement::Expression(expression_statement) => expression_statement.terminator.is_closing_tag(),
134 Statement::Echo(echo) => echo.terminator.is_closing_tag(),
135 Statement::EchoTag(echo) => echo.terminator.is_closing_tag(),
136 Statement::Global(global) => global.terminator.is_closing_tag(),
137 Statement::Static(r#static) => r#static.terminator.is_closing_tag(),
138 Statement::Unset(unset) => unset.terminator.is_closing_tag(),
139 Statement::HaltCompiler(_) => true,
140 _ => false,
141 }
142 }
143
144 #[inline]
145 #[must_use]
146 pub const fn is_loop(&self) -> bool {
147 matches!(self, Statement::For(_) | Statement::Foreach(_) | Statement::While(_) | Statement::DoWhile(_))
148 }
149
150 #[inline]
151 #[must_use]
152 pub const fn is_control_flow(&self) -> bool {
153 matches!(
154 self,
155 Statement::If(_)
156 | Statement::Switch(_)
157 | Statement::Try(_)
158 | Statement::Continue(_)
159 | Statement::Break(_)
160 | Statement::Goto(_)
161 )
162 }
163
164 #[inline]
165 #[must_use]
166 pub const fn is_declaration(&self) -> bool {
167 matches!(
168 self,
169 Statement::Declare(_)
170 | Statement::Namespace(_)
171 | Statement::Class(_)
172 | Statement::Interface(_)
173 | Statement::Trait(_)
174 | Statement::Enum(_)
175 | Statement::Constant(_)
176 | Statement::Function(_)
177 )
178 }
179
180 #[inline]
181 #[must_use]
182 pub const fn is_noop(&self) -> bool {
183 matches!(self, Statement::Noop(_))
184 }
185}
186
187impl HasSpan for ExpressionStatement<'_> {
188 fn span(&self) -> Span {
189 self.expression.span().join(self.terminator.span())
190 }
191}
192
193impl HasSpan for Statement<'_> {
194 fn span(&self) -> Span {
195 match self {
196 Statement::OpeningTag(statement) => statement.span(),
197 Statement::ClosingTag(statement) => statement.span(),
198 Statement::Inline(statement) => statement.span(),
199 Statement::Namespace(statement) => statement.span(),
200 Statement::Use(statement) => statement.span(),
201 Statement::Class(statement) => statement.span(),
202 Statement::Interface(statement) => statement.span(),
203 Statement::Trait(statement) => statement.span(),
204 Statement::Enum(statement) => statement.span(),
205 Statement::Block(statement) => statement.span(),
206 Statement::Constant(statement) => statement.span(),
207 Statement::Function(statement) => statement.span(),
208 Statement::Declare(statement) => statement.span(),
209 Statement::Goto(statement) => statement.span(),
210 Statement::Label(statement) => statement.span(),
211 Statement::Try(statement) => statement.span(),
212 Statement::Foreach(statement) => statement.span(),
213 Statement::For(statement) => statement.span(),
214 Statement::While(statement) => statement.span(),
215 Statement::DoWhile(statement) => statement.span(),
216 Statement::Continue(statement) => statement.span(),
217 Statement::Break(statement) => statement.span(),
218 Statement::Switch(statement) => statement.span(),
219 Statement::If(statement) => statement.span(),
220 Statement::Return(statement) => statement.span(),
221 Statement::Expression(statement) => statement.span(),
222 Statement::Echo(statement) => statement.span(),
223 Statement::EchoTag(statement) => statement.span(),
224 Statement::Global(statement) => statement.span(),
225 Statement::Static(statement) => statement.span(),
226 Statement::Unset(statement) => statement.span(),
227 Statement::HaltCompiler(statement) => statement.span(),
228 Statement::Noop(span) => *span,
229 }
230 }
231}