Files
RustPython/vm/src/obj/objstr.rs
T
Jeong YunWon f8a5cc4b23 Remove type clone functions from vm.ctx
which misleads to clone types when we don't need.
2020-08-23 20:26:37 +09:00

1446 lines
44 KiB
Rust

use std::char;
use std::fmt;
use std::mem::size_of;
use std::ops::Range;
use std::string::ToString;
use crossbeam_utils::atomic::AtomicCell;
use itertools::Itertools;
use num_traits::ToPrimitive;
use unic_ucd_bidi::BidiClass;
use unic_ucd_category::GeneralCategory;
use unic_ucd_ident::{is_xid_continue, is_xid_start};
use unicode_casing::CharExt;
use super::objbytes::{PyBytes, PyBytesRef};
use super::objdict::PyDict;
use super::objint::{PyInt, PyIntRef};
use super::objiter;
use super::objsequence::{PySliceableSequence, SequenceIndex};
use super::objtype::{self, PyClassRef};
use crate::exceptions::IntoPyException;
use crate::format::{FormatSpec, FormatString, FromTemplate};
use crate::function::{OptionalArg, OptionalOption, PyFuncArgs};
use crate::pyobject::{
BorrowValue, IdProtocol, IntoPyObject, ItemProtocol, PyClassImpl, PyContext, PyIterable,
PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TryIntoRef, TypeProtocol,
};
use crate::pystr::{
self, adjust_indices, PyCommonString, PyCommonStringContainer, PyCommonStringWrapper,
};
use crate::vm::VirtualMachine;
use rustpython_common::hash;
/// 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")]
#[derive(Debug)]
pub struct PyString {
value: String,
hash: AtomicCell<Option<hash::PyHash>>,
len: AtomicCell<Option<usize>>,
}
impl<'a> BorrowValue<'a> for PyString {
type Borrowed = &'a str;
fn borrow_value(&'a self) -> Self::Borrowed {
&self.value
}
}
impl AsRef<str> for PyString {
fn as_ref(&self) -> &str {
&self.value
}
}
impl<T> From<&T> for PyString
where
T: AsRef<str> + ?Sized,
{
fn from(s: &T) -> PyString {
s.as_ref().to_owned().into()
}
}
impl From<String> for PyString {
fn from(s: String) -> PyString {
PyString {
value: s,
hash: AtomicCell::default(),
len: AtomicCell::default(),
}
}
}
pub type PyStringRef = PyRef<PyString>;
impl fmt::Display for PyString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.value, f)
}
}
impl TryIntoRef<PyString> for String {
fn try_into_ref(self, vm: &VirtualMachine) -> PyResult<PyRef<PyString>> {
Ok(PyString::from(self).into_ref(vm))
}
}
impl TryIntoRef<PyString> for &str {
fn try_into_ref(self, vm: &VirtualMachine) -> PyResult<PyRef<PyString>> {
Ok(PyString::from(self).into_ref(vm))
}
}
#[pyclass(module = false, name = "str_iterator")]
#[derive(Debug)]
pub struct PyStringIterator {
pub string: PyStringRef,
position: AtomicCell<usize>,
}
impl PyValue for PyStringIterator {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.types.str_iterator_type.clone()
}
}
#[pyimpl]
impl PyStringIterator {
#[pymethod(name = "__next__")]
fn next(&self, vm: &VirtualMachine) -> PyResult<String> {
// TODO: use something more performant than chars().nth() that's still atomic
let pos = self.position.fetch_add(1);
if let Some(c) = self.string.value.chars().nth(pos) {
Ok(c.to_string())
} else {
Err(objiter::new_stop_iteration(vm))
}
}
#[pymethod(name = "__iter__")]
fn iter(zelf: PyRef<Self>) -> PyRef<Self> {
zelf
}
}
#[pyclass(module = false, name = "str_reverseiterator")]
#[derive(Debug)]
pub struct PyStringReverseIterator {
pub position: AtomicCell<isize>,
pub string: PyStringRef,
}
impl PyValue for PyStringReverseIterator {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.types.str_reverseiterator_type.clone()
}
}
#[pyimpl]
impl PyStringReverseIterator {
#[pymethod(name = "__next__")]
fn next(&self, vm: &VirtualMachine) -> PyResult<String> {
let pos = self.position.fetch_sub(1);
if pos > 0 {
if let Some(c) = self.string.value.chars().nth(pos as usize - 1) {
return Ok(c.to_string());
}
}
Err(objiter::new_stop_iteration(vm))
}
#[pymethod(name = "__iter__")]
fn iter(zelf: PyRef<Self>) -> PyRef<Self> {
zelf
}
}
#[derive(FromArgs)]
struct StrArgs {
#[pyarg(positional_or_keyword, optional = true)]
object: OptionalArg<PyObjectRef>,
#[pyarg(positional_or_keyword, optional = true)]
encoding: OptionalArg<PyStringRef>,
#[pyarg(positional_or_keyword, optional = true)]
errors: OptionalArg<PyStringRef>,
}
#[pyimpl(flags(BASETYPE))]
impl PyString {
#[pyslot]
fn tp_new(cls: PyClassRef, args: StrArgs, vm: &VirtualMachine) -> PyResult<PyStringRef> {
let string: PyStringRef = match args.object {
OptionalArg::Present(input) => {
if let OptionalArg::Present(enc) = args.encoding {
vm.decode(input, Some(enc.clone()), args.errors.into_option())?
.downcast()
.map_err(|obj| {
vm.new_type_error(format!(
"'{}' decoder returned '{}' instead of 'str'; use codecs.encode() to \
encode arbitrary types",
enc,
obj.lease_class().name,
))
})?
} else {
vm.to_str(&input)?
}
}
OptionalArg::Missing => {
PyString::from(String::new()).into_ref_with_type(vm, cls.clone())?
}
};
if string.class().is(&cls) {
Ok(string)
} else {
PyString::from(string.borrow_value()).into_ref_with_type(vm, cls)
}
}
#[pymethod(name = "__add__")]
fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
if objtype::isinstance(&other, &vm.ctx.types.str_type) {
Ok(self.value.py_add(borrow_value(&other)))
} else {
Err(vm.new_type_error(format!("Cannot add {} and {}", self, other)))
}
}
#[pymethod(name = "__bool__")]
fn bool(&self) -> bool {
!self.value.is_empty()
}
#[pymethod(name = "__eq__")]
fn eq(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&rhs, &vm.ctx.types.str_type) {
vm.ctx.new_bool(self.value == borrow_value(&rhs))
} else {
vm.ctx.not_implemented()
}
}
#[pymethod(name = "__ne__")]
fn ne(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef {
if objtype::isinstance(&rhs, &vm.ctx.types.str_type) {
vm.ctx.new_bool(self.value != borrow_value(&rhs))
} else {
vm.ctx.not_implemented()
}
}
#[pymethod(name = "__contains__")]
fn contains(&self, needle: PyStringRef) -> bool {
self.value.contains(&needle.value)
}
#[pymethod(name = "__getitem__")]
fn getitem(&self, needle: SequenceIndex, vm: &VirtualMachine) -> PyResult {
match needle {
SequenceIndex::Int(pos) => {
let index: usize = if pos.is_negative() {
(self.value.chars().count() as isize + pos) as usize
} else {
pos.abs() as usize
};
if let Some(character) = self.value.chars().nth(index) {
Ok(vm.ctx.new_str(character.to_string()))
} else {
Err(vm.new_index_error("string index out of range".to_owned()))
}
}
SequenceIndex::Slice(slice) => {
let string = self.get_slice_items(vm, &slice)?;
Ok(vm.ctx.new_str(string))
}
}
}
#[pymethod(name = "__gt__")]
fn gt(&self, other: PyStringRef) -> bool {
self.value > other.value
}
#[pymethod(name = "__ge__")]
fn ge(&self, other: PyStringRef) -> bool {
self.value >= other.value
}
#[pymethod(name = "__lt__")]
fn lt(&self, other: PyStringRef) -> bool {
self.value < other.value
}
#[pymethod(name = "__le__")]
fn le(&self, other: PyStringRef) -> bool {
self.value <= other.value
}
#[pymethod(name = "__hash__")]
pub(crate) fn hash(&self) -> hash::PyHash {
self.hash.load().unwrap_or_else(|| {
let hash = hash::hash_str(&self.value);
self.hash.store(Some(hash));
hash
})
}
#[pymethod(name = "__len__")]
#[inline]
fn len(&self) -> usize {
self.len.load().unwrap_or_else(|| {
let len = self.value.chars().count();
self.len.store(Some(len));
len
})
}
#[pymethod(name = "__sizeof__")]
fn sizeof(&self) -> usize {
size_of::<Self>() + self.value.capacity() * size_of::<u8>()
}
#[pymethod(name = "__mul__")]
#[pymethod(name = "__rmul__")]
fn mul(&self, value: isize) -> String {
self.value.repeat(value.to_usize().unwrap_or(0))
}
#[pymethod(name = "__str__")]
fn str(zelf: PyRef<Self>) -> PyStringRef {
zelf
}
#[pymethod(name = "__repr__")]
pub(crate) fn repr(&self, vm: &VirtualMachine) -> PyResult<String> {
let in_len = self.value.len();
let mut out_len = 0usize;
// let mut max = 127;
let mut squote = 0;
let mut dquote = 0;
for ch in self.value.chars() {
let incr = match ch {
'\'' => {
squote += 1;
1
}
'"' => {
dquote += 1;
1
}
'\\' | '\t' | '\r' | '\n' => 2,
ch if ch < ' ' || ch as u32 == 0x7f => 4, // \xHH
ch if ch.is_ascii() => 1,
ch if char_is_printable(ch) => {
// max = std::cmp::max(ch, max);
1
}
ch if (ch as u32) < 0x100 => 4, // \xHH
ch if (ch as u32) < 0x10000 => 6, // \uHHHH
_ => 10, // \uHHHHHHHH
};
if out_len > (std::isize::MAX as usize) - incr {
return Err(vm.new_overflow_error("string is too long to generate repr".to_owned()));
}
out_len += incr;
}
let (quote, unchanged) = {
let mut quote = '\'';
let mut unchanged = out_len == in_len;
if squote > 0 {
unchanged = false;
if dquote > 0 {
// Both squote and dquote present. Use squote, and escape them
out_len += squote;
} else {
quote = '"';
}
}
(quote, unchanged)
};
out_len += 2; // quotes
let mut repr = String::with_capacity(out_len);
repr.push(quote);
if unchanged {
repr.push_str(&self.value);
} else {
for ch in self.value.chars() {
if ch == quote || ch == '\\' {
repr.push('\\');
repr.push(ch);
} else if ch == '\n' {
repr.push_str("\\n")
} else if ch == '\t' {
repr.push_str("\\t");
} else if ch == '\r' {
repr.push_str("\\r");
} else if ch < ' ' || ch as u32 == 0x7F {
repr.push_str(&format!("\\x{:02x}", ch as u32));
} else if ch.is_ascii() {
repr.push(ch);
} else if !char_is_printable(ch) {
let code = ch as u32;
let escaped = if code < 0xff {
format!("\\x{:02x}", code)
} else if code < 0xffff {
format!("\\u{:04x}", code)
} else {
format!("\\U{:08x}", code)
};
repr.push_str(&escaped);
} else {
repr.push(ch)
}
}
}
repr.push(quote);
Ok(repr)
}
#[pymethod]
fn lower(&self) -> String {
self.value.to_lowercase()
}
// casefold is much more aggressive than lower
#[pymethod]
fn casefold(&self) -> String {
caseless::default_case_fold_str(&self.value)
}
#[pymethod]
fn upper(&self) -> String {
self.value.to_uppercase()
}
#[pymethod]
fn capitalize(&self) -> String {
let mut chars = self.value.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 {
let elements = self.value.py_split(
args,
vm,
|v, s, vm| v.split(s).map(|s| vm.ctx.new_str(s)).collect(),
|v, s, n, vm| v.splitn(n, s).map(|s| vm.ctx.new_str(s)).collect(),
|v, n, vm| v.py_split_whitespace(n, |s| vm.ctx.new_str(s)),
)?;
Ok(vm.ctx.new_list(elements))
}
#[pymethod]
fn rsplit(&self, args: SplitArgs, vm: &VirtualMachine) -> PyResult {
let mut elements = self.value.py_split(
args,
vm,
|v, s, vm| v.rsplit(s).map(|s| vm.ctx.new_str(s)).collect(),
|v, s, n, vm| v.rsplitn(n, s).map(|s| vm.ctx.new_str(s)).collect(),
|v, n, vm| v.py_rsplit_whitespace(n, |s| vm.ctx.new_str(s)),
)?;
// Unlike Python rsplit, Rust rsplitn returns an iterator that
// starts from the end of the string.
elements.reverse();
Ok(vm.ctx.new_list(elements))
}
#[pymethod]
fn strip(&self, chars: OptionalOption<PyStringRef>) -> String {
self.value
.py_strip(
chars,
|s, chars| s.trim_matches(|c| chars.contains(c)),
|s| s.trim(),
)
.to_owned()
}
#[pymethod]
fn lstrip(&self, chars: OptionalOption<PyStringRef>) -> String {
self.value
.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<PyStringRef>) -> String {
self.value
.py_strip(
chars,
|s, chars| s.trim_end_matches(|c| chars.contains(c)),
|s| s.trim_end(),
)
.to_owned()
}
#[pymethod]
fn endswith(&self, args: pystr::StartsEndsWithArgs, vm: &VirtualMachine) -> PyResult<bool> {
self.value.py_startsendswith(
args,
"endswith",
"str",
|s, x: &PyStringRef| s.ends_with(x.borrow_value()),
vm,
)
}
#[pymethod]
fn startswith(&self, args: pystr::StartsEndsWithArgs, vm: &VirtualMachine) -> PyResult<bool> {
self.value.py_startsendswith(
args,
"startswith",
"str",
|s, x: &PyStringRef| s.starts_with(x.borrow_value()),
vm,
)
}
/// removeprefix($self, prefix, /)
///
///
/// 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: PyStringRef) -> String {
self.value
.py_removeprefix(&pref.value, pref.value.len(), |s, p| s.starts_with(p))
.to_owned()
}
/// removesuffix(self, prefix, /)
///
///
/// 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: PyStringRef) -> String {
self.value
.py_removesuffix(&suff.value, suff.value.len(), |s, p| s.ends_with(p))
.to_owned()
}
#[pymethod]
fn isalnum(&self) -> bool {
!self.value.is_empty() && self.value.chars().all(char::is_alphanumeric)
}
#[pymethod]
fn isnumeric(&self) -> bool {
!self.value.is_empty() && self.value.chars().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,
];
!self.value.is_empty()
&& self
.value
.chars()
.filter(|c| !c.is_digit(10))
.all(|c| valid_unicodes.contains(&(c as u16)))
}
#[pymethod]
fn isdecimal(&self) -> bool {
!self.value.is_empty() && self.value.chars().all(|c| c.is_ascii_digit())
}
#[pymethod(name = "__mod__")]
fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
let formatted = self.value.py_cformat(values, vm)?;
Ok(formatted)
}
#[pymethod(name = "__rmod__")]
fn rmod(&self, _values: PyObjectRef, vm: &VirtualMachine) -> PyResult {
Ok(vm.ctx.not_implemented())
}
#[pymethod]
fn format(&self, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult<String> {
match FormatString::from_str(&self.value) {
Ok(format_string) => format_string.format(&args, vm),
Err(err) => Err(err.into_pyexception(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.value) {
Ok(format_string) => format_string.format_map(&mapping, vm),
Err(err) => Err(err.into_pyexception(vm)),
}
}
#[pymethod(name = "__format__")]
fn format_str(&self, spec: PyStringRef, vm: &VirtualMachine) -> PyResult<String> {
match FormatSpec::parse(spec.borrow_value())
.and_then(|format_spec| format_spec.format_string(&self.value))
{
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.value.len());
let mut previous_is_cased = false;
for c in self.value.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.value.len());
for c in self.value.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.value.is_empty() && self.value.chars().all(char::is_alphabetic)
}
#[pymethod]
fn replace(&self, old: PyStringRef, new: PyStringRef, count: OptionalArg<isize>) -> String {
match count {
OptionalArg::Present(maxcount) if maxcount >= 0 => {
if maxcount == 0 || self.value.is_empty() {
// nothing to do; return the original bytes
return self.value.clone();
}
self.value
.replacen(&old.value, &new.value, maxcount as usize)
}
_ => self.value.replace(&old.value, &new.value),
}
}
/// 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.value
.chars()
.all(|c| c == '\u{0020}' || char_is_printable(c))
}
#[pymethod]
fn isspace(&self) -> bool {
if self.value.is_empty() {
return false;
}
use unic_ucd_bidi::bidi_class::abbr_names::*;
self.value.chars().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 {
self.value.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 {
self.value.py_iscase(char::is_uppercase, char::is_lowercase)
}
#[pymethod]
fn isascii(&self) -> bool {
self.value.is_ascii()
}
#[pymethod]
fn splitlines(&self, args: pystr::SplitLinesArgs, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx
.new_list(self.value.py_splitlines(args, |s| vm.ctx.new_str(s)))
}
#[pymethod]
fn join(&self, iterable: PyIterable<PyStringRef>, vm: &VirtualMachine) -> PyResult<String> {
let iter = iterable.iter(vm)?;
self.value.py_join(iter)
}
#[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.value.py_find(&sub.value, range, find)
}
#[pymethod]
fn find(&self, args: FindArgs) -> isize {
self._find(args, |r, s| r.find(s))
.map_or(-1, |v| v as isize)
}
#[pymethod]
fn rfind(&self, args: FindArgs) -> isize {
self._find(args, |r, s| 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| 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| r.rfind(s))
.ok_or_else(|| vm.new_value_error("substring not found".to_owned()))
}
#[pymethod]
fn partition(&self, sep: PyStringRef, vm: &VirtualMachine) -> PyResult {
let (front, has_mid, back) =
self.value
.py_partition(&sep.value, || self.value.splitn(2, &sep.value), vm)?;
Ok(vm.ctx.new_tuple(vec![
vm.ctx.new_str(front),
if has_mid {
sep.into_object()
} else {
vm.ctx.new_str("")
},
vm.ctx.new_str(back),
]))
}
#[pymethod]
fn rpartition(&self, sep: PyStringRef, vm: &VirtualMachine) -> PyResult {
let (back, has_mid, front) =
self.value
.py_partition(&sep.value, || self.value.rsplitn(2, &sep.value), vm)?;
Ok(vm.ctx.new_tuple(vec![
vm.ctx.new_str(front),
if has_mid {
sep.into_object()
} else {
vm.ctx.new_str("")
},
vm.ctx.new_str(back),
]))
}
/// Return `true` if the sequence is ASCII titlecase and the sequence is not
/// empty, `false` otherwise.
#[pymethod]
fn istitle(&self) -> bool {
if self.value.is_empty() {
return false;
}
let mut cased = false;
let mut previous_is_cased = false;
for c in self.value.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.value
.py_count(&needle.value, range, |h, n| h.matches(n).count())
}
#[pymethod]
fn zfill(&self, width: isize) -> String {
// this is safe-guaranteed because the original self.value is valid utf8
unsafe { String::from_utf8_unchecked(self.value.py_zfill(width)) }
}
#[inline]
fn _pad(
&self,
width: isize,
fillchar: OptionalArg<PyStringRef>,
pad: fn(&str, usize, char, usize) -> String,
vm: &VirtualMachine,
) -> PyResult<String> {
let fillchar = match fillchar {
OptionalArg::Present(ref s) => s.value.chars().exactly_one().map_err(|_| {
vm.new_type_error(
"The fill character must be exactly one character long".to_owned(),
)
}),
OptionalArg::Missing => Ok(' '),
}?;
Ok(if self.len() as isize >= width {
String::from(&self.value)
} else {
pad(&self.value, width as usize, fillchar, self.len())
})
}
#[pymethod]
fn center(
&self,
width: isize,
fillchar: OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
self._pad(width, fillchar, PyCommonString::<char>::py_center, vm)
}
#[pymethod]
fn ljust(
&self,
width: isize,
fillchar: OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
self._pad(width, fillchar, PyCommonString::<char>::py_ljust, vm)
}
#[pymethod]
fn rjust(
&self,
width: isize,
fillchar: OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
self._pad(width, fillchar, PyCommonString::<char>::py_rjust, vm)
}
#[pymethod]
fn expandtabs(&self, args: pystr::ExpandTabsArgs) -> String {
let tab_stop = args.tabsize();
let mut expanded_str = String::with_capacity(self.value.len());
let mut tab_size = tab_stop;
let mut col_count = 0 as usize;
for ch in self.value.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.value.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.lease_class().name)
})?;
let mut translated = String::new();
for c in self.value.chars() {
match table.get_item((c as u32).into_pyobject(vm), vm) {
Ok(value) => {
if let Some(text) = value.payload::<PyString>() {
translated.push_str(&text.value);
} else if let Some(bigint) = value.payload::<PyInt>() {
let ch = bigint
.borrow_value()
.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)
}
#[pymethod]
fn maketrans(
dict_or_str: PyObjectRef,
to_str: OptionalArg<PyStringRef>,
none_str: OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult {
let new_dict = vm.ctx.new_dict();
if let OptionalArg::Present(to_str) = to_str {
match dict_or_str.downcast::<PyString>() {
Ok(from_str) => {
if to_str.len() == from_str.len() {
for (c1, c2) in from_str.value.chars().zip(to_str.value.chars()) {
new_dict.set_item(
vm.ctx.new_int(c1 as u32),
vm.ctx.new_int(c2 as u32),
vm,
)?;
}
if let OptionalArg::Present(none_str) = none_str {
for c in none_str.value.chars() {
new_dict.set_item(vm.ctx.new_int(c as u32), vm.get_none(), vm)?;
}
}
Ok(new_dict.into_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.borrow_value().to_i32().into_pyobject(vm),
val,
vm,
)?;
} else if let Some(string) = key.payload::<PyString>() {
if string.len() == 1 {
let num_value = string.value.chars().next().unwrap() as u32;
new_dict.set_item(num_value.into_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.into_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)
}
#[pymethod(magic)]
fn iter(zelf: PyRef<Self>) -> PyStringIterator {
PyStringIterator {
position: AtomicCell::new(0),
string: zelf,
}
}
#[pymethod(magic)]
fn reversed(zelf: PyRef<Self>) -> PyStringReverseIterator {
let begin = zelf.value.chars().count();
PyStringReverseIterator {
position: AtomicCell::new(begin as isize),
string: zelf,
}
}
}
#[derive(FromArgs)]
struct EncodeArgs {
#[pyarg(positional_or_keyword, default = "None")]
encoding: Option<PyStringRef>,
#[pyarg(positional_or_keyword, default = "None")]
errors: Option<PyStringRef>,
}
pub(crate) fn encode_string(
s: PyStringRef,
encoding: Option<PyStringRef>,
errors: Option<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<PyBytesRef> {
vm.encode(s.into_object(), encoding.clone(), errors)?
.downcast::<PyBytes>()
.map_err(|obj| {
vm.new_type_error(format!(
"'{}' encoder returned '{}' instead of 'bytes'; use codecs.encode() to \
encode arbitrary types",
encoding.as_ref().map_or("utf-8", |s| s.borrow_value()),
obj.lease_class().name,
))
})
}
impl PyValue for PyString {
fn class(vm: &VirtualMachine) -> PyClassRef {
vm.ctx.types.str_type.clone()
}
}
impl IntoPyObject for String {
fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_str(self)
}
}
impl IntoPyObject for &str {
fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_str(self)
}
}
impl IntoPyObject for &String {
fn into_pyobject(self, vm: &VirtualMachine) -> PyObjectRef {
vm.ctx.new_str(self.clone())
}
}
impl TryFromObject for std::ffi::CString {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
let s = PyStringRef::try_from_object(vm, obj)?;
Self::new(s.borrow_value().to_owned())
.map_err(|_| vm.new_value_error("embedded null character".to_owned()))
}
}
impl TryFromObject for std::ffi::OsString {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
use std::str::FromStr;
let s = PyStringRef::try_from_object(vm, obj)?;
Ok(std::ffi::OsString::from_str(s.borrow_value()).unwrap())
}
}
type SplitArgs<'a> = pystr::SplitArgs<'a, PyStringRef, str, char>;
#[derive(FromArgs)]
pub struct FindArgs {
#[pyarg(positional_only, optional = false)]
sub: PyStringRef,
#[pyarg(positional_only, default = "None")]
start: Option<PyIntRef>,
#[pyarg(positional_only, default = "None")]
end: Option<PyIntRef>,
}
impl FindArgs {
fn get_value(self, len: usize) -> (PyStringRef, std::ops::Range<usize>) {
let range = adjust_indices(self.start, self.end, len);
(self.sub, range)
}
}
pub fn init(ctx: &PyContext) {
PyString::extend_class(ctx, &ctx.types.str_type);
PyStringIterator::extend_class(ctx, &ctx.types.str_iterator_type);
PyStringReverseIterator::extend_class(ctx, &ctx.types.str_reverseiterator_type);
}
pub fn clone_value(obj: &PyObjectRef) -> String {
obj.payload::<PyString>().unwrap().value.clone()
}
pub fn borrow_value(obj: &PyObjectRef) -> &str {
&obj.payload::<PyString>().unwrap().value
}
impl PySliceableSequence for PyString {
type Sliced = String;
fn do_slice(&self, range: Range<usize>) -> Self::Sliced {
self.value
.chars()
.skip(range.start)
.take(range.end - range.start)
.collect()
}
fn do_slice_reverse(&self, range: Range<usize>) -> Self::Sliced {
let count = self.len();
self.value
.chars()
.rev()
.skip(count - range.end)
.take(range.end - range.start)
.collect()
}
fn do_stepped_slice(&self, range: Range<usize>, step: usize) -> Self::Sliced {
self.value
.chars()
.skip(range.start)
.take(range.end - range.start)
.step_by(step)
.collect()
}
fn do_stepped_slice_reverse(&self, range: Range<usize>, step: usize) -> Self::Sliced {
let count = self.len();
self.value
.chars()
.rev()
.skip(count - range.end)
.take(range.end - range.start)
.step_by(step)
.collect()
}
fn empty() -> Self::Sliced {
String::new()
}
fn len(&self) -> usize {
self.len()
}
fn is_empty(&self) -> bool {
self.value.is_empty()
}
}
// According to python following categories aren't 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').
fn char_is_printable(c: char) -> bool {
let cat = GeneralCategory::of(c);
!(cat.is_other() || cat.is_separator())
}
#[cfg(test)]
mod tests {
use super::*;
#[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!(PyString::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!(PyString::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!(!PyString::from(s).istitle());
}
}
#[test]
fn str_maketrans_and_translate() {
let vm: VirtualMachine = Default::default();
let table = vm.ctx.new_dict();
table.set_item("a", vm.ctx.new_str("🎅"), &vm).unwrap();
table.set_item("b", vm.get_none(), &vm).unwrap();
table.set_item("c", vm.ctx.new_str("xda"), &vm).unwrap();
let translated = PyString::maketrans(
table.into_object(),
OptionalArg::Missing,
OptionalArg::Missing,
&vm,
)
.unwrap();
let text = PyString::from("abc");
let translated = text.translate(translated, &vm).unwrap();
assert_eq!(translated, "🎅xda".to_owned());
let translated = text.translate(vm.ctx.new_int(3), &vm);
assert_eq!(
translated.unwrap_err().lease_class().name,
"TypeError".to_owned()
);
}
}
impl PyCommonStringWrapper<str> for PyStringRef {
fn as_ref(&self) -> &str {
self.value.as_str()
}
}
impl PyCommonStringContainer<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> PyCommonString<'s, char> for str {
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<'a>(&'a self, range: std::ops::Range<usize>) -> &'a Self {
&self[range]
}
fn get_chars<'a>(&'a self, range: std::ops::Range<usize>) -> &'a 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
}
}