use crate::{
Context, JsData, JsResult, JsString, JsValue,
builtins::{BuiltInBuilder, IntrinsicObject, iterable::create_iter_result_object, regexp},
context::intrinsics::Intrinsics,
error::JsNativeError,
js_string,
object::JsObject,
property::Attribute,
realm::Realm,
symbol::JsSymbol,
};
use boa_gc::{Finalize, Trace};
use regexp::{RegExp, advance_string_index};
#[derive(Debug, Clone, Finalize, Trace, JsData)]
pub(crate) struct RegExpStringIterator {
matcher: JsObject,
string: JsString,
global: bool,
unicode: bool,
completed: bool,
}
impl IntrinsicObject for RegExpStringIterator {
fn init(realm: &Realm) {
BuiltInBuilder::with_intrinsic::<Self>(realm)
.prototype(
realm
.intrinsics()
.objects()
.iterator_prototypes()
.iterator(),
)
.static_method(Self::next, js_string!("next"), 0)
.static_property(
JsSymbol::to_string_tag(),
js_string!("RegExp String Iterator"),
Attribute::CONFIGURABLE,
)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().iterator_prototypes().regexp_string()
}
}
impl RegExpStringIterator {
fn new(matcher: JsObject, string: JsString, global: bool, unicode: bool) -> Self {
Self {
matcher,
string,
global,
unicode,
completed: false,
}
}
pub(crate) fn create_regexp_string_iterator(
matcher: JsObject,
string: JsString,
global: bool,
unicode: bool,
context: &mut Context,
) -> JsValue {
let regexp_string_iterator = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
context
.intrinsics()
.objects()
.iterator_prototypes()
.regexp_string(),
Self::new(matcher, string, global, unicode),
);
regexp_string_iterator.into()
}
pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let object = this.as_object();
let mut iterator = object
.as_ref()
.and_then(JsObject::downcast_mut::<Self>)
.ok_or_else(|| {
JsNativeError::typ().with_message("`this` is not a RegExpStringIterator")
})?;
if iterator.completed {
return Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
));
}
let m = RegExp::abstract_exec(&iterator.matcher, iterator.string.clone(), context)?;
if let Some(m) = m {
if !iterator.global {
iterator.completed = true;
return Ok(create_iter_result_object(m.into(), false, context));
}
let m_str = m.get(0, context)?.to_string(context)?;
if m_str.is_empty() {
let this_index = iterator
.matcher
.get(js_string!("lastIndex"), context)?
.to_length(context)?;
let next_index =
advance_string_index(&iterator.string, this_index, iterator.unicode);
iterator
.matcher
.set(js_string!("lastIndex"), next_index, true, context)?;
}
Ok(create_iter_result_object(m.into(), false, context))
} else {
iterator.completed = true;
Ok(create_iter_result_object(
JsValue::undefined(),
true,
context,
))
}
}
}