boa/syntax/ast/node/iteration/for_in_loop/
mod.rs

1use 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                        // Binding already exists
117                        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                    // Continue execution.
269                }
270            }
271            let _ = context.pop_environment();
272        }
273        Ok(result)
274    }
275}