Files
RustPython/vm/src/builtins/str.rs
T
2022-05-07 20:00:57 +09:00

1709 lines
52 KiB
Rust

use super::{
int::{PyInt, PyIntRef},
iter::IterStatus::{self, Exhausted},
PositionIterInternal, PyBytesRef, PyDict, PyTupleRef, PyTypeRef,
};
use crate::{
anystr::{self, adjust_indices, AnyStr, AnyStrContainer, AnyStrWrapper},
class::PyClassImpl,
convert::{ToPyException, ToPyObject},
format::{FormatSpec, FormatString, FromTemplate},
function::{ArgIterable, FuncArgs, OptionalArg, OptionalOption, PyComparisonValue},
protocol::{PyIterReturn, PyMappingMethods, PySequenceMethods},
sequence::SequenceOp,
sliceable::{SequenceIndex, SliceableSequenceOp},
types::{
AsMapping, AsSequence, Comparable, Constructor, Hashable, IterNext, IterNextIterable,
Iterable, PyComparisonOp, Unconstructible,
},
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
TryFromBorrowedObject, VirtualMachine,
};
use ascii::{AsciiStr, AsciiString};
use bstr::ByteSlice;
use itertools::Itertools;
use num_traits::ToPrimitive;
use rustpython_common::{
ascii,
atomic::{self, PyAtomic, Radium},
hash,
lock::PyMutex,
};
use std::{char, fmt, ops::Range, string::ToString};
use unic_ucd_bidi::BidiClass;
use unic_ucd_category::GeneralCategory;
use unic_ucd_ident::{is_xid_continue, is_xid_start};
use unicode_casing::CharExt;
/// Utf8 + state.ascii (+ PyUnicode_Kind in future)
#[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) enum PyStrKind {
Ascii,
Utf8,
}
impl std::ops::BitOr for PyStrKind {
type Output = Self;
fn bitor(self, other: Self) -> Self {
match (self, other) {
(Self::Ascii, Self::Ascii) => Self::Ascii,
_ => Self::Utf8,
}
}
}
impl PyStrKind {
fn new_data(self) -> PyStrKindData {
match self {
PyStrKind::Ascii => PyStrKindData::Ascii,
PyStrKind::Utf8 => PyStrKindData::Utf8(Radium::new(usize::MAX)),
}
}
}
#[derive(Debug)]
enum PyStrKindData {
Ascii,
// uses usize::MAX as a sentinel for "uncomputed"
Utf8(PyAtomic<usize>),
}
impl PyStrKindData {
fn kind(&self) -> PyStrKind {
match self {
PyStrKindData::Ascii => PyStrKind::Ascii,
PyStrKindData::Utf8(_) => PyStrKind::Utf8,
}
}
}
impl TryFromBorrowedObject for String {
fn try_from_borrowed_object(vm: &VirtualMachine, obj: &PyObject) -> PyResult<Self> {
obj.try_value_with(|pystr: &PyStr| Ok(pystr.as_str().to_owned()), vm)
}
}
/// str(object='') -> str
/// str(bytes_or_buffer[, encoding[, errors]]) -> str
///
/// Create a new string object from the given object. If encoding or
/// errors is specified, then the object must expose a data buffer
/// that will be decoded using the given encoding and error handler.
/// Otherwise, returns the result of object.__str__() (if defined)
/// or repr(object).
/// encoding defaults to sys.getdefaultencoding().
/// errors defaults to 'strict'."
#[pyclass(module = false, name = "str")]
pub struct PyStr {
bytes: Box<[u8]>,
kind: PyStrKindData,
hash: PyAtomic<hash::PyHash>,
}
impl fmt::Debug for PyStr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("PyStr")
.field("value", &self.as_str())
.field("kind", &self.kind)
.field("hash", &self.hash)
.finish()
}
}
impl AsRef<str> for PyStr {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl AsRef<str> for PyStrRef {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<'a> From<&'a AsciiStr> for PyStr {
fn from(s: &'a AsciiStr) -> Self {
s.to_owned().into()
}
}
impl From<AsciiString> for PyStr {
fn from(s: AsciiString) -> Self {
unsafe { Self::new_ascii_unchecked(s.into()) }
}
}
impl<'a> From<&'a str> for PyStr {
fn from(s: &'a str) -> Self {
s.to_owned().into()
}
}
impl From<String> for PyStr {
fn from(s: String) -> Self {
s.into_boxed_str().into()
}
}
impl<'a> From<std::borrow::Cow<'a, str>> for PyStr {
fn from(s: std::borrow::Cow<'a, str>) -> Self {
s.into_owned().into()
}
}
impl From<Box<str>> for PyStr {
#[inline]
fn from(value: Box<str>) -> Self {
// doing the check is ~10x faster for ascii, and is actually only 2% slower worst case for
// non-ascii; see https://github.com/RustPython/RustPython/pull/2586#issuecomment-844611532
let is_ascii = value.is_ascii();
let bytes = value.into_boxed_bytes();
let kind = if is_ascii {
PyStrKind::Ascii
} else {
PyStrKind::Utf8
}
.new_data();
Self {
bytes,
kind,
hash: Radium::new(hash::SENTINEL),
}
}
}
pub type PyStrRef = PyRef<PyStr>;
impl fmt::Display for PyStr {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self.as_str(), f)
}
}
pub trait IntoPyStrRef {
fn into_pystr_ref(self, vm: &VirtualMachine) -> PyStrRef;
}
impl IntoPyStrRef for PyStrRef {
#[inline]
fn into_pystr_ref(self, _vm: &VirtualMachine) -> PyRef<PyStr> {
self
}
}
impl IntoPyStrRef for PyStr {
#[inline]
fn into_pystr_ref(self, vm: &VirtualMachine) -> PyRef<PyStr> {
self.into_ref(vm)
}
}
impl IntoPyStrRef for AsciiString {
#[inline]
fn into_pystr_ref(self, vm: &VirtualMachine) -> PyRef<PyStr> {
PyStr::from(self).into_ref(vm)
}
}
impl IntoPyStrRef for String {
#[inline]
fn into_pystr_ref(self, vm: &VirtualMachine) -> PyRef<PyStr> {
PyStr::from(self).into_ref(vm)
}
}
impl IntoPyStrRef for &str {
#[inline]
fn into_pystr_ref(self, vm: &VirtualMachine) -> PyRef<PyStr> {
PyStr::from(self).into_ref(vm)
}
}
#[pyclass(module = false, name = "str_iterator")]
#[derive(Debug)]
pub struct PyStrIterator {
internal: PyMutex<(PositionIterInternal<PyStrRef>, usize)>,
}
impl PyPayload for PyStrIterator {
fn class(vm: &VirtualMachine) -> &PyTypeRef {
&vm.ctx.types.str_iterator_type
}
}
#[pyimpl(with(Constructor, IterNext))]
impl PyStrIterator {
#[pymethod(magic)]
fn length_hint(&self) -> usize {
self.internal.lock().0.length_hint(|obj| obj.char_len())
}
#[pymethod(magic)]
fn setstate(&self, state: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
let mut internal = self.internal.lock();
internal.1 = usize::MAX;
internal
.0
.set_state(state, |obj, pos| pos.min(obj.char_len()), vm)
}
#[pymethod(magic)]
fn reduce(&self, vm: &VirtualMachine) -> PyTupleRef {
self.internal
.lock()
.0
.builtins_iter_reduce(|x| x.clone().into(), vm)
}
}
impl Unconstructible for PyStrIterator {}
impl IterNextIterable for PyStrIterator {}
impl IterNext for PyStrIterator {
fn next(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
let mut internal = zelf.internal.lock();
if let IterStatus::Active(s) = &internal.0.status {
let value = s.as_str();
if internal.1 == usize::MAX {
if let Some((offset, ch)) = value.char_indices().nth(internal.0.position) {
internal.0.position += 1;
internal.1 = offset + ch.len_utf8();
return Ok(PyIterReturn::Return(ch.to_pyobject(vm)));
}
} else if let Some(value) = value.get(internal.1..) {
if let Some(ch) = value.chars().next() {
internal.0.position += 1;
internal.1 += ch.len_utf8();
return Ok(PyIterReturn::Return(ch.to_pyobject(vm)));
}
}
internal.0.status = Exhausted;
}
Ok(PyIterReturn::StopIteration(None))
}
}
#[derive(FromArgs)]
pub struct StrArgs {
#[pyarg(any, optional)]
object: OptionalArg<PyObjectRef>,
#[pyarg(any, optional)]
encoding: OptionalArg<PyStrRef>,
#[pyarg(any, optional)]
errors: OptionalArg<PyStrRef>,
}
impl Constructor for PyStr {
type Args = StrArgs;
fn py_new(cls: PyTypeRef, args: Self::Args, vm: &VirtualMachine) -> PyResult {
let string: PyStrRef = match args.object {
OptionalArg::Present(input) => {
if let OptionalArg::Present(enc) = args.encoding {
vm.state.codec_registry.decode_text(
input,
enc.as_str(),
args.errors.into_option(),
vm,
)?
} else {
input.str(vm)?
}
}
OptionalArg::Missing => {
PyStr::from(String::new()).into_ref_with_type(vm, cls.clone())?
}
};
if string.class().is(&cls) {
Ok(string.into())
} else {
PyStr::from(string.as_str())
.into_ref_with_type(vm, cls)
.map(Into::into)
}
}
}
impl PyStr {
/// # Safety: Given `bytes` must be valid data for given `kind`
pub(crate) unsafe fn new_str_unchecked(bytes: Vec<u8>, kind: PyStrKind) -> Self {
let s = Self {
bytes: bytes.into_boxed_slice(),
kind: kind.new_data(),
hash: Radium::new(hash::SENTINEL),
};
debug_assert!(matches!(s.kind, PyStrKindData::Ascii) || !s.as_str().is_ascii());
s
}
/// # Safety
/// Given `bytes` must be ascii
pub unsafe fn new_ascii_unchecked(bytes: Vec<u8>) -> Self {
Self::new_str_unchecked(bytes, PyStrKind::Ascii)
}
pub fn new_ref(s: impl Into<Self>, ctx: &Context) -> PyRef<Self> {
PyRef::new_ref(s.into(), ctx.types.str_type.clone(), None)
}
fn new_substr(&self, s: String) -> Self {
let kind = if self.kind.kind() == PyStrKind::Ascii || s.is_ascii() {
PyStrKind::Ascii
} else {
PyStrKind::Utf8
};
unsafe {
// SAFETY: kind is properly decided for substring
Self::new_str_unchecked(s.into_bytes(), kind)
}
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe {
// SAFETY: Both PyStrKind::{Ascii, Utf8} are valid utf8 string
std::str::from_utf8_unchecked(&self.bytes)
}
}
fn char_all<F>(&self, test: F) -> bool
where
F: Fn(char) -> bool,
{
match self.kind.kind() {
PyStrKind::Ascii => self.bytes.iter().all(|&x| test(char::from(x))),
PyStrKind::Utf8 => self.as_str().chars().all(test),
}
}
}
#[pyimpl(
flags(BASETYPE),
with(AsMapping, AsSequence, Hashable, Comparable, Iterable, Constructor)
)]
impl PyStr {
#[pymethod(magic)]
fn add(zelf: PyRef<Self>, other: PyObjectRef, vm: &VirtualMachine) -> PyResult {
if let Some(other) = other.payload::<PyStr>() {
let bytes = zelf.as_str().py_add(other.as_ref());
Ok(unsafe {
// SAFETY: `kind` is safely decided
let kind = zelf.kind.kind() | other.kind.kind();
Self::new_str_unchecked(bytes.into_bytes(), kind)
}
.to_pyobject(vm))
} else if let Some(radd) = vm.get_method(other.clone(), "__radd__") {
// hack to get around not distinguishing number add from seq concat
vm.invoke(&radd?, (zelf,))
} else {
Err(vm.new_type_error(format!(
"can only concatenate str (not \"{}\") to str",
other.class().name()
)))
}
}
#[pymethod(magic)]
fn bool(&self) -> bool {
!self.bytes.is_empty()
}
fn _contains(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult<bool> {
if let Some(needle) = needle.payload::<Self>() {
Ok(self.as_str().contains(needle.as_str()))
} else {
Err(vm.new_type_error(format!(
"'in <string>' requires string as left operand, not {}",
needle.class().name()
)))
}
}
#[pymethod(magic)]
fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
self._contains(&needle, vm)
}
fn _getitem(&self, needle: &PyObject, vm: &VirtualMachine) -> PyResult {
match SequenceIndex::try_from_borrowed_object(vm, needle)? {
SequenceIndex::Int(i) => self.get_item_by_index(vm, i).map(|x| x.to_string()),
SequenceIndex::Slice(slice) => self.get_item_by_slice(vm, slice),
}
.map(|x| self.new_substr(x).into_ref(vm).into())
}
#[pymethod(magic)]
fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
self._getitem(&needle, vm)
}
#[inline]
pub(crate) fn hash(&self, vm: &VirtualMachine) -> hash::PyHash {
match self.hash.load(atomic::Ordering::Relaxed) {
hash::SENTINEL => self._compute_hash(vm),
hash => hash,
}
}
#[cold]
fn _compute_hash(&self, vm: &VirtualMachine) -> hash::PyHash {
let hash_val = vm.state.hash_secret.hash_str(self.as_str());
debug_assert_ne!(hash_val, hash::SENTINEL);
// like with char_len, we don't need a cmpxchg loop, since it'll always be the same value
self.hash.store(hash_val, atomic::Ordering::Relaxed);
hash_val
}
#[inline]
pub fn byte_len(&self) -> usize {
self.bytes.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
#[pymethod(name = "__len__")]
#[inline]
pub fn char_len(&self) -> usize {
match self.kind {
PyStrKindData::Ascii => self.bytes.len(),
PyStrKindData::Utf8(ref len) => match len.load(atomic::Ordering::Relaxed) {
usize::MAX => self._compute_char_len(),
len => len,
},
}
}
#[cold]
fn _compute_char_len(&self) -> usize {
match self.kind {
PyStrKindData::Utf8(ref char_len) => {
let len = self.as_str().chars().count();
// len cannot be usize::MAX, since vec.capacity() < sys.maxsize
char_len.store(len, atomic::Ordering::Relaxed);
len
}
_ => unsafe {
debug_assert!(false); // invalid for non-utf8 strings
std::hint::unreachable_unchecked()
},
}
}
#[pymethod(name = "isascii")]
#[inline(always)]
pub fn is_ascii(&self) -> bool {
match self.kind {
PyStrKindData::Ascii => true,
PyStrKindData::Utf8(_) => false,
}
}
#[pymethod(magic)]
fn sizeof(&self) -> usize {
std::mem::size_of::<Self>() + self.byte_len() * std::mem::size_of::<u8>()
}
#[pymethod(name = "__rmul__")]
#[pymethod(magic)]
fn mul(zelf: PyRef<Self>, value: isize, vm: &VirtualMachine) -> PyResult<PyRef<Self>> {
if value == 1 && zelf.class().is(&vm.ctx.types.str_type) {
// Special case: when some `str` is multiplied by `1`,
// nothing really happens, we need to return an object itself
// with the same `id()` to be compatible with CPython.
// This only works for `str` itself, not its subclasses.
return Ok(zelf);
}
zelf.as_str()
.as_bytes()
.mul(vm, value)
.map(|x| Self::from(unsafe { String::from_utf8_unchecked(x) }).into_ref(vm))
}
#[pymethod(magic)]
fn str(zelf: PyRef<Self>) -> PyStrRef {
zelf
}
#[pymethod(magic)]
pub(crate) fn repr(&self, vm: &VirtualMachine) -> PyResult<String> {
rustpython_common::str::repr(self.as_str())
.to_string_checked()
.map_err(|err| vm.new_overflow_error(err.to_string()))
}
#[pymethod]
fn lower(&self) -> String {
match self.kind.kind() {
PyStrKind::Ascii => self.as_str().to_ascii_lowercase(),
PyStrKind::Utf8 => self.as_str().to_lowercase(),
}
}
// casefold is much more aggressive than lower
#[pymethod]
fn casefold(&self) -> String {
caseless::default_case_fold_str(self.as_str())
}
#[pymethod]
fn upper(&self) -> String {
match self.kind.kind() {
PyStrKind::Ascii => self.as_str().to_ascii_uppercase(),
PyStrKind::Utf8 => self.as_str().to_uppercase(),
}
}
#[pymethod]
fn capitalize(&self) -> String {
let mut chars = self.as_str().chars();
if let Some(first_char) = chars.next() {
format!(
"{}{}",
first_char.to_uppercase(),
&chars.as_str().to_lowercase(),
)
} else {
"".to_owned()
}
}
#[pymethod]
fn split(&self, args: SplitArgs, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
let elements = match self.kind.kind() {
PyStrKind::Ascii => self.as_str().py_split(
args,
vm,
|v, s, vm| {
v.as_bytes()
.split_str(s)
.map(|s| {
unsafe { PyStr::new_ascii_unchecked(s.to_owned()) }.to_pyobject(vm)
})
.collect()
},
|v, s, n, vm| {
v.as_bytes()
.splitn_str(n, s)
.map(|s| {
unsafe { PyStr::new_ascii_unchecked(s.to_owned()) }.to_pyobject(vm)
})
.collect()
},
|v, n, vm| {
v.as_bytes().py_split_whitespace(n, |s| {
unsafe { PyStr::new_ascii_unchecked(s.to_owned()) }.to_pyobject(vm)
})
},
),
PyStrKind::Utf8 => self.as_str().py_split(
args,
vm,
|v, s, vm| v.split(s).map(|s| vm.ctx.new_str(s).into()).collect(),
|v, s, n, vm| v.splitn(n, s).map(|s| vm.ctx.new_str(s).into()).collect(),
|v, n, vm| v.py_split_whitespace(n, |s| vm.ctx.new_str(s).into()),
),
}?;
Ok(elements)
}
#[pymethod]
fn rsplit(&self, args: SplitArgs, vm: &VirtualMachine) -> PyResult<Vec<PyObjectRef>> {
let mut elements = self.as_str().py_split(
args,
vm,
|v, s, vm| v.rsplit(s).map(|s| vm.ctx.new_str(s).into()).collect(),
|v, s, n, vm| v.rsplitn(n, s).map(|s| vm.ctx.new_str(s).into()).collect(),
|v, n, vm| v.py_rsplit_whitespace(n, |s| vm.ctx.new_str(s).into()),
)?;
// Unlike Python rsplit, Rust rsplitn returns an iterator that
// starts from the end of the string.
elements.reverse();
Ok(elements)
}
#[pymethod]
fn strip(&self, chars: OptionalOption<PyStrRef>) -> String {
self.as_str()
.py_strip(
chars,
|s, chars| s.trim_matches(|c| chars.contains(c)),
|s| s.trim(),
)
.to_owned()
}
#[pymethod]
fn lstrip(&self, chars: OptionalOption<PyStrRef>) -> String {
self.as_str()
.py_strip(
chars,
|s, chars| s.trim_start_matches(|c| chars.contains(c)),
|s| s.trim_start(),
)
.to_owned()
}
#[pymethod]
fn rstrip(&self, chars: OptionalOption<PyStrRef>) -> String {
self.as_str()
.py_strip(
chars,
|s, chars| s.trim_end_matches(|c| chars.contains(c)),
|s| s.trim_end(),
)
.to_owned()
}
#[pymethod]
fn endswith(&self, options: anystr::StartsEndsWithArgs, vm: &VirtualMachine) -> PyResult<bool> {
let (affix, substr) =
match options.prepare(self.as_str(), self.len(), |s, r| s.get_chars(r)) {
Some(x) => x,
None => return Ok(false),
};
substr.py_startsendswith(
affix,
"endswith",
"str",
|s, x: &PyStrRef| s.ends_with(x.as_str()),
vm,
)
}
#[pymethod]
fn startswith(
&self,
options: anystr::StartsEndsWithArgs,
vm: &VirtualMachine,
) -> PyResult<bool> {
let (affix, substr) =
match options.prepare(self.as_str(), self.len(), |s, r| s.get_chars(r)) {
Some(x) => x,
None => return Ok(false),
};
substr.py_startsendswith(
affix,
"startswith",
"str",
|s, x: &PyStrRef| s.starts_with(x.as_str()),
vm,
)
}
/// Return a str with the given prefix string removed if present.
///
/// If the string starts with the prefix string, return string[len(prefix):]
/// Otherwise, return a copy of the original string.
#[pymethod]
fn removeprefix(&self, pref: PyStrRef) -> String {
self.as_str()
.py_removeprefix(pref.as_str(), pref.byte_len(), |s, p| s.starts_with(p))
.to_owned()
}
/// Return a str with the given suffix string removed if present.
///
/// If the string ends with the suffix string, return string[:len(suffix)]
/// Otherwise, return a copy of the original string.
#[pymethod]
fn removesuffix(&self, suff: PyStrRef) -> String {
self.as_str()
.py_removesuffix(suff.as_str(), suff.byte_len(), |s, p| s.ends_with(p))
.to_owned()
}
#[pymethod]
fn isalnum(&self) -> bool {
!self.bytes.is_empty() && self.char_all(char::is_alphanumeric)
}
#[pymethod]
fn isnumeric(&self) -> bool {
!self.bytes.is_empty() && self.char_all(char::is_numeric)
}
#[pymethod]
fn isdigit(&self) -> bool {
// python's isdigit also checks if exponents are digits, these are the unicodes for exponents
let valid_unicodes: [u16; 10] = [
0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079,
];
let s = self.as_str();
!s.is_empty()
&& s.chars()
.filter(|c| !c.is_ascii_digit())
.all(|c| valid_unicodes.contains(&(c as u16)))
}
#[pymethod]
fn isdecimal(&self) -> bool {
!self.bytes.is_empty()
&& self.char_all(|c| GeneralCategory::of(c) == GeneralCategory::DecimalNumber)
}
#[pymethod(name = "__mod__")]
fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
let formatted = self.as_str().py_cformat(values, vm)?;
Ok(formatted)
}
#[pymethod(magic)]
fn rmod(&self, _values: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.not_implemented()
}
#[pymethod]
fn format(&self, args: FuncArgs, vm: &VirtualMachine) -> PyResult<String> {
let format_string =
FormatString::from_str(self.as_str()).map_err(|e| e.to_pyexception(vm))?;
format_string.format(&args, vm)
}
/// S.format_map(mapping) -> str
///
/// Return a formatted version of S, using substitutions from mapping.
/// The substitutions are identified by braces ('{' and '}').
#[pymethod]
fn format_map(&self, mapping: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
match FormatString::from_str(self.as_str()) {
Ok(format_string) => format_string.format_map(&mapping, vm),
Err(err) => Err(err.to_pyexception(vm)),
}
}
#[pymethod(name = "__format__")]
fn format_str(&self, spec: PyStrRef, vm: &VirtualMachine) -> PyResult<String> {
match FormatSpec::parse(spec.as_str())
.and_then(|format_spec| format_spec.format_string(self.as_str()))
{
Ok(string) => Ok(string),
Err(err) => Err(vm.new_value_error(err.to_string())),
}
}
/// Return a titlecased version of the string where words start with an
/// uppercase character and the remaining characters are lowercase.
#[pymethod]
fn title(&self) -> String {
let mut title = String::with_capacity(self.bytes.len());
let mut previous_is_cased = false;
for c in self.as_str().chars() {
if c.is_lowercase() {
if !previous_is_cased {
title.extend(c.to_titlecase());
} else {
title.push(c);
}
previous_is_cased = true;
} else if c.is_uppercase() || c.is_titlecase() {
if previous_is_cased {
title.extend(c.to_lowercase());
} else {
title.push(c);
}
previous_is_cased = true;
} else {
previous_is_cased = false;
title.push(c);
}
}
title
}
#[pymethod]
fn swapcase(&self) -> String {
let mut swapped_str = String::with_capacity(self.bytes.len());
for c in self.as_str().chars() {
// to_uppercase returns an iterator, to_ascii_uppercase returns the char
if c.is_lowercase() {
swapped_str.push(c.to_ascii_uppercase());
} else if c.is_uppercase() {
swapped_str.push(c.to_ascii_lowercase());
} else {
swapped_str.push(c);
}
}
swapped_str
}
#[pymethod]
fn isalpha(&self) -> bool {
!self.bytes.is_empty() && self.char_all(char::is_alphabetic)
}
#[pymethod]
fn replace(&self, old: PyStrRef, new: PyStrRef, count: OptionalArg<isize>) -> String {
let s = self.as_str();
match count {
OptionalArg::Present(maxcount) if maxcount >= 0 => {
if maxcount == 0 || s.is_empty() {
// nothing to do; return the original bytes
s.into()
} else {
s.replacen(old.as_str(), new.as_str(), maxcount as usize)
}
}
_ => s.replace(old.as_str(), new.as_str()),
}
}
/// Return true if all characters in the string are printable or the string is empty,
/// false otherwise. Nonprintable characters are those characters defined in the
/// Unicode character database as `Other` or `Separator`,
/// excepting the ASCII space (0x20) which is considered printable.
///
/// All characters except those characters defined in the Unicode character
/// database as following categories are considered printable.
/// * Cc (Other, Control)
/// * Cf (Other, Format)
/// * Cs (Other, Surrogate)
/// * Co (Other, Private Use)
/// * Cn (Other, Not Assigned)
/// * Zl Separator, Line ('\u2028', LINE SEPARATOR)
/// * Zp Separator, Paragraph ('\u2029', PARAGRAPH SEPARATOR)
/// * Zs (Separator, Space) other than ASCII space('\x20').
#[pymethod]
fn isprintable(&self) -> bool {
self.char_all(|c| c == '\u{0020}' || rustpython_common::char::is_printable(c))
}
#[pymethod]
fn isspace(&self) -> bool {
use unic_ucd_bidi::bidi_class::abbr_names::*;
!self.bytes.is_empty()
&& self.char_all(|c| {
GeneralCategory::of(c) == GeneralCategory::SpaceSeparator
|| matches!(BidiClass::of(c), WS | B | S)
})
}
// Return true if all cased characters in the string are lowercase and there is at least one cased character, false otherwise.
#[pymethod]
fn islower(&self) -> bool {
match self.kind.kind() {
PyStrKind::Ascii => self.bytes.py_iscase(char::is_lowercase, char::is_uppercase),
PyStrKind::Utf8 => self
.as_str()
.py_iscase(char::is_lowercase, char::is_uppercase),
}
}
// Return true if all cased characters in the string are uppercase and there is at least one cased character, false otherwise.
#[pymethod]
fn isupper(&self) -> bool {
match self.kind.kind() {
PyStrKind::Ascii => self.bytes.py_iscase(char::is_uppercase, char::is_lowercase),
PyStrKind::Utf8 => self
.as_str()
.py_iscase(char::is_uppercase, char::is_lowercase),
}
}
#[pymethod]
fn splitlines(&self, args: anystr::SplitLinesArgs, vm: &VirtualMachine) -> Vec<PyObjectRef> {
self.as_str()
.py_splitlines(args, |s| self.new_substr(s.to_owned()).to_pyobject(vm))
}
#[pymethod]
fn join(&self, iterable: ArgIterable<PyStrRef>, vm: &VirtualMachine) -> PyResult<String> {
let iter = iterable.iter(vm)?;
self.as_str().py_join(iter)
}
// FIXME: two traversals of str is expensive
#[inline]
fn _to_char_idx(r: &str, byte_idx: usize) -> usize {
r[..byte_idx].chars().count()
}
#[inline]
fn _find<F>(&self, args: FindArgs, find: F) -> Option<usize>
where
F: Fn(&str, &str) -> Option<usize>,
{
let (sub, range) = args.get_value(self.len());
self.as_str().py_find(sub.as_str(), range, find)
}
#[pymethod]
fn find(&self, args: FindArgs) -> isize {
self._find(args, |r, s| Some(Self::_to_char_idx(r, r.find(s)?)))
.map_or(-1, |v| v as isize)
}
#[pymethod]
fn rfind(&self, args: FindArgs) -> isize {
self._find(args, |r, s| Some(Self::_to_char_idx(r, r.rfind(s)?)))
.map_or(-1, |v| v as isize)
}
#[pymethod]
fn index(&self, args: FindArgs, vm: &VirtualMachine) -> PyResult<usize> {
self._find(args, |r, s| Some(Self::_to_char_idx(r, r.find(s)?)))
.ok_or_else(|| vm.new_value_error("substring not found".to_owned()))
}
#[pymethod]
fn rindex(&self, args: FindArgs, vm: &VirtualMachine) -> PyResult<usize> {
self._find(args, |r, s| Some(Self::_to_char_idx(r, r.rfind(s)?)))
.ok_or_else(|| vm.new_value_error("substring not found".to_owned()))
}
#[pymethod]
fn partition(&self, sep: PyStrRef, vm: &VirtualMachine) -> PyResult {
let (front, has_mid, back) = self.as_str().py_partition(
sep.as_str(),
|| self.as_str().splitn(2, sep.as_str()),
vm,
)?;
let partition = (
self.new_substr(front),
if has_mid {
sep
} else {
vm.ctx.new_str(ascii!(""))
},
self.new_substr(back),
);
Ok(partition.to_pyobject(vm))
}
#[pymethod]
fn rpartition(&self, sep: PyStrRef, vm: &VirtualMachine) -> PyResult {
let (back, has_mid, front) = self.as_str().py_partition(
sep.as_str(),
|| self.as_str().rsplitn(2, sep.as_str()),
vm,
)?;
Ok((
self.new_substr(front),
if has_mid {
sep
} else {
vm.ctx.new_str(ascii!(""))
},
self.new_substr(back),
)
.to_pyobject(vm))
}
/// Return `true` if the sequence is ASCII titlecase and the sequence is not
/// empty, `false` otherwise.
#[pymethod]
fn istitle(&self) -> bool {
if self.bytes.is_empty() {
return false;
}
let mut cased = false;
let mut previous_is_cased = false;
for c in self.as_str().chars() {
if c.is_uppercase() || c.is_titlecase() {
if previous_is_cased {
return false;
}
previous_is_cased = true;
cased = true;
} else if c.is_lowercase() {
if !previous_is_cased {
return false;
}
previous_is_cased = true;
cased = true;
} else {
previous_is_cased = false;
}
}
cased
}
#[pymethod]
fn count(&self, args: FindArgs) -> usize {
let (needle, range) = args.get_value(self.len());
self.as_str()
.py_count(needle.as_str(), range, |h, n| h.matches(n).count())
}
#[pymethod]
fn zfill(&self, width: isize) -> String {
unsafe {
// SAFETY: this is safe-guaranteed because the original self.as_str() is valid utf8
String::from_utf8_unchecked(self.as_str().py_zfill(width))
}
}
#[inline]
fn _pad(
&self,
width: isize,
fillchar: OptionalArg<PyStrRef>,
pad: fn(&str, usize, char, usize) -> String,
vm: &VirtualMachine,
) -> PyResult<String> {
let fillchar = fillchar.map_or(Ok(' '), |ref s| {
s.as_str().chars().exactly_one().map_err(|_| {
vm.new_type_error(
"The fill character must be exactly one character long".to_owned(),
)
})
})?;
Ok(if self.len() as isize >= width {
String::from(self.as_str())
} else {
pad(self.as_str(), width as usize, fillchar, self.len())
})
}
#[pymethod]
fn center(
&self,
width: isize,
fillchar: OptionalArg<PyStrRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
self._pad(width, fillchar, AnyStr::py_center, vm)
}
#[pymethod]
fn ljust(
&self,
width: isize,
fillchar: OptionalArg<PyStrRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
self._pad(width, fillchar, AnyStr::py_ljust, vm)
}
#[pymethod]
fn rjust(
&self,
width: isize,
fillchar: OptionalArg<PyStrRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
self._pad(width, fillchar, AnyStr::py_rjust, vm)
}
#[pymethod]
fn expandtabs(&self, args: anystr::ExpandTabsArgs) -> String {
let tab_stop = args.tabsize();
let mut expanded_str = String::with_capacity(self.byte_len());
let mut tab_size = tab_stop;
let mut col_count = 0usize;
for ch in self.as_str().chars() {
match ch {
'\t' => {
let num_spaces = tab_size - col_count;
col_count += num_spaces;
let expand = " ".repeat(num_spaces);
expanded_str.push_str(&expand);
}
'\r' | '\n' => {
expanded_str.push(ch);
col_count = 0;
tab_size = 0;
}
_ => {
expanded_str.push(ch);
col_count += 1;
}
}
if col_count >= tab_size {
tab_size += tab_stop;
}
}
expanded_str
}
#[pymethod]
fn isidentifier(&self) -> bool {
let mut chars = self.as_str().chars();
let is_identifier_start = chars.next().map_or(false, |c| c == '_' || is_xid_start(c));
// a string is not an identifier if it has whitespace or starts with a number
is_identifier_start && chars.all(is_xid_continue)
}
// https://docs.python.org/3/library/stdtypes.html#str.translate
#[pymethod]
fn translate(&self, table: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
vm.get_method_or_type_error(table.clone(), "__getitem__", || {
format!("'{}' object is not subscriptable", table.class().name())
})?;
let mut translated = String::new();
for c in self.as_str().chars() {
match table.get_item((c as u32).to_pyobject(vm), vm) {
Ok(value) => {
if let Some(text) = value.payload::<PyStr>() {
translated.push_str(text.as_str());
} else if let Some(bigint) = value.payload::<PyInt>() {
let ch = bigint
.as_bigint()
.to_u32()
.and_then(std::char::from_u32)
.ok_or_else(|| {
vm.new_value_error(
"character mapping must be in range(0x110000)".to_owned(),
)
})?;
translated.push(ch as char);
} else if !vm.is_none(&value) {
return Err(vm.new_type_error(
"character mapping must return integer, None or str".to_owned(),
));
}
}
_ => translated.push(c),
}
}
Ok(translated)
}
#[pystaticmethod]
fn maketrans(
dict_or_str: PyObjectRef,
to_str: OptionalArg<PyStrRef>,
none_str: OptionalArg<PyStrRef>,
vm: &VirtualMachine,
) -> PyResult {
let new_dict = vm.ctx.new_dict();
if let OptionalArg::Present(to_str) = to_str {
match dict_or_str.downcast::<PyStr>() {
Ok(from_str) => {
if to_str.len() == from_str.len() {
for (c1, c2) in from_str.as_str().chars().zip(to_str.as_str().chars()) {
new_dict.set_item(
vm.new_pyobj(c1 as u32),
vm.new_pyobj(c2 as u32),
vm,
)?;
}
if let OptionalArg::Present(none_str) = none_str {
for c in none_str.as_str().chars() {
new_dict.set_item(vm.new_pyobj(c as u32), vm.ctx.none(), vm)?;
}
}
Ok(new_dict.to_pyobject(vm))
} else {
Err(vm.new_value_error(
"the first two maketrans arguments must have equal length".to_owned(),
))
}
}
_ => Err(vm.new_type_error(
"first maketrans argument must be a string if there is a second argument"
.to_owned(),
)),
}
} else {
// dict_str must be a dict
match dict_or_str.downcast::<PyDict>() {
Ok(dict) => {
for (key, val) in dict {
if let Some(num) = key.payload::<PyInt>() {
new_dict.set_item(num.as_bigint().to_i32().to_pyobject(vm), val, vm)?;
} else if let Some(string) = key.payload::<PyStr>() {
if string.len() == 1 {
let num_value = string.as_str().chars().next().unwrap() as u32;
new_dict.set_item(num_value.to_pyobject(vm), val, vm)?;
} else {
return Err(vm.new_value_error(
"string keys in translate table must be of length 1".to_owned(),
));
}
}
}
Ok(new_dict.to_pyobject(vm))
}
_ => Err(vm.new_value_error(
"if you give only one argument to maketrans it must be a dict".to_owned(),
)),
}
}
}
#[pymethod]
fn encode(zelf: PyRef<Self>, args: EncodeArgs, vm: &VirtualMachine) -> PyResult<PyBytesRef> {
encode_string(zelf, args.encoding, args.errors, vm)
}
}
impl PyStrRef {
pub fn concat_in_place(&mut self, other: &str, vm: &VirtualMachine) {
// TODO: call [A]Rc::get_mut on the str to try to mutate the data in place
if other.is_empty() {
return;
}
let mut s = String::with_capacity(self.byte_len() + other.len());
s.push_str(self.as_ref());
s.push_str(other);
*self = PyStr::from(s).into_ref(vm);
}
}
impl Hashable for PyStr {
#[inline]
fn hash(zelf: &crate::Py<Self>, vm: &VirtualMachine) -> PyResult<hash::PyHash> {
Ok(zelf.hash(vm))
}
}
impl Comparable for PyStr {
fn cmp(
zelf: &crate::Py<Self>,
other: &PyObject,
op: PyComparisonOp,
_vm: &VirtualMachine,
) -> PyResult<PyComparisonValue> {
if let Some(res) = op.identical_optimization(zelf, other) {
return Ok(res.into());
}
let other = class_or_notimplemented!(Self, other);
Ok(op.eval_ord(zelf.as_str().cmp(other.as_str())).into())
}
}
impl Iterable for PyStr {
fn iter(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyResult {
Ok(PyStrIterator {
internal: PyMutex::new((PositionIterInternal::new(zelf, 0), 0)),
}
.into_pyobject(vm))
}
}
impl AsMapping for PyStr {
fn as_mapping(_zelf: &Py<Self>, _vm: &VirtualMachine) -> PyMappingMethods {
Self::MAPPING_METHODS
}
}
impl PyStr {
const MAPPING_METHODS: PyMappingMethods = PyMappingMethods {
length: Some(|mapping, _vm| Ok(Self::mapping_downcast(mapping).len())),
subscript: Some(|mapping, needle, vm| Self::mapping_downcast(mapping)._getitem(needle, vm)),
ass_subscript: None,
};
}
impl AsSequence for PyStr {
fn as_sequence(
_zelf: &Py<Self>,
_vm: &VirtualMachine,
) -> std::borrow::Cow<'static, PySequenceMethods> {
std::borrow::Cow::Borrowed(&Self::SEQUENCE_METHDOS)
}
}
impl PyStr {
const SEQUENCE_METHDOS: PySequenceMethods = PySequenceMethods {
length: Some(|seq, _vm| Ok(Self::sequence_downcast(seq).len())),
concat: Some(|seq, other, vm| {
let zelf = Self::sequence_downcast(seq);
Self::add(zelf.to_owned(), other.to_owned(), vm)
}),
repeat: Some(|seq, n, vm| {
let zelf = Self::sequence_downcast(seq);
Self::mul(zelf.to_owned(), n as isize, vm).map(|x| x.into())
}),
item: Some(|seq, i, vm| {
let zelf = Self::sequence_downcast(seq);
zelf.get_item_by_index(vm, i)
.map(|x| zelf.new_substr(x.to_string()).into_ref(vm).into())
}),
contains: Some(|seq, needle, vm| Self::sequence_downcast(seq)._contains(needle, vm)),
..*PySequenceMethods::not_implemented()
};
}
#[derive(FromArgs)]
struct EncodeArgs {
#[pyarg(any, default)]
encoding: Option<PyStrRef>,
#[pyarg(any, default)]
errors: Option<PyStrRef>,
}
pub(crate) fn encode_string(
s: PyStrRef,
encoding: Option<PyStrRef>,
errors: Option<PyStrRef>,
vm: &VirtualMachine,
) -> PyResult<PyBytesRef> {
let encoding = encoding
.as_ref()
.map_or(crate::codecs::DEFAULT_ENCODING, |s| s.as_str());
vm.state.codec_registry.encode_text(s, encoding, errors, vm)
}
impl PyPayload for PyStr {
fn class(vm: &VirtualMachine) -> &PyTypeRef {
&vm.ctx.types.str_type
}
}
impl ToPyObject for String {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_str(self).into()
}
}
impl ToPyObject for char {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_str(self.to_string()).into()
}
}
impl ToPyObject for &str {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_str(self).into()
}
}
impl ToPyObject for &String {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_str(self.clone()).into()
}
}
impl ToPyObject for &AsciiStr {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_str(self).into()
}
}
impl ToPyObject for AsciiString {
fn to_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_str(self).into()
}
}
type SplitArgs<'a> = anystr::SplitArgs<'a, PyStrRef>;
#[derive(FromArgs)]
pub struct FindArgs {
#[pyarg(positional)]
sub: PyStrRef,
#[pyarg(positional, default)]
start: Option<PyIntRef>,
#[pyarg(positional, default)]
end: Option<PyIntRef>,
}
impl FindArgs {
fn get_value(self, len: usize) -> (PyStrRef, std::ops::Range<usize>) {
let range = adjust_indices(self.start, self.end, len);
(self.sub, range)
}
}
pub fn init(ctx: &Context) {
PyStr::extend_class(ctx, &ctx.types.str_type);
PyStrIterator::extend_class(ctx, &ctx.types.str_iterator_type);
}
impl SliceableSequenceOp for PyStr {
type Item = char;
type Sliced = String;
fn do_get(&self, index: usize) -> Self::Item {
if self.is_ascii() {
self.bytes[index] as char
} else {
self.as_str().chars().nth(index).unwrap()
}
}
fn do_slice(&self, range: Range<usize>) -> Self::Sliced {
let value = self.as_str();
if self.is_ascii() {
value[range].to_owned()
} else {
rustpython_common::str::get_chars(value, range).to_owned()
}
}
fn do_slice_reverse(&self, range: Range<usize>) -> Self::Sliced {
if self.is_ascii() {
// this is an ascii string
let mut v = self.bytes[range].to_vec();
v.reverse();
unsafe {
// SAFETY: an ascii string is always utf8
String::from_utf8_unchecked(v)
}
} else {
let mut s = String::with_capacity(self.bytes.len());
s.extend(
self.as_str()
.chars()
.rev()
.skip(self.char_len() - range.end)
.take(range.end - range.start),
);
s
}
}
fn do_stepped_slice(&self, range: Range<usize>, step: usize) -> Self::Sliced {
if self.is_ascii() {
let v = self.bytes[range].iter().copied().step_by(step).collect();
unsafe {
// SAFETY: Any subset of ascii string is a valid utf8 string
String::from_utf8_unchecked(v)
}
} else {
let mut s = String::with_capacity(2 * ((range.len() / step) + 1));
s.extend(
self.as_str()
.chars()
.skip(range.start)
.take(range.end - range.start)
.step_by(step),
);
s
}
}
fn do_stepped_slice_reverse(&self, range: Range<usize>, step: usize) -> Self::Sliced {
if self.is_ascii() {
// this is an ascii string
let v: Vec<u8> = self.bytes[range]
.iter()
.rev()
.copied()
.step_by(step)
.collect();
// TODO: from_utf8_unchecked?
String::from_utf8(v).unwrap()
} else {
// not ascii, so the codepoints have to be at least 2 bytes each
let mut s = String::with_capacity(2 * ((range.len() / step) + 1));
s.extend(
self.as_str()
.chars()
.rev()
.skip(self.char_len() - range.end)
.take(range.end - range.start)
.step_by(step),
);
s
}
}
fn empty() -> Self::Sliced {
String::new()
}
fn len(&self) -> usize {
self.char_len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Interpreter;
use std::ops::Deref;
#[test]
fn str_title() {
let tests = vec![
(" Hello ", " hello "),
("Hello ", "hello "),
("Hello ", "Hello "),
("Format This As Title String", "fOrMaT thIs aS titLe String"),
("Format,This-As*Title;String", "fOrMaT,thIs-aS*titLe;String"),
("Getint", "getInt"),
("Greek Ωppercases ...", "greek ωppercases ..."),
("Greek ῼitlecases ...", "greek ῳitlecases ..."),
];
for (title, input) in tests {
assert_eq!(PyStr::from(input).title().as_str(), title);
}
}
#[test]
fn str_istitle() {
let pos = vec![
"A",
"A Titlecased Line",
"A\nTitlecased Line",
"A Titlecased, Line",
"Greek Ωppercases ...",
"Greek ῼitlecases ...",
];
for s in pos {
assert!(PyStr::from(s).istitle());
}
let neg = vec![
"",
"a",
"\n",
"Not a capitalized String",
"Not\ta Titlecase String",
"Not--a Titlecase String",
"NOT",
];
for s in neg {
assert!(!PyStr::from(s).istitle());
}
}
#[test]
fn str_maketrans_and_translate() {
Interpreter::without_stdlib(Default::default()).enter(|vm| {
let table = vm.ctx.new_dict();
table
.set_item("a", vm.ctx.new_str("🎅").into(), &vm)
.unwrap();
table.set_item("b", vm.ctx.none(), &vm).unwrap();
table
.set_item("c", vm.ctx.new_str(ascii!("xda")).into(), &vm)
.unwrap();
let translated = PyStr::maketrans(
table.into(),
OptionalArg::Missing,
OptionalArg::Missing,
&vm,
)
.unwrap();
let text = PyStr::from("abc");
let translated = text.translate(translated, &vm).unwrap();
assert_eq!(translated, "🎅xda".to_owned());
let translated = text.translate(vm.ctx.new_int(3).into(), &vm);
assert_eq!(
translated.unwrap_err().class().name().deref(),
"TypeError".to_owned()
);
})
}
}
impl<'s> AnyStrWrapper<'s> for PyStrRef {
type Str = str;
fn as_ref(&self) -> &str {
&*self.as_str()
}
}
impl AnyStrContainer<str> for String {
fn new() -> Self {
String::new()
}
fn with_capacity(capacity: usize) -> Self {
String::with_capacity(capacity)
}
fn push_str(&mut self, other: &str) {
String::push_str(self, other)
}
}
impl<'s> AnyStr<'s> for str {
type Char = char;
type Container = String;
type CharIter = std::str::Chars<'s>;
type ElementIter = std::str::Chars<'s>;
fn element_bytes_len(c: char) -> usize {
c.len_utf8()
}
fn to_container(&self) -> Self::Container {
self.to_owned()
}
fn as_bytes(&self) -> &[u8] {
self.as_bytes()
}
fn as_utf8_str(&self) -> Result<&str, std::str::Utf8Error> {
Ok(self)
}
fn chars(&'s self) -> Self::CharIter {
str::chars(self)
}
fn elements(&'s self) -> Self::ElementIter {
str::chars(self)
}
fn get_bytes(&self, range: std::ops::Range<usize>) -> &Self {
&self[range]
}
fn get_chars(&self, range: std::ops::Range<usize>) -> &Self {
rustpython_common::str::get_chars(self, range)
}
fn is_empty(&self) -> bool {
Self::is_empty(self)
}
fn bytes_len(&self) -> usize {
Self::len(self)
}
fn py_split_whitespace<F>(&self, maxsplit: isize, convert: F) -> Vec<PyObjectRef>
where
F: Fn(&Self) -> PyObjectRef,
{
// CPython split_whitespace
let mut splited = Vec::new();
let mut last_offset = 0;
let mut count = maxsplit;
for (offset, _) in self.match_indices(|c: char| c.is_ascii_whitespace() || c == '\x0b') {
if last_offset == offset {
last_offset += 1;
continue;
}
if count == 0 {
break;
}
splited.push(convert(&self[last_offset..offset]));
last_offset = offset + 1;
count -= 1;
}
if last_offset != self.len() {
splited.push(convert(&self[last_offset..]));
}
splited
}
fn py_rsplit_whitespace<F>(&self, maxsplit: isize, convert: F) -> Vec<PyObjectRef>
where
F: Fn(&Self) -> PyObjectRef,
{
// CPython rsplit_whitespace
let mut splited = Vec::new();
let mut last_offset = self.len();
let mut count = maxsplit;
for (offset, _) in self.rmatch_indices(|c: char| c.is_ascii_whitespace() || c == '\x0b') {
if last_offset == offset + 1 {
last_offset -= 1;
continue;
}
if count == 0 {
break;
}
splited.push(convert(&self[offset + 1..last_offset]));
last_offset = offset;
count -= 1;
}
if last_offset != 0 {
splited.push(convert(&self[..last_offset]));
}
splited
}
}