boa/builtins/regexp/
regexp_string_iterator.rs

1//! This module implements the global `RegExp String Iterator` object.
2//!
3//! A RegExp String Iterator is an object, that represents a specific iteration over some specific String instance object, matching against some specific RegExp instance object.
4//! There is not a named constructor for RegExp String Iterator objects.
5//! Instead, RegExp String Iterator objects are created by calling certain methods of RegExp instance objects.
6//!
7//! More information:
8//!  - [ECMAScript reference][spec]
9//!
10//! [spec]: https://tc39.es/ecma262/#sec-regexp-string-iterator-objects
11
12use regexp::{advance_string_index, RegExp};
13
14use crate::{
15    builtins::{function::make_builtin_fn, iterable::create_iter_result_object, regexp},
16    gc::{Finalize, Trace},
17    object::{JsObject, ObjectData},
18    property::PropertyDescriptor,
19    symbol::WellKnownSymbols,
20    BoaProfiler, Context, JsResult, JsString, JsValue,
21};
22
23// TODO: See todos in create_regexp_string_iterator and next.
24#[derive(Debug, Clone, Finalize, Trace)]
25pub struct RegExpStringIterator {
26    matcher: JsValue,
27    string: JsString,
28    global: bool,
29    unicode: bool,
30    completed: bool,
31}
32
33// TODO: See todos in create_regexp_string_iterator and next.
34impl RegExpStringIterator {
35    fn new(matcher: JsValue, string: JsString, global: bool, unicode: bool) -> Self {
36        Self {
37            matcher,
38            string,
39            global,
40            unicode,
41            completed: false,
42        }
43    }
44
45    /// `22.2.7.1 CreateRegExpStringIterator ( R, S, global, fullUnicode )`
46    ///
47    /// More information:
48    ///  - [ECMAScript reference][spec]
49    ///
50    /// [spec]: https://tc39.es/ecma262/#sec-createregexpstringiterator
51    pub(crate) fn create_regexp_string_iterator(
52        matcher: &JsValue,
53        string: JsString,
54        global: bool,
55        unicode: bool,
56        context: &mut Context,
57    ) -> JsResult<JsValue> {
58        // TODO: Implement this with closures and generators.
59        //       For now all values of the closure are stored in RegExpStringIterator and the actual closure execution is in `.next()`.
60
61        // 1. Assert: Type(S) is String.
62        // 2. Assert: Type(global) is Boolean.
63        // 3. Assert: Type(fullUnicode) is Boolean.
64
65        // 4. Let closure be a new Abstract Closure with no parameters that captures R, S, global,
66        //    and fullUnicode and performs the following steps when called:
67
68        // 5. Return ! CreateIteratorFromClosure(closure, "%RegExpStringIteratorPrototype%", %RegExpStringIteratorPrototype%).
69        let regexp_string_iterator = JsValue::new_object(context);
70        regexp_string_iterator.set_data(ObjectData::reg_exp_string_iterator(Self::new(
71            matcher.clone(),
72            string,
73            global,
74            unicode,
75        )));
76        regexp_string_iterator
77            .as_object()
78            .expect("regexp string iterator object")
79            .set_prototype_instance(
80                context
81                    .iterator_prototypes()
82                    .regexp_string_iterator()
83                    .into(),
84            );
85
86        Ok(regexp_string_iterator)
87    }
88
89    pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
90        if let JsValue::Object(ref object) = this {
91            let mut object = object.borrow_mut();
92            if let Some(iterator) = object.as_regexp_string_iterator_mut() {
93                if iterator.completed {
94                    return Ok(create_iter_result_object(
95                        JsValue::undefined(),
96                        true,
97                        context,
98                    ));
99                }
100
101                // TODO: This is the code that should be created as a closure in create_regexp_string_iterator.
102
103                // i. Let match be ? RegExpExec(R, S).
104                let m = RegExp::abstract_exec(&iterator.matcher, iterator.string.clone(), context)?;
105
106                if let Some(m) = m {
107                    // iii. If global is false, then
108                    if !iterator.global {
109                        // 1. Perform ? Yield(match).
110                        // 2. Return undefined.
111                        iterator.completed = true;
112                        return Ok(create_iter_result_object(m.into(), false, context));
113                    }
114
115                    // iv. Let matchStr be ? ToString(? Get(match, "0")).
116                    let m_str = m.get("0", context)?.to_string(context)?;
117
118                    // v. If matchStr is the empty String, then
119                    if m_str.is_empty() {
120                        // 1. Let thisIndex be ℝ(? ToLength(? Get(R, "lastIndex"))).
121                        let this_index = iterator
122                            .matcher
123                            .get_field("lastIndex", context)?
124                            .to_length(context)?;
125
126                        // 2. Let nextIndex be ! AdvanceStringIndex(S, thisIndex, fullUnicode).
127                        let next_index = advance_string_index(
128                            iterator.string.clone(),
129                            this_index,
130                            iterator.unicode,
131                        );
132
133                        // 3. Perform ? Set(R, "lastIndex", 𝔽(nextIndex), true).
134                        iterator
135                            .matcher
136                            .set_field("lastIndex", next_index, true, context)?;
137                    }
138
139                    // vi. Perform ? Yield(match).
140                    Ok(create_iter_result_object(m.into(), false, context))
141                } else {
142                    // ii. If match is null, return undefined.
143                    iterator.completed = true;
144                    Ok(create_iter_result_object(
145                        JsValue::undefined(),
146                        true,
147                        context,
148                    ))
149                }
150            } else {
151                context.throw_type_error("`this` is not a RegExpStringIterator")
152            }
153        } else {
154            context.throw_type_error("`this` is not a RegExpStringIterator")
155        }
156    }
157
158    /// Create the %ArrayIteratorPrototype% object
159    ///
160    /// More information:
161    ///  - [ECMA reference][spec]
162    ///
163    /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object
164    pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
165        let _timer = BoaProfiler::global().start_event("RegExp String Iterator", "init");
166
167        // Create prototype
168        let result = context.construct_object();
169        make_builtin_fn(Self::next, "next", &result, 0, context);
170        result.set_prototype_instance(iterator_prototype);
171
172        let to_string_tag = WellKnownSymbols::to_string_tag();
173        let to_string_tag_property = PropertyDescriptor::builder()
174            .value("RegExp String Iterator")
175            .writable(false)
176            .enumerable(false)
177            .configurable(true);
178        result.insert(to_string_tag, to_string_tag_property);
179        result
180    }
181}