boa/builtins/string/
string_iterator.rs

1use crate::{
2    builtins::{
3        function::make_builtin_fn, iterable::create_iter_result_object, string::code_point_at,
4    },
5    gc::{Finalize, Trace},
6    object::{JsObject, ObjectData},
7    property::PropertyDescriptor,
8    symbol::WellKnownSymbols,
9    BoaProfiler, Context, JsResult, JsValue,
10};
11
12#[derive(Debug, Clone, Finalize, Trace)]
13pub struct StringIterator {
14    string: JsValue,
15    next_index: i32,
16}
17
18impl StringIterator {
19    fn new(string: JsValue) -> Self {
20        Self {
21            string,
22            next_index: 0,
23        }
24    }
25
26    pub fn create_string_iterator(string: JsValue, context: &mut Context) -> JsResult<JsValue> {
27        let string_iterator = JsValue::new_object(context);
28        string_iterator.set_data(ObjectData::string_iterator(Self::new(string)));
29        string_iterator
30            .as_object()
31            .expect("array iterator object")
32            .set_prototype_instance(context.iterator_prototypes().string_iterator().into());
33        Ok(string_iterator)
34    }
35
36    pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
37        if let JsValue::Object(ref object) = this {
38            let mut object = object.borrow_mut();
39            if let Some(string_iterator) = object.as_string_iterator_mut() {
40                if string_iterator.string.is_undefined() {
41                    return Ok(create_iter_result_object(
42                        JsValue::undefined(),
43                        true,
44                        context,
45                    ));
46                }
47                let native_string = string_iterator.string.to_string(context)?;
48                let len = native_string.encode_utf16().count() as i32;
49                let position = string_iterator.next_index;
50                if position >= len {
51                    string_iterator.string = JsValue::undefined();
52                    return Ok(create_iter_result_object(
53                        JsValue::undefined(),
54                        true,
55                        context,
56                    ));
57                }
58                let (_, code_unit_count, _) =
59                    code_point_at(native_string, position).expect("Invalid code point position");
60                string_iterator.next_index += code_unit_count as i32;
61                let result_string = crate::builtins::string::String::substring(
62                    &string_iterator.string,
63                    &[position.into(), string_iterator.next_index.into()],
64                    context,
65                )?;
66                Ok(create_iter_result_object(result_string, false, context))
67            } else {
68                context.throw_type_error("`this` is not an ArrayIterator")
69            }
70        } else {
71            context.throw_type_error("`this` is not an ArrayIterator")
72        }
73    }
74
75    /// Create the %ArrayIteratorPrototype% object
76    ///
77    /// More information:
78    ///  - [ECMA reference][spec]
79    ///
80    /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
81    pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
82        let _timer = BoaProfiler::global().start_event("String Iterator", "init");
83
84        // Create prototype
85        let array_iterator = context.construct_object();
86        make_builtin_fn(Self::next, "next", &array_iterator, 0, context);
87        array_iterator.set_prototype_instance(iterator_prototype);
88
89        let to_string_tag = WellKnownSymbols::to_string_tag();
90        let to_string_tag_property = PropertyDescriptor::builder()
91            .value("String Iterator")
92            .writable(false)
93            .enumerable(false)
94            .configurable(true);
95        array_iterator.insert(to_string_tag, to_string_tag_property);
96        array_iterator
97    }
98}