boa/builtins/map/
map_iterator.rs

1use crate::{
2    builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
3    object::{JsObject, ObjectData},
4    property::{PropertyDescriptor, PropertyNameKind},
5    symbol::WellKnownSymbols,
6    BoaProfiler, Context, JsResult,
7};
8use gc::{Finalize, Trace};
9
10use super::ordered_map::MapLock;
11/// The Map Iterator object represents an iteration over a map. It implements the iterator protocol.
12///
13/// More information:
14///  - [ECMAScript reference][spec]
15///
16/// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects
17#[derive(Debug, Clone, Finalize, Trace)]
18pub struct MapIterator {
19    iterated_map: Option<JsObject>,
20    map_next_index: usize,
21    map_iteration_kind: PropertyNameKind,
22    lock: MapLock,
23}
24
25impl MapIterator {
26    pub(crate) const NAME: &'static str = "MapIterator";
27
28    /// Abstract operation CreateMapIterator( map, kind )
29    ///
30    /// Creates a new iterator over the given map.
31    ///
32    /// More information:
33    ///  - [ECMA reference][spec]
34    ///
35    /// [spec]: https://www.ecma-international.org/ecma-262/11.0/index.html#sec-createmapiterator
36    pub(crate) fn create_map_iterator(
37        map: &JsValue,
38        kind: PropertyNameKind,
39        context: &mut Context,
40    ) -> JsResult<JsValue> {
41        if let Some(map_obj) = map.as_object() {
42            if let Some(map) = map_obj.borrow_mut().as_map_mut() {
43                let lock = map.lock(map_obj.clone());
44                let iter = MapIterator {
45                    iterated_map: Some(map_obj.clone()),
46                    map_next_index: 0,
47                    map_iteration_kind: kind,
48                    lock,
49                };
50                let map_iterator = JsValue::new_object(context);
51                map_iterator.set_data(ObjectData::map_iterator(iter));
52                map_iterator
53                    .as_object()
54                    .expect("map iterator object")
55                    .set_prototype_instance(context.iterator_prototypes().map_iterator().into());
56                return Ok(map_iterator);
57            }
58        }
59        context.throw_type_error("`this` is not a Map")
60    }
61
62    /// %MapIteratorPrototype%.next( )
63    ///
64    /// Advances the iterator and gets the next result in the map.
65    ///
66    /// More information:
67    ///  - [ECMA reference][spec]
68    ///
69    /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next
70    pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
71        let iterator_object = match this {
72            JsValue::Object(obj) if obj.borrow().is_map_iterator() => obj,
73            _ => return context.throw_type_error("`this` is not a MapIterator"),
74        };
75
76        let mut iterator_object = iterator_object.borrow_mut();
77
78        let map_iterator = iterator_object
79            .as_map_iterator_mut()
80            .expect("checked that obj was a map iterator");
81
82        let mut index = map_iterator.map_next_index;
83        let item_kind = map_iterator.map_iteration_kind;
84
85        if let Some(obj) = map_iterator.iterated_map.take() {
86            let map = obj.borrow();
87            let entries = map.as_map_ref().expect("iterator should only iterate maps");
88            let num_entries = entries.full_len();
89            while index < num_entries {
90                let e = entries.get_index(index);
91                index += 1;
92                map_iterator.map_next_index = index;
93                if let Some((key, value)) = e {
94                    let item = match item_kind {
95                        PropertyNameKind::Key => {
96                            Ok(create_iter_result_object(key.clone(), false, context))
97                        }
98                        PropertyNameKind::Value => {
99                            Ok(create_iter_result_object(value.clone(), false, context))
100                        }
101                        PropertyNameKind::KeyAndValue => {
102                            let result = Array::create_array_from_list(
103                                [key.clone(), value.clone()],
104                                context,
105                            );
106                            Ok(create_iter_result_object(result.into(), false, context))
107                        }
108                    };
109                    drop(map);
110                    map_iterator.iterated_map = Some(obj);
111                    return item;
112                }
113            }
114        }
115
116        Ok(create_iter_result_object(
117            JsValue::undefined(),
118            true,
119            context,
120        ))
121    }
122
123    /// Create the %MapIteratorPrototype% object
124    ///
125    /// More information:
126    ///  - [ECMA reference][spec]
127    ///
128    /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%-object
129    pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
130        let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
131
132        // Create prototype
133        let map_iterator = context.construct_object();
134        make_builtin_fn(Self::next, "next", &map_iterator, 0, context);
135        map_iterator.set_prototype_instance(iterator_prototype);
136
137        let to_string_tag = WellKnownSymbols::to_string_tag();
138        let to_string_tag_property = PropertyDescriptor::builder()
139            .value("Map Iterator")
140            .writable(false)
141            .enumerable(false)
142            .configurable(true);
143        map_iterator.insert(to_string_tag, to_string_tag_property);
144        map_iterator
145    }
146}