use crate::array::TypedArray;
use crate::error::SyntaxError;
use crate::string::JsString;
use crate::utils::bind;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Clone, Debug)]
#[repr(transparent)]
pub struct RegExp {
inner: emlite::Val,
}
bind!(RegExp);
#[derive(Debug, Clone, Copy)]
pub enum RegExpFlags {
Global,
IgnoreCase,
Multiline,
DotAll,
Unicode,
Sticky,
}
impl RegExpFlags {
fn to_string(flags: &[RegExpFlags]) -> String {
let mut result = String::new();
for flag in flags {
match flag {
RegExpFlags::Global => result.push('g'),
RegExpFlags::IgnoreCase => result.push('i'),
RegExpFlags::Multiline => result.push('m'),
RegExpFlags::DotAll => result.push('s'),
RegExpFlags::Unicode => result.push('u'),
RegExpFlags::Sticky => result.push('y'),
}
}
result
}
}
fn new_syntax_error(msg: &str) -> SyntaxError {
let ctor = emlite::Val::global("SyntaxError");
ctor.invoke(&[msg.into()]).as_::<SyntaxError>()
}
impl RegExp {
pub fn new(pattern: &str, flags: Option<&[RegExpFlags]>) -> Result<Self, SyntaxError> {
let ctor = emlite::Val::global("RegExp");
let result = match flags {
Some(f) => {
let flags_str = RegExpFlags::to_string(f);
ctor.invoke(&[pattern.into(), flags_str.into()])
}
None => ctor.invoke(&[pattern.into()]),
};
result.as_::<Result<Self, SyntaxError>>()
}
pub fn test(&self, text: &str) -> bool {
self.inner.call("test", &[text.into()]).as_::<bool>()
}
pub fn exec(&self, text: &str) -> Option<TypedArray<JsString>> {
let result = self.inner.call("exec", &[text.into()]);
if result.is_null() {
None
} else {
Some(result.as_::<TypedArray<JsString>>())
}
}
pub fn contains(&self, text: &str) -> bool {
self.test(text)
}
pub fn find(&self, text: &str) -> Option<TypedArray<JsString>> {
self.exec(text)
}
pub fn source(&self) -> Option<String> {
self.inner.get("source").as_::<Option<String>>()
}
pub fn flags(&self) -> Option<String> {
self.inner.get("flags").as_::<Option<String>>()
}
pub fn global(&self) -> bool {
self.inner.get("global").as_::<bool>()
}
pub fn ignore_case(&self) -> bool {
self.inner.get("ignoreCase").as_::<bool>()
}
pub fn multiline(&self) -> bool {
self.inner.get("multiline").as_::<bool>()
}
pub fn dot_all(&self) -> bool {
self.inner.get("dotAll").as_::<bool>()
}
pub fn unicode(&self) -> bool {
self.inner.get("unicode").as_::<bool>()
}
pub fn sticky(&self) -> bool {
self.inner.get("sticky").as_::<bool>()
}
pub fn last_index(&self) -> i32 {
self.inner.get("lastIndex").as_::<i32>()
}
pub fn set_last_index(&self, index: i32) {
self.inner.set("lastIndex", index);
}
pub fn to_string_repr(&self) -> Option<String> {
self.inner.call("toString", &[]).as_::<Option<String>>()
}
pub fn literal(text: &str) -> Result<Self, SyntaxError> {
let escaped = Self::escape(text);
Self::new(&escaped, None)
}
pub fn new_case_insensitive(pattern: &str) -> Result<Self, SyntaxError> {
Self::new(pattern, Some(&[RegExpFlags::IgnoreCase]))
}
pub fn new_global(pattern: &str) -> Result<Self, SyntaxError> {
Self::new(pattern, Some(&[RegExpFlags::Global]))
}
pub fn new_global_ignore_case(pattern: &str) -> Result<Self, SyntaxError> {
Self::new(
pattern,
Some(&[RegExpFlags::Global, RegExpFlags::IgnoreCase]),
)
}
pub fn new_multiline(pattern: &str) -> Result<Self, SyntaxError> {
Self::new(pattern, Some(&[RegExpFlags::Multiline]))
}
pub fn find_all(&self, text: &str) -> Result<Vec<TypedArray<JsString>>, SyntaxError> {
if !self.global() {
return Err(new_syntax_error(
"RegExp must have global flag for find_all",
));
}
let mut out = Vec::new();
self.set_last_index(0);
loop {
let result = self.inner.call("exec", &[text.into()]);
if result.is_null() {
break;
}
out.push(result.as_::<TypedArray<JsString>>());
}
Ok(out)
}
pub fn begin(&self, text: &str) -> Result<MatchIter, SyntaxError> {
if !self.global() {
return Err(new_syntax_error(
"RegExp must have global flag for iteration",
));
}
let it = MatchIter {
regexp: self.inner.clone(),
text: text.to_string(),
at_end: false,
};
it.regexp.set("lastIndex", 0);
Ok(it)
}
pub fn escape(text: &str) -> String {
text.chars()
.map(|c| match c {
'\\' | '[' | ']' | '(' | ')' | '{' | '}' | '?' | '+' | '*' | '|' | '^' | '$'
| '.' => {
format!("\\{}", c)
}
_ => c.to_string(),
})
.collect()
}
}
pub struct MatchIter {
regexp: emlite::Val,
text: String,
at_end: bool,
}
impl Iterator for MatchIter {
type Item = TypedArray<JsString>;
fn next(&mut self) -> Option<Self::Item> {
if self.at_end {
return None;
}
let result = self.regexp.call("exec", &[self.text.as_str().into()]);
if result.is_null() {
self.at_end = true;
None
} else {
Some(result.as_::<TypedArray<JsString>>())
}
}
}
impl crate::prelude::DynCast for RegExp {
fn instanceof(val: &emlite::Val) -> bool {
let regexp_ctor = emlite::Val::global("RegExp");
val.instanceof(regexp_ctor)
}
fn unchecked_from_val(v: emlite::Val) -> Self {
Self { inner: v }
}
fn unchecked_from_val_ref(v: &emlite::Val) -> &Self {
unsafe { &*(v as *const emlite::Val as *const Self) }
}
fn unchecked_from_val_mut(v: &mut emlite::Val) -> &mut Self {
unsafe { &mut *(v as *mut emlite::Val as *mut Self) }
}
}
impl core::fmt::Display for RegExp {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.to_string_repr().unwrap_or_default())
}
}