use regexp::{advance_string_index, RegExp};
use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, regexp},
gc::{Finalize, Trace},
object::{JsObject, ObjectData},
property::PropertyDescriptor,
symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, JsString, JsValue,
};
#[derive(Debug, Clone, Finalize, Trace)]
pub struct RegExpStringIterator {
matcher: JsValue,
string: JsString,
global: bool,
unicode: bool,
completed: bool,
}
impl RegExpStringIterator {
fn new(matcher: JsValue, string: JsString, global: bool, unicode: bool) -> Self {
Self {
matcher,
string,
global,
unicode,
completed: false,
}
}
pub(crate) fn create_regexp_string_iterator(
matcher: &JsValue,
string: JsString,
global: bool,
unicode: bool,
context: &mut Context,
) -> JsResult<JsValue> {
let regexp_string_iterator = JsValue::new_object(context);
regexp_string_iterator.set_data(ObjectData::reg_exp_string_iterator(Self::new(
matcher.clone(),
string,
global,
unicode,
)));
regexp_string_iterator
.as_object()
.expect("regexp string iterator object")
.set_prototype_instance(
context
.iterator_prototypes()
.regexp_string_iterator()
.into(),
);
Ok(regexp_string_iterator)
}
pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
if let JsValue::Object(ref object) = this {
let mut object = object.borrow_mut();
if let Some(iterator) = object.as_regexp_string_iterator_mut() {
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_field("lastIndex", context)?
.to_length(context)?;
let next_index = advance_string_index(
iterator.string.clone(),
this_index,
iterator.unicode,
);
iterator
.matcher
.set_field("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,
))
}
} else {
context.throw_type_error("`this` is not a RegExpStringIterator")
}
} else {
context.throw_type_error("`this` is not a RegExpStringIterator")
}
}
pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject {
let _timer = BoaProfiler::global().start_event("RegExp String Iterator", "init");
let result = context.construct_object();
make_builtin_fn(Self::next, "next", &result, 0, context);
result.set_prototype_instance(iterator_prototype);
let to_string_tag = WellKnownSymbols::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder()
.value("RegExp String Iterator")
.writable(false)
.enumerable(false)
.configurable(true);
result.insert(to_string_tag, to_string_tag_property);
result
}
}