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}