use crate::error::JsError;
use crate::gc::Gc;
use crate::interpreter::Interpreter;
use crate::platform::CompiledRegex;
use crate::prelude::{Rc, String, ToString, Vec};
use crate::value::{ExoticObject, Guarded, JsObject, JsString, JsValue, PropertyKey};
pub fn init_regexp_prototype(interp: &mut Interpreter) {
let proto = interp.regexp_prototype.clone();
interp.register_method(&proto, "test", regexp_test, 1);
interp.register_method(&proto, "exec", regexp_exec, 1);
}
pub fn create_regexp_constructor(interp: &mut Interpreter) -> Gc<JsObject> {
let constructor = interp.create_native_function("RegExp", regexp_constructor, 2);
let proto_key = PropertyKey::String(interp.intern("prototype"));
constructor
.borrow_mut()
.set_property(proto_key, JsValue::Object(interp.regexp_prototype.clone()));
let constructor_key = PropertyKey::String(interp.intern("constructor"));
interp
.regexp_prototype
.borrow_mut()
.set_property(constructor_key, JsValue::Object(constructor.clone()));
interp.register_species_getter(&constructor);
constructor
}
pub fn regexp_constructor(
interp: &mut Interpreter,
_this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let empty = interp.intern("");
let pattern_arg = args
.first()
.cloned()
.unwrap_or(JsValue::String(empty.clone()));
let pattern = interp.to_js_string(&pattern_arg).to_string();
let flags_arg = args.get(1).cloned().unwrap_or(JsValue::String(empty));
let flags = interp.to_js_string(&flags_arg).to_string();
let compiled = interp.compile_regexp(&pattern, &flags)?;
let source_key = PropertyKey::String(interp.intern("source"));
let flags_key = PropertyKey::String(interp.intern("flags"));
let global_key = PropertyKey::String(interp.intern("global"));
let ignore_case_key = PropertyKey::String(interp.intern("ignoreCase"));
let multiline_key = PropertyKey::String(interp.intern("multiline"));
let dot_all_key = PropertyKey::String(interp.intern("dotAll"));
let unicode_key = PropertyKey::String(interp.intern("unicode"));
let sticky_key = PropertyKey::String(interp.intern("sticky"));
let last_index_key = PropertyKey::String(interp.intern("lastIndex"));
let guard = interp.heap.create_guard();
let regexp_obj = interp.create_object(&guard);
{
let mut obj = regexp_obj.borrow_mut();
obj.exotic = ExoticObject::RegExp {
pattern: pattern.clone(),
flags: flags.clone(),
compiled: Some(compiled),
};
obj.prototype = Some(interp.regexp_prototype.clone());
obj.set_property(source_key, JsValue::String(JsString::from(pattern)));
obj.set_property(flags_key, JsValue::String(JsString::from(flags.clone())));
obj.set_property(global_key, JsValue::Boolean(flags.contains('g')));
obj.set_property(ignore_case_key, JsValue::Boolean(flags.contains('i')));
obj.set_property(multiline_key, JsValue::Boolean(flags.contains('m')));
obj.set_property(dot_all_key, JsValue::Boolean(flags.contains('s')));
obj.set_property(unicode_key, JsValue::Boolean(flags.contains('u')));
obj.set_property(sticky_key, JsValue::Boolean(flags.contains('y')));
obj.set_property(last_index_key, JsValue::Number(0.0));
}
Ok(Guarded::with_guard(JsValue::Object(regexp_obj), guard))
}
pub fn get_regexp_data(this: &JsValue) -> Result<(String, String), JsError> {
let JsValue::Object(obj) = this else {
return Err(JsError::type_error("this is not a RegExp"));
};
let obj_ref = obj.borrow();
if let ExoticObject::RegExp {
ref pattern,
ref flags,
..
} = obj_ref.exotic
{
Ok((pattern.clone(), flags.clone()))
} else {
Err(JsError::type_error("this is not a RegExp"))
}
}
pub fn get_compiled_regexp(
interp: &Interpreter,
obj: &Gc<JsObject>,
) -> Result<Rc<dyn CompiledRegex>, JsError> {
let obj_ref = obj.borrow();
if let ExoticObject::RegExp {
ref pattern,
ref flags,
ref compiled,
..
} = obj_ref.exotic
{
if let Some(compiled) = compiled {
return Ok(compiled.clone());
}
let pattern = pattern.clone();
let flags = flags.clone();
drop(obj_ref);
interp.compile_regexp(&pattern, &flags)
} else {
Err(JsError::type_error("this is not a RegExp"))
}
}
pub fn regexp_test(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let JsValue::Object(ref obj) = this else {
return Err(JsError::type_error("this is not a RegExp"));
};
let re = get_compiled_regexp(interp, obj)?;
let input_arg = args.first().cloned().unwrap_or(JsValue::Undefined);
let input = interp.coerce_to_string(&input_arg)?.to_string();
let is_match = re
.is_match(&input)
.map_err(|e| JsError::syntax_error(e, 0, 0))?;
Ok(Guarded::unguarded(JsValue::Boolean(is_match)))
}
pub fn regexp_exec(
interp: &mut Interpreter,
this: JsValue,
args: &[JsValue],
) -> Result<Guarded, JsError> {
let JsValue::Object(ref obj) = this else {
return Err(JsError::type_error("this is not a RegExp"));
};
let (_, flags) = get_regexp_data(&this)?;
let re = get_compiled_regexp(interp, obj)?;
let input_arg = args.first().cloned().unwrap_or(JsValue::Undefined);
let input = interp.coerce_to_string(&input_arg)?.to_string();
let is_global = flags.contains('g');
let is_sticky = flags.contains('y');
let last_index_key = PropertyKey::String(interp.intern("lastIndex"));
let index_key = PropertyKey::String(interp.intern("index"));
let input_key = PropertyKey::String(interp.intern("input"));
let last_index = if is_global || is_sticky {
let li = obj
.borrow()
.get_property(&last_index_key)
.unwrap_or(JsValue::Number(0.0));
match li {
JsValue::Number(n) => n as usize,
_ => 0,
}
} else {
0
};
if last_index > input.len() {
if is_global || is_sticky {
obj.borrow_mut()
.set_property(last_index_key, JsValue::Number(0.0));
}
return Ok(Guarded::unguarded(JsValue::Null));
}
let match_result = re
.find(&input, last_index)
.map_err(|e| JsError::syntax_error(e, 0, 0))?;
match match_result {
Some(regex_match) => {
let mut result = Vec::new();
for capture in ®ex_match.captures {
match capture {
Some((start, end)) => {
let s = input.get(*start..*end).unwrap_or("");
result.push(JsValue::String(JsString::from(s)));
}
None => result.push(JsValue::Undefined),
}
}
let guard = interp.heap.create_guard();
let arr = interp.create_array_from(&guard, result);
arr.borrow_mut()
.set_property(index_key, JsValue::Number(regex_match.start as f64));
arr.borrow_mut()
.set_property(input_key, JsValue::String(JsString::from(input.clone())));
if is_global || is_sticky {
obj.borrow_mut()
.set_property(last_index_key, JsValue::Number(regex_match.end as f64));
}
Ok(Guarded::with_guard(JsValue::Object(arr), guard))
}
None => {
if is_global || is_sticky {
obj.borrow_mut()
.set_property(last_index_key, JsValue::Number(0.0));
}
Ok(Guarded::unguarded(JsValue::Null))
}
}
}