mirror of https://github.com/boa-dev/boa.git
Haled Odat
1 year ago
6 changed files with 358 additions and 203 deletions
@ -0,0 +1,210 @@ |
|||||||
|
use crate::{builtins::string::is_trimmable_whitespace, JsString}; |
||||||
|
|
||||||
|
use super::{is_ascii, JsStr, JsStrVariant}; |
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
pub enum JsStringSliceVariant<'a> { |
||||||
|
U8Ascii(&'a str), |
||||||
|
U8NonAscii(&'a str, usize), |
||||||
|
U16Ascii(&'a [u16]), |
||||||
|
U16NonAscii(&'a [u16]), |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)] |
||||||
|
pub struct JsStringSlice<'a> { |
||||||
|
inner: JsStringSliceVariant<'a>, |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> JsStringSlice<'a> { |
||||||
|
pub(crate) unsafe fn u8_ascii_unchecked(value: &'a str) -> Self { |
||||||
|
debug_assert!(value.is_ascii(), "string must be ascii"); |
||||||
|
|
||||||
|
Self { |
||||||
|
inner: JsStringSliceVariant::U8Ascii(value), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) unsafe fn u16_ascii_unchecked(value: &'a [u16]) -> Self { |
||||||
|
debug_assert!(is_ascii(value), "string must be ascii"); |
||||||
|
|
||||||
|
Self { |
||||||
|
inner: JsStringSliceVariant::U16Ascii(value), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) unsafe fn u8_non_ascii_unchecked(value: &'a str) -> Self { |
||||||
|
debug_assert!(!value.is_ascii(), "string must not be ascii"); |
||||||
|
let len = value.encode_utf16().count(); |
||||||
|
|
||||||
|
Self { |
||||||
|
inner: JsStringSliceVariant::U8NonAscii(value, len), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) unsafe fn u16_non_ascii_unchecked(value: &'a [u16]) -> Self { |
||||||
|
debug_assert!(!is_ascii(value), "string must not be ascii"); |
||||||
|
|
||||||
|
Self { |
||||||
|
inner: JsStringSliceVariant::U16NonAscii(value), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub(crate) fn variant(self) -> JsStringSliceVariant<'a> { |
||||||
|
self.inner |
||||||
|
} |
||||||
|
|
||||||
|
pub fn len(&self) -> usize { |
||||||
|
match self.variant() { |
||||||
|
JsStringSliceVariant::U8Ascii(s) => s.len(), |
||||||
|
JsStringSliceVariant::U8NonAscii(_, len) => len, |
||||||
|
JsStringSliceVariant::U16NonAscii(s) | JsStringSliceVariant::U16Ascii(s) => s.len(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn is_ascii(&self) -> bool { |
||||||
|
matches!( |
||||||
|
self.variant(), |
||||||
|
JsStringSliceVariant::U8Ascii(_) | JsStringSliceVariant::U16Ascii(_) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
/// Trims both leading and trailing space.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub fn trim(&self) -> Self { |
||||||
|
self.trim_start().trim_end() |
||||||
|
} |
||||||
|
|
||||||
|
/// Trims all leading space.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub fn trim_start(&self) -> JsStringSlice<'a> { |
||||||
|
match self.variant() { |
||||||
|
JsStringSliceVariant::U8Ascii(s) => { |
||||||
|
// SAFETY: Calling `trim_start()` on ASCII string always returns ASCII string, so this is safe.
|
||||||
|
unsafe { JsStringSlice::u8_ascii_unchecked(s.trim_start()) } |
||||||
|
} |
||||||
|
JsStringSliceVariant::U8NonAscii(s, _) => JsStringSlice::from(s.trim_start()), |
||||||
|
JsStringSliceVariant::U16Ascii(s) => { |
||||||
|
let value = if let Some(left) = s.iter().copied().position(|r| { |
||||||
|
!char::from_u32(u32::from(r)) |
||||||
|
.map(is_trimmable_whitespace) |
||||||
|
.unwrap_or_default() |
||||||
|
}) { |
||||||
|
&s[left..] |
||||||
|
} else { |
||||||
|
// SAFETY: An empty string is valid ASCII, so this is safe.
|
||||||
|
return unsafe { JsStringSlice::u8_ascii_unchecked("") }; |
||||||
|
}; |
||||||
|
|
||||||
|
// SAFETY: Calling `trim_start()` on ASCII string always returns ASCII string, so this is safe.
|
||||||
|
unsafe { JsStringSlice::u16_ascii_unchecked(value) } |
||||||
|
} |
||||||
|
JsStringSliceVariant::U16NonAscii(s) => { |
||||||
|
let value = if let Some(left) = s.iter().copied().position(|r| { |
||||||
|
!char::from_u32(u32::from(r)) |
||||||
|
.map(is_trimmable_whitespace) |
||||||
|
.unwrap_or_default() |
||||||
|
}) { |
||||||
|
&s[left..] |
||||||
|
} else { |
||||||
|
// SAFETY: An empty string is valid ASCII, so this is safe.
|
||||||
|
return unsafe { JsStringSlice::u8_ascii_unchecked("") }; |
||||||
|
}; |
||||||
|
|
||||||
|
JsStringSlice::from(value) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Trims all trailing space.
|
||||||
|
#[inline] |
||||||
|
#[must_use] |
||||||
|
pub fn trim_end(&self) -> JsStringSlice<'a> { |
||||||
|
match self.variant() { |
||||||
|
JsStringSliceVariant::U8Ascii(s) => { |
||||||
|
// SAFETY: Calling `trim_start()` on ASCII string always returns ASCII string, so this is safe.
|
||||||
|
unsafe { JsStringSlice::u8_ascii_unchecked(s.trim_end()) } |
||||||
|
} |
||||||
|
JsStringSliceVariant::U8NonAscii(s, _) => JsStringSlice::from(s.trim_end()), |
||||||
|
JsStringSliceVariant::U16Ascii(s) => { |
||||||
|
let value = if let Some(right) = s.iter().copied().rposition(|r| { |
||||||
|
!char::from_u32(u32::from(r)) |
||||||
|
.map(is_trimmable_whitespace) |
||||||
|
.unwrap_or_default() |
||||||
|
}) { |
||||||
|
&s[..=right] |
||||||
|
} else { |
||||||
|
// SAFETY: An empty string is valid ASCII, so this is safe.
|
||||||
|
return unsafe { JsStringSlice::u8_ascii_unchecked("") }; |
||||||
|
}; |
||||||
|
|
||||||
|
// SAFETY: Calling `trim_start()` on ASCII string always returns ASCII string, so this is safe.
|
||||||
|
unsafe { JsStringSlice::u16_ascii_unchecked(value) } |
||||||
|
} |
||||||
|
JsStringSliceVariant::U16NonAscii(s) => { |
||||||
|
let value = if let Some(right) = s.iter().copied().rposition(|r| { |
||||||
|
!char::from_u32(u32::from(r)) |
||||||
|
.map(is_trimmable_whitespace) |
||||||
|
.unwrap_or_default() |
||||||
|
}) { |
||||||
|
&s[..=right] |
||||||
|
} else { |
||||||
|
// SAFETY: An empty string is valid ASCII, so this is safe.
|
||||||
|
return unsafe { JsStringSlice::u8_ascii_unchecked("") }; |
||||||
|
}; |
||||||
|
|
||||||
|
JsStringSlice::from(value) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub fn iter(self) -> crate::string::Iter<'a> { |
||||||
|
crate::string::Iter::new(self) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> From<&'a JsString> for JsStringSlice<'a> { |
||||||
|
fn from(value: &'a JsString) -> Self { |
||||||
|
Self::from(value.as_str()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> From<JsStr<'a>> for JsStringSlice<'a> { |
||||||
|
fn from(value: JsStr<'a>) -> Self { |
||||||
|
match value.variant() { |
||||||
|
JsStrVariant::Ascii(s) => { |
||||||
|
// SAFETY: `JsStrVariant::Ascii` always contains ASCII string, so this safe.
|
||||||
|
unsafe { Self::u8_ascii_unchecked(s) } |
||||||
|
} |
||||||
|
JsStrVariant::U16(s) => { |
||||||
|
// SAFETY: `JsStrVariant::Ascii` always contains non-ASCII string, so this safe.
|
||||||
|
unsafe { Self::u16_non_ascii_unchecked(s) } |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> From<&'a str> for JsStringSlice<'a> { |
||||||
|
fn from(value: &'a str) -> Self { |
||||||
|
if value.is_ascii() { |
||||||
|
// SAFETY: Already checked that it's ASCII, so this is safe.
|
||||||
|
return unsafe { Self::u8_ascii_unchecked(value) }; |
||||||
|
} |
||||||
|
|
||||||
|
// SAFETY: Already checked that it's non-ASCII, so this is safe.
|
||||||
|
unsafe { Self::u8_non_ascii_unchecked(value) } |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> From<&'a [u16]> for JsStringSlice<'a> { |
||||||
|
fn from(s: &'a [u16]) -> Self { |
||||||
|
if is_ascii(s) { |
||||||
|
// SAFETY: Already checked that it's ASCII, so this is safe.
|
||||||
|
return unsafe { Self::u16_ascii_unchecked(s) }; |
||||||
|
} |
||||||
|
|
||||||
|
// SAFETY: Already checked that it's non-ASCII, so this is safe.
|
||||||
|
unsafe { Self::u16_non_ascii_unchecked(s) } |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue