boa/syntax/ast/node/iteration/for_in_loop/
mod.rs1use crate::{
2 builtins::{iterable::IteratorRecord, ForInIterator},
3 environment::{
4 declarative_environment_record::DeclarativeEnvironmentRecord,
5 lexical_environment::VariableScope,
6 },
7 exec::{Executable, InterpreterState},
8 gc::{Finalize, Trace},
9 syntax::ast::node::{Declaration, Node},
10 BoaProfiler, Context, JsResult, JsValue,
11};
12use std::fmt;
13
14#[cfg(feature = "deser")]
15use serde::{Deserialize, Serialize};
16
17#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
18#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
19pub struct ForInLoop {
20 variable: Box<Node>,
21 expr: Box<Node>,
22 body: Box<Node>,
23 label: Option<Box<str>>,
24}
25
26impl ForInLoop {
27 pub fn new<V, I, B>(variable: V, expr: I, body: B) -> Self
28 where
29 V: Into<Node>,
30 I: Into<Node>,
31 B: Into<Node>,
32 {
33 Self {
34 variable: Box::new(variable.into()),
35 expr: Box::new(expr.into()),
36 body: Box::new(body.into()),
37 label: None,
38 }
39 }
40
41 pub fn variable(&self) -> &Node {
42 &self.variable
43 }
44
45 pub fn expr(&self) -> &Node {
46 &self.expr
47 }
48
49 pub fn body(&self) -> &Node {
50 &self.body
51 }
52
53 pub fn label(&self) -> Option<&str> {
54 self.label.as_ref().map(Box::as_ref)
55 }
56
57 pub fn set_label(&mut self, label: Box<str>) {
58 self.label = Some(label);
59 }
60
61 pub fn display(&self, f: &mut fmt::Formatter<'_>, indentation: usize) -> fmt::Result {
62 if let Some(ref label) = self.label {
63 write!(f, "{}: ", label)?;
64 }
65 write!(f, "for ({} in {}) ", self.variable, self.expr)?;
66 self.body().display(f, indentation)
67 }
68}
69
70impl fmt::Display for ForInLoop {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 self.display(f, 0)
73 }
74}
75
76impl From<ForInLoop> for Node {
77 fn from(for_in: ForInLoop) -> Node {
78 Self::ForInLoop(for_in)
79 }
80}
81
82impl Executable for ForInLoop {
83 fn run(&self, context: &mut Context) -> JsResult<JsValue> {
84 let _timer = BoaProfiler::global().start_event("ForIn", "exec");
85 let object = self.expr().run(context)?;
86 let mut result = JsValue::undefined();
87
88 if object.is_null_or_undefined() {
89 return Ok(result);
90 }
91 let object = object.to_object(context)?;
92 let for_in_iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), context);
93 let next_function = for_in_iterator
94 .get_property("next")
95 .as_ref()
96 .map(|p| p.expect_value())
97 .cloned()
98 .ok_or_else(|| context.construct_type_error("Could not find property `next`"))?;
99 let iterator = IteratorRecord::new(for_in_iterator, next_function);
100
101 loop {
102 {
103 let env = context.get_current_environment();
104 context.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
105 }
106 let iterator_result = iterator.next(context)?;
107 if iterator_result.done {
108 context.pop_environment();
109 break;
110 }
111 let next_result = iterator_result.value;
112
113 match self.variable() {
114 Node::Identifier(ref name) => {
115 if context.has_binding(name.as_ref())? {
116 context.set_mutable_binding(
118 name.as_ref(),
119 next_result.clone(),
120 context.strict(),
121 )?;
122 } else {
123 context.create_mutable_binding(
124 name.as_ref(),
125 true,
126 VariableScope::Function,
127 )?;
128 context.initialize_binding(name.as_ref(), next_result)?;
129 }
130 }
131 Node::VarDeclList(ref list) => match list.as_ref() {
132 [var] => {
133 if var.init().is_some() {
134 return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer");
135 }
136
137 match &var {
138 Declaration::Identifier { ident, .. } => {
139 if context.has_binding(ident.as_ref())? {
140 context.set_mutable_binding(
141 ident.as_ref(),
142 next_result,
143 context.strict(),
144 )?;
145 } else {
146 context.create_mutable_binding(
147 ident.as_ref(),
148 false,
149 VariableScope::Function,
150 )?;
151 context.initialize_binding(ident.as_ref(), next_result)?;
152 }
153 }
154 Declaration::Pattern(p) => {
155 for (ident, value) in p.run(Some(next_result), context)? {
156 if context.has_binding(ident.as_ref())? {
157 context.set_mutable_binding(
158 ident.as_ref(),
159 value,
160 context.strict(),
161 )?;
162 } else {
163 context.create_mutable_binding(
164 ident.as_ref(),
165 false,
166 VariableScope::Function,
167 )?;
168 context.initialize_binding(ident.as_ref(), value)?;
169 }
170 }
171 }
172 }
173 }
174 _ => {
175 return context.throw_syntax_error(
176 "only one variable can be declared in the head of a for-in loop",
177 )
178 }
179 },
180 Node::LetDeclList(ref list) => match list.as_ref() {
181 [var] => {
182 if var.init().is_some() {
183 return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer");
184 }
185
186 match &var {
187 Declaration::Identifier { ident, .. } => {
188 context.create_mutable_binding(
189 ident.as_ref(),
190 false,
191 VariableScope::Block,
192 )?;
193 context.initialize_binding(ident.as_ref(), next_result)?;
194 }
195 Declaration::Pattern(p) => {
196 for (ident, value) in p.run(Some(next_result), context)? {
197 context.create_mutable_binding(
198 ident.as_ref(),
199 false,
200 VariableScope::Block,
201 )?;
202 context.initialize_binding(ident.as_ref(), value)?;
203 }
204 }
205 }
206 }
207 _ => {
208 return context.throw_syntax_error(
209 "only one variable can be declared in the head of a for-in loop",
210 )
211 }
212 },
213 Node::ConstDeclList(ref list) => match list.as_ref() {
214 [var] => {
215 if var.init().is_some() {
216 return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer");
217 }
218
219 match &var {
220 Declaration::Identifier { ident, .. } => {
221 context.create_immutable_binding(
222 ident.as_ref(),
223 false,
224 VariableScope::Block,
225 )?;
226 context.initialize_binding(ident.as_ref(), next_result)?;
227 }
228 Declaration::Pattern(p) => {
229 for (ident, value) in p.run(Some(next_result), context)? {
230 context.create_immutable_binding(
231 ident.as_ref(),
232 false,
233 VariableScope::Block,
234 )?;
235 context.initialize_binding(ident.as_ref(), value)?;
236 }
237 }
238 }
239 }
240 _ => {
241 return context.throw_syntax_error(
242 "only one variable can be declared in the head of a for-in loop",
243 )
244 }
245 },
246 Node::Assign(_) => {
247 return context.throw_syntax_error(
248 "a declaration in the head of a for-in loop can't have an initializer",
249 );
250 }
251 _ => {
252 return context
253 .throw_syntax_error("unknown left hand side in head of for-in loop")
254 }
255 }
256
257 result = self.body().run(context)?;
258 match context.executor().get_current_state() {
259 InterpreterState::Break(label) => {
260 handle_state_with_labels!(self, label, context, break);
261 break;
262 }
263 InterpreterState::Continue(label) => {
264 handle_state_with_labels!(self, label, context, continue);
265 }
266 InterpreterState::Return => return Ok(result),
267 InterpreterState::Executing => {
268 }
270 }
271 let _ = context.pop_environment();
272 }
273 Ok(result)
274 }
275}