use std::{
borrow::Cow,
cmp::Ordering,
convert::TryFrom,
ffi::{CStr, CString},
fmt,
iter::FromIterator,
os::raw::{c_int, c_long},
str::Utf8Error,
string,
};
use crate::{
object::{NonNullObject, Ty},
prelude::*,
ruby,
};
mod encoding;
pub use encoding::*;
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct String(NonNullObject);
impl fmt::Display for String {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
unsafe { self.to_str_lossy().fmt(f) }
}
}
unsafe impl Object for String {
#[inline]
fn unique_id() -> Option<u128> {
Some(!(Ty::STRING.id() as u128))
}
#[inline]
fn cast<A: Object>(obj: A) -> Option<Self> {
Some(obj.to_s())
}
#[inline]
fn ty(self) -> Ty { Ty::STRING }
#[inline]
fn is_ty(self, ty: Ty) -> bool { ty == Ty::STRING }
}
impl AsRef<AnyObject> for String {
#[inline]
fn as_ref(&self) -> &AnyObject { self.0.as_ref() }
}
impl From<String> for AnyObject {
#[inline]
fn from(object: String) -> AnyObject { object.0.into() }
}
macro_rules! forward_from {
($($t:ty,)+) => { $(
impl From<$t> for AnyObject {
#[inline]
fn from(string: $t) -> Self {
String::from(string).into()
}
}
)+ }
}
forward_from! {
&str, &std::string::String,
&[u8], &Vec<u8>,
&CStr, &CString,
}
impl From<&str> for String {
#[inline]
fn from(s: &str) -> String {
unsafe { String::from_raw(ruby::rb_utf8_str_new(
s.as_ptr() as *const _,
s.len() as _,
)) }
}
}
impl From<&std::string::String> for String {
#[inline]
fn from(s: &std::string::String) -> String {
unsafe { String::from_raw(ruby::rb_utf8_str_new(
s.as_ptr() as *const _,
s.len() as _,
)) }
}
}
impl From<&CStr> for String {
#[inline]
fn from(s: &CStr) -> String {
s.to_bytes().into()
}
}
impl From<&CString> for String {
#[inline]
fn from(s: &CString) -> String {
s.as_c_str().into()
}
}
impl From<&[u8]> for String {
#[inline]
fn from(bytes: &[u8]) -> String {
let ptr = bytes.as_ptr();
let len = bytes.len();
unsafe { String::from_raw(ruby::rb_str_new(ptr as *const _, len as _)) }
}
}
impl From<&Vec<u8>> for String {
#[inline]
fn from(bytes: &Vec<u8>) -> String {
bytes.as_slice().into()
}
}
impl TryFrom<String> for std::string::String {
type Error = Utf8Error;
#[inline]
fn try_from(s: String) -> Result<Self, Self::Error> {
s.to_string()
}
}
impl FromIterator<char> for String {
#[inline]
fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower_bound, _) = iter.size_hint();
let string = String::with_capacity(lower_bound);
unsafe {
iter.into_iter().for_each(|c| string.push(c));
string.force_encoding(Encoding::utf8());
}
string
}
}
impl<'a> FromIterator<&'a char> for String {
#[inline]
fn from_iter<I: IntoIterator<Item = &'a char>>(iter: I) -> Self {
iter.into_iter().map(|&c| c).collect()
}
}
impl<'a> FromIterator<&'a str> for String {
#[inline]
fn from_iter<I: IntoIterator<Item = &'a str>>(iter: I) -> Self {
let string = String::new();
unsafe { iter.into_iter().for_each(|s| string.push_str(s)) };
string
}
}
impl<'a> FromIterator<&'a std::string::String> for String {
#[inline]
fn from_iter<I>(iter: I) -> Self
where I: IntoIterator<Item = &'a std::string::String>
{
iter.into_iter().map(|s| s.as_str()).collect()
}
}
impl<O: Object> PartialEq<O> for String {
#[inline]
fn eq(&self, obj: &O) -> bool {
let this = self.raw();
let that = obj.raw();
unsafe { ruby::rb_str_equal(this, that) != crate::util::FALSE_VALUE }
}
}
macro_rules! impl_eq {
($($t:ty, $bytes:ident;)+) => { $(
impl PartialEq<$t> for String {
#[inline]
fn eq(&self, other: &$t) -> bool {
unsafe { self.as_bytes() == other.$bytes() }
}
}
impl PartialEq<&$t> for String {
#[inline]
fn eq(&self, other: &&$t) -> bool {
*self == **other
}
}
impl PartialEq<String> for $t {
#[inline]
fn eq(&self, other: &String) -> bool {
other == self
}
}
impl PartialEq<String> for &$t {
#[inline]
fn eq(&self, other: &String) -> bool {
other == self
}
}
)+ }
}
impl_eq! {
[u8], as_ref;
Vec<u8>, as_slice;
str, as_bytes;
string::String, as_bytes;
CStr, to_bytes;
CString, to_bytes;
}
impl<S: ?Sized + Clone> PartialEq<Cow<'_, S>> for String
where String: PartialEq<S>
{
#[inline]
fn eq(&self, other: &Cow<'_, S>) -> bool {
self == AsRef::<S>::as_ref(other)
}
}
impl Eq for String {}
impl PartialOrd for String {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for String {
#[inline]
fn cmp(&self, other: &Self) -> Ordering {
unsafe { ruby::rb_str_cmp(self.raw(), other.raw()).cmp(&0) }
}
}
impl String {
#[inline]
pub(crate) fn rstring(self) -> *mut ruby::RString {
self.as_any_object()._ptr() as _
}
#[inline]
pub(crate) fn _enc_index(self) -> c_int {
unsafe { ruby::rb_enc_get_index(self.raw()) }
}
#[inline]
pub(crate) fn _enc_index_skip_ivar(self) -> c_int {
unsafe { (*self.rstring()).basic.encoding_index() }
}
#[inline]
pub fn new() -> Self {
Self::with_capacity(0)
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
unsafe { Self::from_raw(ruby::rb_str_buf_new(capacity as _)) }
}
#[inline]
pub unsafe fn with_encoding(s: impl AsRef<[u8]>, enc: Encoding) -> Self {
let s = s.as_ref();
String::from_raw(ruby::rb_external_str_new_with_enc(
s.as_ptr() as *const _,
s.len() as _,
enc._enc(),
))
}
#[inline]
pub fn duplicate(self) -> Self {
unsafe { Self::from_raw(ruby::rb_str_dup(self.raw())) }
}
#[inline]
pub fn encoding(self) -> Encoding {
Encoding::_from_index(self._enc_index())
}
#[inline]
pub unsafe fn force_encoding(self, encoding: Encoding) {
ruby::rb_enc_associate_index(self.raw(), encoding._index());
}
#[inline]
pub fn encoding_is_ascii_8bit(self) -> bool {
self._enc_index_skip_ivar() == ruby::rb_encoding::ascii_8bit_index()
}
#[inline]
pub fn encoding_is_utf8(self) -> bool {
self._enc_index_skip_ivar() == ruby::rb_encoding::utf8_index()
}
#[inline]
pub fn encoding_is_us_ascii(self) -> bool {
self._enc_index_skip_ivar() == ruby::rb_encoding::us_ascii_index()
}
#[inline]
pub unsafe fn as_bytes(&self) -> &[u8] {
let ptr = (*self.rstring()).start() as *const u8;
std::slice::from_raw_parts(ptr, self.len())
}
#[inline]
pub fn to_bytes(self) -> Vec<u8> {
unsafe { self.as_bytes().into() }
}
#[inline]
pub fn bytes_all<F>(self, f: F) -> bool
where F: FnMut(u8) -> bool
{
unsafe { self.as_bytes().iter().cloned().all(f) }
}
#[inline]
pub fn bytes_any<F>(self, f: F) -> bool
where F: FnMut(u8) -> bool
{
unsafe { self.as_bytes().iter().cloned().any(f) }
}
pub unsafe fn to_str(&self) -> Result<&str, Utf8Error> {
if self.encoding_is_utf8() {
return Ok(self.to_str_unchecked());
}
std::str::from_utf8(self.as_bytes())
}
pub unsafe fn to_str_lossy(&self) -> Cow<'_, str> {
if self.encoding_is_utf8() {
return Cow::Borrowed(self.to_str_unchecked());
}
std::string::String::from_utf8_lossy(self.as_bytes())
}
#[inline]
pub unsafe fn to_str_unchecked(&self) -> &str {
std::str::from_utf8_unchecked(self.as_bytes())
}
#[inline]
pub fn to_string(self) -> Result<std::string::String, Utf8Error> {
unsafe { Ok(self.to_str()?.into()) }
}
#[inline]
pub fn len(self) -> usize {
unsafe { (*self.rstring()).len() }
}
#[inline]
pub fn char_len(self) -> usize {
unsafe { ruby::rb_str_strlen(self.raw()) as usize }
}
#[inline]
pub fn is_empty(self) -> bool {
self.len() == 0
}
pub fn is_whitespace(self) -> bool {
unsafe {
if let Ok(s) = self.to_str() {
s.chars().all(|ch| ch.is_whitespace())
} else {
false
}
}
}
pub fn is_ascii_whitespace(self) -> bool {
self.bytes_all(|b| b.is_ascii_whitespace())
}
#[inline]
pub unsafe fn push(self, c: char) {
self.push_str(c.encode_utf8(&mut [0; 4]))
}
#[inline]
pub unsafe fn push_str(self, s: &str) {
ruby::rb_str_cat(self.raw(), s.as_ptr() as *const _, s.len() as _);
}
#[inline]
pub fn ellipsized(self, len: usize) -> Self {
if len > c_long::max_value() as usize {
return self.duplicate();
}
let len = len as c_long;
unsafe { Self::from_raw(ruby::rb_str_ellipsize(self.raw(), len)) }
}
#[inline]
pub fn is_locked(self) -> bool {
unsafe { (*self.rstring()).is_locked() }
}
#[inline]
#[must_use]
pub fn with_lock<F, O>(self, f: F) -> Option<O>
where F: FnOnce(Self) -> O
{
if self.is_locked() {
return None;
}
unsafe { self.raw_lock() };
let output = f(self);
unsafe { self.raw_unlock() };
Some(output)
}
#[inline]
pub unsafe fn raw_lock(self) {
ruby::rb_str_locktmp(self.raw());
}
#[inline]
pub unsafe fn raw_unlock(self) {
ruby::rb_str_unlocktmp(self.raw());
}
}
#[cfg(all(test, nightly))]
mod benches {
use test::{Bencher, black_box};
use super::*;
const STRING_MULTIPLE: usize = 10;
fn create_string() -> String {
let mut string = std::string::String::new();
for _ in 0..STRING_MULTIPLE {
string.push('a');
string.push('ñ');
string.push('ß');
}
String::from(&*string)
}
#[bench]
fn to_str(b: &mut Bencher) {
crate::vm::init().unwrap();
let string = create_string();
b.bytes = string.len() as u64;
b.iter(move || unsafe {
let f = black_box(String::to_str);
let _ = black_box(f(&black_box(string)));
});
}
#[bench]
fn to_str_checked(b: &mut Bencher) {
crate::vm::init().unwrap();
let string = create_string();
let enc = String::from("ASCII-8BIT");
string.call_with_protected("force_encoding", &[enc]).unwrap();
b.bytes = string.len() as u64;
b.iter(move || unsafe {
let f = black_box(String::to_str);
let _ = black_box(f(&black_box(string)));
});
}
#[bench]
fn to_str_no_lookup(b: &mut Bencher) {
crate::vm::init().unwrap();
unsafe fn to_str(s: &String) -> Result<&str, Utf8Error> {
std::str::from_utf8(s.as_bytes())
}
let string = create_string();
b.bytes = string.len() as u64;
b.iter(move || unsafe {
let f = black_box(to_str);
let _ = black_box(f(&black_box(string)));
});
}
}