boa/builtins/array/
array_iterator.rs

1use crate::{
2    builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
3    gc::{Finalize, Trace},
4    object::{JsObject, ObjectData},
5    property::{PropertyDescriptor, PropertyNameKind},
6    symbol::WellKnownSymbols,
7    BoaProfiler, Context, JsResult,
8};
9
10/// The Array Iterator object represents an iteration over an array. It implements the iterator protocol.
11///
12/// More information:
13///  - [ECMAScript reference][spec]
14///
15/// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects
16#[derive(Debug, Clone, Finalize, Trace)]
17pub struct ArrayIterator {
18    array: JsValue,
19    next_index: u32,
20    kind: PropertyNameKind,
21}
22
23impl ArrayIterator {
24    pub(crate) const NAME: &'static str = "ArrayIterator";
25
26    fn new(array: JsValue, kind: PropertyNameKind) -> Self {
27        ArrayIterator {
28            array,
29            kind,
30            next_index: 0,
31        }
32    }
33
34    /// CreateArrayIterator( array, kind )
35    ///
36    /// Creates a new iterator over the given array.
37    ///
38    /// More information:
39    ///  - [ECMA reference][spec]
40    ///
41    /// [spec]: https://tc39.es/ecma262/#sec-createarrayiterator
42    pub(crate) fn create_array_iterator(
43        array: JsValue,
44        kind: PropertyNameKind,
45        context: &Context,
46    ) -> JsValue {
47        let array_iterator = JsValue::new_object(context);
48        array_iterator.set_data(ObjectData::array_iterator(Self::new(array, kind)));
49        array_iterator
50            .as_object()
51            .expect("array iterator object")
52            .set_prototype_instance(context.iterator_prototypes().array_iterator().into());
53        array_iterator
54    }
55
56    /// %ArrayIteratorPrototype%.next( )
57    ///
58    /// Gets the next result in the array.
59    ///
60    /// More information:
61    ///  - [ECMA reference][spec]
62    ///
63    /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next
64    pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
65        if let JsValue::Object(ref object) = this {
66            let mut object = object.borrow_mut();
67            if let Some(array_iterator) = object.as_array_iterator_mut() {
68                let index = array_iterator.next_index;
69                if array_iterator.array.is_undefined() {
70                    return Ok(create_iter_result_object(
71                        JsValue::undefined(),
72                        true,
73                        context,
74                    ));
75                }
76                let len = array_iterator
77                    .array
78                    .get_field("length", context)?
79                    .as_number()
80                    .ok_or_else(|| context.construct_type_error("Not an array"))?
81                    as u32;
82                if array_iterator.next_index >= len {
83                    array_iterator.array = JsValue::undefined();
84                    return Ok(create_iter_result_object(
85                        JsValue::undefined(),
86                        true,
87                        context,
88                    ));
89                }
90                array_iterator.next_index = index + 1;
91                return match array_iterator.kind {
92                    PropertyNameKind::Key => {
93                        Ok(create_iter_result_object(index.into(), false, context))
94                    }
95                    PropertyNameKind::Value => {
96                        let element_value = array_iterator.array.get_field(index, context)?;
97                        Ok(create_iter_result_object(element_value, false, context))
98                    }
99                    PropertyNameKind::KeyAndValue => {
100                        let element_value = array_iterator.array.get_field(index, context)?;
101                        let result =
102                            Array::create_array_from_list([index.into(), element_value], context);
103                        Ok(create_iter_result_object(result.into(), false, context))
104                    }
105                };
106            }
107        }
108        context.throw_type_error("`this` is not an ArrayIterator")
109    }
110
111    /// Create the %ArrayIteratorPrototype% object
112    ///
113    /// More information:
114    ///  - [ECMA reference][spec]
115    ///
116    /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
117    pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
118        let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
119
120        // Create prototype
121        let array_iterator = context.construct_object();
122        make_builtin_fn(Self::next, "next", &array_iterator, 0, context);
123        array_iterator.set_prototype_instance(iterator_prototype);
124
125        let to_string_tag = WellKnownSymbols::to_string_tag();
126        let to_string_tag_property = PropertyDescriptor::builder()
127            .value("Array Iterator")
128            .writable(false)
129            .enumerable(false)
130            .configurable(true);
131        array_iterator.insert(to_string_tag, to_string_tag_property);
132        array_iterator
133    }
134}