Add #[py_class] attribute proc macro

This commit is contained in:
coolreader18 committed 2019-03-29 00:16:34 -05:00
1 parent 6618def266
commit ff85838556
6 files changed
+517 -278

No files matched your search

+1 -1
View File
@@ -8,6 +8,6 @@ edition = "2018"
proc-macro = true
[dependencies]
syn = "0.15.29"
syn = { version = "0.15.29", features = ["full"] }
quote = "0.6.11"
proc-macro2 = "0.4.27"
+190 -8
View File
@@ -1,21 +1,57 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{Data, DeriveInput, Fields};
use syn::{
parse_macro_input, AttributeArgs, Data, DeriveInput, Fields, Ident, ImplItem, Item, Lit, Meta,
NestedMeta,
};
#[proc_macro_derive(FromArgs)]
fn rustpython_path(inside_vm: bool) -> syn::Path {
let path = if inside_vm {
quote!(crate)
} else {
quote!(::rustpython_vm)
};
syn::parse2(path).unwrap()
}
/// Does the item have the #[__inside_vm] attribute on it, signifying that the derive target is
/// being derived from inside the `rustpython_vm` crate.
fn rustpython_path_derive(input: &DeriveInput) -> syn::Path {
rustpython_path(
input
.attrs
.iter()
.any(|attr| attr.path.is_ident("__inside_vm")),
)
}
fn rustpython_path_attr(attr: &AttributeArgs) -> syn::Path {
rustpython_path(attr.iter().any(|meta| {
if let syn::NestedMeta::Meta(meta) = meta {
if let syn::Meta::Word(ident) = meta {
ident == "__inside_vm"
} else {
false
}
} else {
false
}
}))
}
#[proc_macro_derive(FromArgs, attributes(__inside_vm))]
pub fn derive_from_args(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
let gen = impl_from_args(&ast);
let gen = impl_from_args(ast);
gen.to_string().parse().unwrap()
}
fn impl_from_args(input: &DeriveInput) -> TokenStream2 {
// FIXME: This references types using `crate` instead of `rustpython_vm`
// so that it can be used in the latter. How can we support both?
fn impl_from_args(input: DeriveInput) -> TokenStream2 {
let rp_path = rustpython_path_derive(&input);
let fields = match input.data {
Data::Struct(ref data) => {
match data.fields {
@@ -36,7 +72,7 @@ fn impl_from_args(input: &DeriveInput) -> TokenStream2 {
let name = &input.ident;
quote! {
impl crate::function::FromArgs for #name {
impl #rp_path::function::FromArgs for #name {
fn from_args(
vm: &crate::vm::VirtualMachine,
args: &mut crate::function::PyFuncArgs
@@ -46,3 +82,149 @@ fn impl_from_args(input: &DeriveInput) -> TokenStream2 {
}
}
}
#[proc_macro_attribute]
pub fn py_class(attr: TokenStream, item: TokenStream) -> TokenStream {
let attr = parse_macro_input!(attr as AttributeArgs);
let item = parse_macro_input!(item as Item);
impl_py_class(attr, item).into()
}
enum MethodKind {
Method,
Property,
}
impl MethodKind {
fn to_ctx_constructor_fn(&self) -> Ident {
let f = match self {
MethodKind::Method => "new_rustfunc",
MethodKind::Property => "new_property",
};
Ident::new(f, Span::call_site())
}
}
struct Method {
fn_name: Ident,
py_name: String,
kind: MethodKind,
}
fn item_impl_to_methods<'a>(imp: &'a syn::ItemImpl) -> impl Iterator<Item = Method> + 'a {
imp.items.iter().filter_map(|item| {
if let ImplItem::Method(meth) = item {
let mut py_name = None;
let mut kind = MethodKind::Method;
let metas_iter = meth
.attrs
.iter()
.filter_map(|attr| {
if attr.path.is_ident("py_class") {
let meta = attr.parse_meta().expect("Invalid attribute");
if let Meta::List(list) = meta {
Some(list)
} else {
panic!(
"#[py_class] attribute on a method should be a list, like \
#[py_class(...)]"
)
}
} else {
None
}
})
.flat_map(|attr| attr.nested);
for meta in metas_iter {
if let NestedMeta::Meta(meta) = meta {
match meta {
Meta::NameValue(name_value) => {
if name_value.ident == "name" {
if let Lit::Str(s) = &name_value.lit {
py_name = Some(s.value());
} else {
panic!("#[py_class(name = ...)] must be a string");
}
}
}
Meta::Word(ident) => match ident.to_string().as_str() {
"property" => kind = MethodKind::Property,
_ => {}
},
_ => {}
}
}
}
let py_name = py_name.unwrap_or_else(|| meth.sig.ident.to_string());
Some(Method {
fn_name: meth.sig.ident.clone(),
py_name,
kind,
})
} else {
None
}
})
}
fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 {
let imp = if let Item::Impl(imp) = item {
imp
} else {
return quote!(#item);
};
let rp_path = rustpython_path_attr(&attr);
let mut class_name = None;
let mut doc = None;
for attr in attr {
if let NestedMeta::Meta(meta) = attr {
if let Meta::NameValue(name_value) = meta {
if name_value.ident == "name" {
if let Lit::Str(s) = name_value.lit {
class_name = Some(s.value());
} else {
panic!("#[py_class(name = ...)] must be a string");
}
} else if name_value.ident == "doc" {
if let Lit::Str(s) = name_value.lit {
doc = Some(s.value());
} else {
panic!("#[py_class(name = ...)] must be a string");
}
}
}
}
}
let class_name = class_name.expect("#[py_class] must have a name");
let doc = match doc {
Some(doc) => quote!(Some(#doc)),
None => quote!(None),
};
let ty = &imp.self_ty;
let methods = item_impl_to_methods(&imp).map(
|Method {
py_name,
fn_name,
kind,
}| {
let constructor_fn = kind.to_ctx_constructor_fn();
quote! {
ctx.set_attr(class, #py_name, ctx.#constructor_fn(#ty::#fn_name));
}
},
);
quote! {
#imp
impl #rp_path::pyobject::IntoPyClass for #ty {
const NAME: &'static str = #class_name;
const DOC: Option<&'static str> = #doc;
fn _extend_class(
ctx: &#rp_path::pyobject::PyContext,
class: &#rp_path::obj::objtype::PyClassRef,
) {
#(#methods)*
}
}
}
}
+1
View File
@@ -536,6 +536,7 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
}
#[derive(Debug, FromArgs)]
#[__inside_vm]
pub struct PrintOptions {
sep: Option<PyStringRef>,
end: Option<PyStringRef>,
+1 -1
View File
@@ -30,7 +30,7 @@ extern crate statrs;
extern crate rustpython_parser;
#[macro_use]
extern crate rustpython_derive;
pub extern crate rustpython_derive;
//extern crate eval; use eval::eval::*;
// use py_code_object::{Function, NativeType, PyCodeObject};
+304 -268
View File
@@ -10,8 +10,8 @@ use unicode_segmentation::UnicodeSegmentation;
use crate::format::{FormatParseError, FormatPart, FormatString};
use crate::function::{OptionalArg, PyFuncArgs};
use crate::pyobject::{
IdProtocol, IntoPyObject, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue,
TryFromObject, TryIntoRef, TypeProtocol,
IdProtocol, IntoPyClass, IntoPyObject, PyContext, PyIterable, PyObjectRef, PyRef, PyResult,
PyValue, TryFromObject, TryIntoRef, TypeProtocol,
};
use crate::vm::VirtualMachine;
@@ -48,80 +48,138 @@ impl TryIntoRef<PyString> for &str {
}
}
impl PyStringRef {
fn add(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(format!("{}{}", self.value, get_value(&rhs)))
fn get_fill_char<'a>(rep: &'a OptionalArg<PyStringRef>, vm: &VirtualMachine) -> PyResult<&'a str> {
let rep_str = match rep {
OptionalArg::Present(ref st) => &st.value,
OptionalArg::Missing => " ",
};
if rep_str.len() == 1 {
Ok(rep_str)
} else {
Err(vm.new_type_error("The fill character must be exactly one character long".to_string()))
}
}
#[py_class(
__inside_vm,
name = "str",
doc = "str(object='') -> str\n\
str(bytes_or_buffer[, encoding[, errors]]) -> str\n\
\n\
Create a new string object from the given object. If encoding or\n\
errors is specified, then the object must expose a data buffer\n\
that will be decoded using the given encoding and error handler.\n\
Otherwise, returns the result of object.__str__() (if defined)\n\
or repr(object).\n\
encoding defaults to sys.getdefaultencoding().\n\
errors defaults to 'strict'."
)]
impl PyString {
// TODO: should with following format
// class str(object='')
// class str(object=b'', encoding='utf-8', errors='strict')
#[py_class(name = "__new__")]
fn new(
cls: PyClassRef,
object: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyStringRef> {
let string = match object {
OptionalArg::Present(ref input) => vm.to_str(input)?.into_object(),
OptionalArg::Missing => vm.new_str("".to_string()),
};
if string.class().is(&cls) {
TryFromObject::try_from_object(vm, string)
} else {
Err(vm.new_type_error(format!("Cannot add {} and {}", self, rhs)))
let payload = string.payload::<PyString>().unwrap();
payload.clone().into_ref_with_type(vm, cls)
}
}
#[py_class(name = "__add__")]
fn add(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(format!("{}{}", zelf.value, get_value(&rhs)))
} else {
Err(vm.new_type_error(format!("Cannot add {} and {}", zelf, rhs)))
}
}
fn bool(self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty()
#[py_class(name = "__bool__")]
fn bool(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
!zelf.value.is_empty()
}
fn eq(self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool {
#[py_class(name = "__eq__")]
fn eq(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> bool {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
self.value == get_value(&rhs)
zelf.value == get_value(&rhs)
} else {
false
}
}
fn contains(self, needle: PyStringRef, _vm: &VirtualMachine) -> bool {
self.value.contains(&needle.value)
#[py_class(name = "__contains__")]
fn contains(zelf: PyStringRef, needle: PyStringRef, _vm: &VirtualMachine) -> bool {
zelf.value.contains(&needle.value)
}
fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
subscript(vm, &self.value, needle)
#[py_class(name = "__getitem__")]
fn getitem(zelf: PyStringRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult {
subscript(vm, &zelf.value, needle)
}
fn gt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
#[py_class(name = "__gt__")]
fn gt(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(self.value > get_value(&rhs))
Ok(zelf.value > get_value(&rhs))
} else {
Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs)))
Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs)))
}
}
fn ge(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
#[py_class(name = "__ge__")]
fn ge(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(self.value >= get_value(&rhs))
Ok(zelf.value >= get_value(&rhs))
} else {
Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs)))
Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs)))
}
}
fn lt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
#[py_class(name = "__lt__")]
fn lt(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(self.value < get_value(&rhs))
Ok(zelf.value < get_value(&rhs))
} else {
Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs)))
Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs)))
}
}
fn le(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
#[py_class(name = "__le__")]
fn le(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
if objtype::isinstance(&rhs, &vm.ctx.str_type()) {
Ok(self.value <= get_value(&rhs))
Ok(zelf.value <= get_value(&rhs))
} else {
Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs)))
Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs)))
}
}
fn hash(self, _vm: &VirtualMachine) -> usize {
#[py_class(name = "__hash__")]
fn hash(zelf: PyStringRef, _vm: &VirtualMachine) -> usize {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
self.value.hash(&mut hasher);
zelf.value.hash(&mut hasher);
hasher.finish() as usize
}
fn len(self, _vm: &VirtualMachine) -> usize {
self.value.chars().count()
#[py_class(name = "__len__")]
fn len(zelf: PyStringRef, _vm: &VirtualMachine) -> usize {
zelf.value.chars().count()
}
fn mul(self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
#[py_class(name = "__mul__")]
fn mul(zelf: PyStringRef, val: PyObjectRef, vm: &VirtualMachine) -> PyResult<String> {
if objtype::isinstance(&val, &vm.ctx.int_type()) {
let value = &self.value;
let value = &zelf.value;
let multiplier = objint::get_value(&val).to_i32().unwrap();
let mut result = String::new();
for _x in 0..multiplier {
@@ -129,16 +187,18 @@ impl PyStringRef {
}
Ok(result)
} else {
Err(vm.new_type_error(format!("Cannot multiply {} and {}", self, val)))
Err(vm.new_type_error(format!("Cannot multiply {} and {}", zelf, val)))
}
}
fn str(self, _vm: &VirtualMachine) -> PyStringRef {
self
#[py_class(name = "__str__")]
fn str(zelf: PyStringRef, _vm: &VirtualMachine) -> PyStringRef {
zelf
}
fn repr(self, _vm: &VirtualMachine) -> String {
let value = &self.value;
#[py_class(name = "__repr__")]
fn repr(zelf: PyStringRef, _vm: &VirtualMachine) -> String {
let value = &zelf.value;
let quote_char = if count_char(value, '\'') > count_char(value, '"') {
'"'
} else {
@@ -167,31 +227,31 @@ impl PyStringRef {
formatted
}
fn lower(self, _vm: &VirtualMachine) -> String {
self.value.to_lowercase()
fn lower(zelf: PyStringRef, _vm: &VirtualMachine) -> String {
zelf.value.to_lowercase()
}
// casefold is much more aggressive than lower
fn casefold(self, _vm: &VirtualMachine) -> String {
caseless::default_case_fold_str(&self.value)
fn casefold(zelf: PyStringRef, _vm: &VirtualMachine) -> String {
caseless::default_case_fold_str(&zelf.value)
}
fn upper(self, _vm: &VirtualMachine) -> String {
self.value.to_uppercase()
fn upper(zelf: PyStringRef, _vm: &VirtualMachine) -> String {
zelf.value.to_uppercase()
}
fn capitalize(self, _vm: &VirtualMachine) -> String {
let (first_part, lower_str) = self.value.split_at(1);
fn capitalize(zelf: PyStringRef, _vm: &VirtualMachine) -> String {
let (first_part, lower_str) = zelf.value.split_at(1);
format!("{}{}", first_part.to_uppercase(), lower_str)
}
fn split(
self,
pattern: OptionalArg<Self>,
zelf: PyStringRef,
pattern: OptionalArg<PyStringRef>,
num: OptionalArg<usize>,
vm: &VirtualMachine,
) -> PyObjectRef {
let value = &self.value;
let value = &zelf.value;
let pattern = match pattern {
OptionalArg::Present(ref s) => &s.value,
OptionalArg::Missing => " ",
@@ -207,12 +267,12 @@ impl PyStringRef {
}
fn rsplit(
self,
pattern: OptionalArg<Self>,
zelf: PyStringRef,
pattern: OptionalArg<PyStringRef>,
num: OptionalArg<usize>,
vm: &VirtualMachine,
) -> PyObjectRef {
let value = &self.value;
let value = &zelf.value;
let pattern = match pattern {
OptionalArg::Present(ref s) => &s.value,
OptionalArg::Missing => " ",
@@ -227,85 +287,113 @@ impl PyStringRef {
vm.ctx.new_list(elements)
}
fn strip(self, _vm: &VirtualMachine) -> String {
self.value.trim().to_string()
fn strip(zelf: PyStringRef, _vm: &VirtualMachine) -> String {
zelf.value.trim().to_string()
}
fn lstrip(self, _vm: &VirtualMachine) -> String {
self.value.trim_start().to_string()
fn lstrip(zelf: PyStringRef, _vm: &VirtualMachine) -> String {
zelf.value.trim_start().to_string()
}
fn rstrip(self, _vm: &VirtualMachine) -> String {
self.value.trim_end().to_string()
fn rstrip(zelf: PyStringRef, _vm: &VirtualMachine) -> String {
zelf.value.trim_end().to_string()
}
fn endswith(
self,
zelf: PyStringRef,
suffix: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
_vm: &VirtualMachine,
) -> bool {
if let Some((start, end)) = adjust_indices(start, end, self.value.len()) {
self.value[start..end].ends_with(&suffix.value)
if let Some((start, end)) = adjust_indices(start, end, zelf.value.len()) {
zelf.value[start..end].ends_with(&suffix.value)
} else {
false
}
}
fn startswith(
self,
zelf: PyStringRef,
prefix: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
_vm: &VirtualMachine,
) -> bool {
if let Some((start, end)) = adjust_indices(start, end, self.value.len()) {
self.value[start..end].starts_with(&prefix.value)
if let Some((start, end)) = adjust_indices(start, end, zelf.value.len()) {
zelf.value[start..end].starts_with(&prefix.value)
} else {
false
}
}
fn isalnum(self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty() && self.value.chars().all(char::is_alphanumeric)
fn isalnum(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
!zelf.value.is_empty() && zelf.value.chars().all(char::is_alphanumeric)
}
fn isnumeric(self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty() && self.value.chars().all(char::is_numeric)
fn isnumeric(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
!zelf.value.is_empty() && zelf.value.chars().all(char::is_numeric)
}
fn isdigit(self, _vm: &VirtualMachine) -> bool {
fn isdigit(zelf: PyStringRef, _vm: &VirtualMachine) -> 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,
];
if self.value.is_empty() {
if zelf.value.is_empty() {
false
} else {
self.value
zelf.value
.chars()
.filter(|c| !c.is_digit(10))
.all(|c| valid_unicodes.contains(&(c as u16)))
}
}
fn isdecimal(self, _vm: &VirtualMachine) -> bool {
if self.value.is_empty() {
fn isdecimal(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
if zelf.value.is_empty() {
false
} else {
self.value.chars().all(|c| c.is_ascii_digit())
zelf.value.chars().all(|c| c.is_ascii_digit())
}
}
fn title(self, _vm: &VirtualMachine) -> String {
make_title(&self.value)
fn format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
if args.args.is_empty() {
return Err(vm.new_type_error(
"descriptor 'format' of 'str' object needs an argument".to_string(),
));
}
let zelf = &args.args[0];
if !objtype::isinstance(&zelf, &vm.ctx.str_type()) {
let zelf_typ = zelf.class();
let actual_type = vm.to_pystr(&zelf_typ)?;
return Err(vm.new_type_error(format!(
"descriptor 'format' requires a 'str' object but received a '{}'",
actual_type
)));
}
let format_string_text = get_value(zelf);
match FormatString::from_str(format_string_text.as_str()) {
Ok(format_string) => perform_format(vm, &format_string, &args),
Err(err) => match err {
FormatParseError::UnmatchedBracket => {
Err(vm.new_value_error("expected '}' before end of string".to_string()))
}
_ => Err(vm.new_value_error("Unexpected error parsing format string".to_string())),
},
}
}
fn swapcase(self, _vm: &VirtualMachine) -> String {
let mut swapped_str = String::with_capacity(self.value.len());
for c in self.value.chars() {
fn title(zelf: PyStringRef, _vm: &VirtualMachine) -> String {
make_title(&zelf.value)
}
fn swapcase(zelf: PyStringRef, _vm: &VirtualMachine) -> String {
let mut swapped_str = String::with_capacity(zelf.value.len());
for c in zelf.value.chars() {
// to_uppercase returns an iterator, to_ascii_uppercase returns the char
if c.is_lowercase() {
swapped_str.push(c.to_ascii_uppercase());
@@ -318,54 +406,54 @@ impl PyStringRef {
swapped_str
}
fn isalpha(self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty() && self.value.chars().all(char::is_alphanumeric)
fn isalpha(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
!zelf.value.is_empty() && zelf.value.chars().all(char::is_alphanumeric)
}
fn replace(
self,
old: Self,
new: Self,
zelf: PyStringRef,
old: PyStringRef,
new: PyStringRef,
num: OptionalArg<usize>,
_vm: &VirtualMachine,
) -> String {
match num.into_option() {
Some(num) => self.value.replacen(&old.value, &new.value, num),
None => self.value.replace(&old.value, &new.value),
Some(num) => zelf.value.replacen(&old.value, &new.value, num),
None => zelf.value.replace(&old.value, &new.value),
}
}
// cpython's isspace ignores whitespace, including \t and \n, etc, unless the whole string is empty
// which is why isspace is using is_ascii_whitespace. Same for isupper & islower
fn isspace(self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty() && self.value.chars().all(|c| c.is_ascii_whitespace())
fn isspace(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
!zelf.value.is_empty() && zelf.value.chars().all(|c| c.is_ascii_whitespace())
}
fn isupper(self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty()
&& self
fn isupper(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
!zelf.value.is_empty()
&& zelf
.value
.chars()
.filter(|x| !x.is_ascii_whitespace())
.all(char::is_uppercase)
}
fn islower(self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty()
&& self
fn islower(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
!zelf.value.is_empty()
&& zelf
.value
.chars()
.filter(|x| !x.is_ascii_whitespace())
.all(char::is_lowercase)
}
fn isascii(self, _vm: &VirtualMachine) -> bool {
!self.value.is_empty() && self.value.chars().all(|c| c.is_ascii())
fn isascii(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
!zelf.value.is_empty() && zelf.value.chars().all(|c| c.is_ascii())
}
// doesn't implement keep new line delimiter just yet
fn splitlines(self, vm: &VirtualMachine) -> PyObjectRef {
let elements = self
fn splitlines(zelf: PyStringRef, vm: &VirtualMachine) -> PyObjectRef {
let elements = zelf
.value
.split('\n')
.map(|e| vm.ctx.new_str(e.to_string()))
@@ -373,13 +461,17 @@ impl PyStringRef {
vm.ctx.new_list(elements)
}
fn join(self, iterable: PyIterable<PyStringRef>, vm: &VirtualMachine) -> PyResult<String> {
fn join(
zelf: PyStringRef,
iterable: PyIterable<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
let mut joined = String::new();
for (idx, elem) in iterable.iter(vm)?.enumerate() {
let elem = elem?;
if idx != 0 {
joined.push_str(&self.value);
joined.push_str(&zelf.value);
}
joined.push_str(&elem.value)
}
@@ -388,13 +480,13 @@ impl PyStringRef {
}
fn find(
self,
sub: Self,
zelf: PyStringRef,
sub: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
_vm: &VirtualMachine,
) -> isize {
let value = &self.value;
let value = &zelf.value;
if let Some((start, end)) = adjust_indices(start, end, value.len()) {
match value[start..end].find(&sub.value) {
Some(num) => (start + num) as isize,
@@ -406,13 +498,13 @@ impl PyStringRef {
}
fn rfind(
self,
sub: Self,
zelf: PyStringRef,
sub: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
_vm: &VirtualMachine,
) -> isize {
let value = &self.value;
let value = &zelf.value;
if let Some((start, end)) = adjust_indices(start, end, value.len()) {
match value[start..end].rfind(&sub.value) {
Some(num) => (start + num) as isize,
@@ -424,13 +516,13 @@ impl PyStringRef {
}
fn index(
self,
sub: Self,
zelf: PyStringRef,
sub: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult<usize> {
let value = &self.value;
let value = &zelf.value;
if let Some((start, end)) = adjust_indices(start, end, value.len()) {
match value[start..end].find(&sub.value) {
Some(num) => Ok(start + num),
@@ -442,13 +534,13 @@ impl PyStringRef {
}
fn rindex(
self,
sub: Self,
zelf: PyStringRef,
sub: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
vm: &VirtualMachine,
) -> PyResult<usize> {
let value = &self.value;
let value = &zelf.value;
if let Some((start, end)) = adjust_indices(start, end, value.len()) {
match value[start..end].rfind(&sub.value) {
Some(num) => Ok(start + num),
@@ -459,8 +551,8 @@ impl PyStringRef {
}
}
fn partition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef {
let value = &self.value;
fn partition(zelf: PyStringRef, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef {
let value = &zelf.value;
let sub = &sub.value;
let mut new_tup = Vec::new();
if value.contains(sub) {
@@ -477,8 +569,8 @@ impl PyStringRef {
vm.ctx.new_tuple(new_tup)
}
fn rpartition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef {
let value = &self.value;
fn rpartition(zelf: PyStringRef, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef {
let value = &zelf.value;
let sub = &sub.value;
let mut new_tup = Vec::new();
if value.contains(sub) {
@@ -496,31 +588,31 @@ impl PyStringRef {
vm.ctx.new_tuple(new_tup)
}
fn istitle(self, _vm: &VirtualMachine) -> bool {
if self.value.is_empty() {
fn istitle(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
if zelf.value.is_empty() {
false
} else {
self.value.split(' ').all(|word| word == make_title(word))
zelf.value.split(' ').all(|word| word == make_title(word))
}
}
fn count(
self,
sub: Self,
zelf: PyStringRef,
sub: PyStringRef,
start: OptionalArg<isize>,
end: OptionalArg<isize>,
_vm: &VirtualMachine,
) -> usize {
let value = &self.value;
let value = &zelf.value;
if let Some((start, end)) = adjust_indices(start, end, value.len()) {
self.value[start..end].matches(&sub.value).count()
zelf.value[start..end].matches(&sub.value).count()
} else {
0
}
}
fn zfill(self, len: usize, _vm: &VirtualMachine) -> String {
let value = &self.value;
fn zfill(zelf: PyStringRef, len: usize, _vm: &VirtualMachine) -> String {
let value = &zelf.value;
if len <= value.len() {
value.to_string()
} else {
@@ -528,35 +620,36 @@ impl PyStringRef {
}
}
fn get_fill_char<'a>(rep: &'a OptionalArg<Self>, vm: &VirtualMachine) -> PyResult<&'a str> {
let rep_str = match rep {
OptionalArg::Present(ref st) => &st.value,
OptionalArg::Missing => " ",
};
if rep_str.len() == 1 {
Ok(rep_str)
} else {
Err(vm.new_type_error(
"The fill character must be exactly one character long".to_string(),
))
}
}
fn ljust(self, len: usize, rep: OptionalArg<Self>, vm: &VirtualMachine) -> PyResult<String> {
let value = &self.value;
let rep_char = PyStringRef::get_fill_char(&rep, vm)?;
fn ljust(
zelf: PyStringRef,
len: usize,
rep: OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
let value = &zelf.value;
let rep_char = get_fill_char(&rep, vm)?;
Ok(format!("{}{}", value, rep_char.repeat(len)))
}
fn rjust(self, len: usize, rep: OptionalArg<Self>, vm: &VirtualMachine) -> PyResult<String> {
let value = &self.value;
let rep_char = PyStringRef::get_fill_char(&rep, vm)?;
fn rjust(
zelf: PyStringRef,
len: usize,
rep: OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
let value = &zelf.value;
let rep_char = get_fill_char(&rep, vm)?;
Ok(format!("{}{}", rep_char.repeat(len), value))
}
fn center(self, len: usize, rep: OptionalArg<Self>, vm: &VirtualMachine) -> PyResult<String> {
let value = &self.value;
let rep_char = PyStringRef::get_fill_char(&rep, vm)?;
fn center(
zelf: PyStringRef,
len: usize,
rep: OptionalArg<PyStringRef>,
vm: &VirtualMachine,
) -> PyResult<String> {
let value = &zelf.value;
let rep_char = get_fill_char(&rep, vm)?;
let left_buff: usize = (len - value.len()) / 2;
let right_buff = len - value.len() - left_buff;
Ok(format!(
@@ -567,12 +660,12 @@ impl PyStringRef {
))
}
fn expandtabs(self, tab_stop: OptionalArg<usize>, _vm: &VirtualMachine) -> String {
fn expandtabs(zelf: PyStringRef, tab_stop: OptionalArg<usize>, _vm: &VirtualMachine) -> String {
let tab_stop = tab_stop.into_option().unwrap_or(8 as usize);
let mut expanded_str = String::new();
let mut tab_size = tab_stop;
let mut col_count = 0 as usize;
for ch in self.value.chars() {
for ch in zelf.value.chars() {
// 0x0009 is tab
if ch == 0x0009 as char {
let num_spaces = tab_size - col_count;
@@ -590,8 +683,8 @@ impl PyStringRef {
expanded_str
}
fn isidentifier(self, _vm: &VirtualMachine) -> bool {
let value = &self.value;
fn isidentifier(zelf: PyStringRef, _vm: &VirtualMachine) -> bool {
let value = &zelf.value;
// a string is not an identifier if it has whitespace or starts with a number
if !value.chars().any(|c| c.is_ascii_whitespace())
&& !value.chars().nth(0).unwrap().is_digit(10)
@@ -623,75 +716,66 @@ impl IntoPyObject for String {
#[rustfmt::skip] // to avoid line splitting
pub fn init(context: &PyContext) {
let str_type = &context.str_type;
let str_doc = "str(object='') -> str\n\
str(bytes_or_buffer[, encoding[, errors]]) -> str\n\
\n\
Create a new string object from the given object. If encoding or\n\
errors is specified, then the object must expose a data buffer\n\
that will be decoded using the given encoding and error handler.\n\
Otherwise, returns the result of object.__str__() (if defined)\n\
or repr(object).\n\
encoding defaults to sys.getdefaultencoding().\n\
errors defaults to 'strict'.";
extend_class!(context, str_type, {
"__add__" => context.new_rustfunc(PyStringRef::add),
"__bool__" => context.new_rustfunc(PyStringRef::bool),
"__contains__" => context.new_rustfunc(PyStringRef::contains),
"__doc__" => context.new_str(str_doc.to_string()),
"__eq__" => context.new_rustfunc(PyStringRef::eq),
"__ge__" => context.new_rustfunc(PyStringRef::ge),
"__getitem__" => context.new_rustfunc(PyStringRef::getitem),
"__gt__" => context.new_rustfunc(PyStringRef::gt),
"__hash__" => context.new_rustfunc(PyStringRef::hash),
"__lt__" => context.new_rustfunc(PyStringRef::lt),
"__le__" => context.new_rustfunc(PyStringRef::le),
"__len__" => context.new_rustfunc(PyStringRef::len),
"__mul__" => context.new_rustfunc(PyStringRef::mul),
"__new__" => context.new_rustfunc(str_new),
"__repr__" => context.new_rustfunc(PyStringRef::repr),
"__str__" => context.new_rustfunc(PyStringRef::str),
"capitalize" => context.new_rustfunc(PyStringRef::capitalize),
"casefold" => context.new_rustfunc(PyStringRef::casefold),
"center" => context.new_rustfunc(PyStringRef::center),
"count" => context.new_rustfunc(PyStringRef::count),
"endswith" => context.new_rustfunc(PyStringRef::endswith),
"expandtabs" => context.new_rustfunc(PyStringRef::expandtabs),
"find" => context.new_rustfunc(PyStringRef::find),
"format" => context.new_rustfunc(str_format),
"index" => context.new_rustfunc(PyStringRef::index),
"isalnum" => context.new_rustfunc(PyStringRef::isalnum),
"isalpha" => context.new_rustfunc(PyStringRef::isalpha),
"isascii" => context.new_rustfunc(PyStringRef::isascii),
"isdecimal" => context.new_rustfunc(PyStringRef::isdecimal),
"isdigit" => context.new_rustfunc(PyStringRef::isdigit),
"isidentifier" => context.new_rustfunc(PyStringRef::isidentifier),
"islower" => context.new_rustfunc(PyStringRef::islower),
"isnumeric" => context.new_rustfunc(PyStringRef::isnumeric),
"isspace" => context.new_rustfunc(PyStringRef::isspace),
"isupper" => context.new_rustfunc(PyStringRef::isupper),
"istitle" => context.new_rustfunc(PyStringRef::istitle),
"join" => context.new_rustfunc(PyStringRef::join),
"lower" => context.new_rustfunc(PyStringRef::lower),
"ljust" => context.new_rustfunc(PyStringRef::ljust),
"lstrip" => context.new_rustfunc(PyStringRef::lstrip),
"partition" => context.new_rustfunc(PyStringRef::partition),
"replace" => context.new_rustfunc(PyStringRef::replace),
"rfind" => context.new_rustfunc(PyStringRef::rfind),
"rindex" => context.new_rustfunc(PyStringRef::rindex),
"rjust" => context.new_rustfunc(PyStringRef::rjust),
"rpartition" => context.new_rustfunc(PyStringRef::rpartition),
"rsplit" => context.new_rustfunc(PyStringRef::rsplit),
"rstrip" => context.new_rustfunc(PyStringRef::rstrip),
"split" => context.new_rustfunc(PyStringRef::split),
"splitlines" => context.new_rustfunc(PyStringRef::splitlines),
"startswith" => context.new_rustfunc(PyStringRef::startswith),
"strip" => context.new_rustfunc(PyStringRef::strip),
"swapcase" => context.new_rustfunc(PyStringRef::swapcase),
"title" => context.new_rustfunc(PyStringRef::title),
"upper" => context.new_rustfunc(PyStringRef::upper),
"zfill" => context.new_rustfunc(PyStringRef::zfill),
});
PyString::extend_class(context, str_type);
// extend_class!(context, str_type, {
// "__add__" => context.new_rustfunc(PyStringRef::add),
// "__bool__" => context.new_rustfunc(PyStringRef::bool),
// "__contains__" => context.new_rustfunc(PyStringRef::contains),
// "__doc__" => context.new_str(str_doc.to_string()),
// "__eq__" => context.new_rustfunc(PyStringRef::eq),
// "__ge__" => context.new_rustfunc(PyStringRef::ge),
// "__getitem__" => context.new_rustfunc(PyStringRef::getitem),
// "__gt__" => context.new_rustfunc(PyStringRef::gt),
// "__hash__" => context.new_rustfunc(PyStringRef::hash),
// "__lt__" => context.new_rustfunc(PyStringRef::lt),
// "__le__" => context.new_rustfunc(PyStringRef::le),
// "__len__" => context.new_rustfunc(PyStringRef::len),
// "__mul__" => context.new_rustfunc(PyStringRef::mul),
// "__new__" => context.new_rustfunc(str_new),
// "__repr__" => context.new_rustfunc(PyStringRef::repr),
// "__str__" => context.new_rustfunc(PyStringRef::str),
// "capitalize" => context.new_rustfunc(PyStringRef::capitalize),
// "casefold" => context.new_rustfunc(PyStringRef::casefold),
// "center" => context.new_rustfunc(PyStringRef::center),
// "count" => context.new_rustfunc(PyStringRef::count),
// "endswith" => context.new_rustfunc(PyStringRef::endswith),
// "expandtabs" => context.new_rustfunc(PyStringRef::expandtabs),
// "find" => context.new_rustfunc(PyStringRef::find),
// "format" => context.new_rustfunc(str_format),
// "index" => context.new_rustfunc(PyStringRef::index),
// "isalnum" => context.new_rustfunc(PyStringRef::isalnum),
// "isalpha" => context.new_rustfunc(PyStringRef::isalpha),
// "isascii" => context.new_rustfunc(PyStringRef::isascii),
// "isdecimal" => context.new_rustfunc(PyStringRef::isdecimal),
// "isdigit" => context.new_rustfunc(PyStringRef::isdigit),
// "isidentifier" => context.new_rustfunc(PyStringRef::isidentifier),
// "islower" => context.new_rustfunc(PyStringRef::islower),
// "isnumeric" => context.new_rustfunc(PyStringRef::isnumeric),
// "isspace" => context.new_rustfunc(PyStringRef::isspace),
// "isupper" => context.new_rustfunc(PyStringRef::isupper),
// "istitle" => context.new_rustfunc(PyStringRef::istitle),
// "join" => context.new_rustfunc(PyStringRef::join),
// "lower" => context.new_rustfunc(PyStringRef::lower),
// "ljust" => context.new_rustfunc(PyStringRef::ljust),
// "lstrip" => context.new_rustfunc(PyStringRef::lstrip),
// "partition" => context.new_rustfunc(PyStringRef::partition),
// "replace" => context.new_rustfunc(PyStringRef::replace),
// "rfind" => context.new_rustfunc(PyStringRef::rfind),
// "rindex" => context.new_rustfunc(PyStringRef::rindex),
// "rjust" => context.new_rustfunc(PyStringRef::rjust),
// "rpartition" => context.new_rustfunc(PyStringRef::rpartition),
// "rsplit" => context.new_rustfunc(PyStringRef::rsplit),
// "rstrip" => context.new_rustfunc(PyStringRef::rstrip),
// "split" => context.new_rustfunc(PyStringRef::split),
// "splitlines" => context.new_rustfunc(PyStringRef::splitlines),
// "startswith" => context.new_rustfunc(PyStringRef::startswith),
// "strip" => context.new_rustfunc(PyStringRef::strip),
// "swapcase" => context.new_rustfunc(PyStringRef::swapcase),
// "title" => context.new_rustfunc(PyStringRef::title),
// "upper" => context.new_rustfunc(PyStringRef::upper),
// "zfill" => context.new_rustfunc(PyStringRef::zfill),
// });
}
pub fn get_value(obj: &PyObjectRef) -> String {
@@ -706,34 +790,6 @@ fn count_char(s: &str, c: char) -> usize {
s.chars().filter(|x| *x == c).count()
}
fn str_format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
if args.args.is_empty() {
return Err(
vm.new_type_error("descriptor 'format' of 'str' object needs an argument".to_string())
);
}
let zelf = &args.args[0];
if !objtype::isinstance(&zelf, &vm.ctx.str_type()) {
let zelf_typ = zelf.class();
let actual_type = vm.to_pystr(&zelf_typ)?;
return Err(vm.new_type_error(format!(
"descriptor 'format' requires a 'str' object but received a '{}'",
actual_type
)));
}
let format_string_text = get_value(zelf);
match FormatString::from_str(format_string_text.as_str()) {
Ok(format_string) => perform_format(vm, &format_string, &args),
Err(err) => match err {
FormatParseError::UnmatchedBracket => {
Err(vm.new_value_error("expected '}' before end of string".to_string()))
}
_ => Err(vm.new_value_error("Unexpected error parsing format string".to_string())),
},
}
}
fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: &str) -> PyResult {
let returned_type = vm.ctx.new_str(format_spec.to_string());
let result = vm.call_method(&argument, "__format__", vec![returned_type])?;
@@ -797,26 +853,6 @@ fn perform_format(
Ok(vm.ctx.new_str(final_string))
}
// TODO: should with following format
// class str(object='')
// class str(object=b'', encoding='utf-8', errors='strict')
fn str_new(
cls: PyClassRef,
object: OptionalArg<PyObjectRef>,
vm: &VirtualMachine,
) -> PyResult<PyStringRef> {
let string = match object {
OptionalArg::Present(ref input) => vm.to_str(input)?.into_object(),
OptionalArg::Missing => vm.new_str("".to_string()),
};
if string.class().is(&cls) {
TryFromObject::try_from_object(vm, string)
} else {
let payload = string.payload::<PyString>().unwrap();
payload.clone().into_ref_with_type(vm, cls)
}
}
impl PySliceableSequence for String {
fn do_slice(&self, range: Range<usize>) -> Self {
to_graphemes(self)
+20
View File
@@ -1259,6 +1259,26 @@ where
}
}
pub trait IntoPyClass {
const NAME: &'static str;
const DOC: Option<&'static str> = None;
fn _extend_class(ctx: &PyContext, class: &PyClassRef);
fn extend_class(ctx: &PyContext, class: &PyClassRef) {
Self::_extend_class(ctx, class);
if let Some(doc) = Self::DOC {
ctx.set_attr(class, "__doc__", ctx.new_str(doc.into()));
}
}
fn make_class(ctx: &PyContext) -> PyClassRef {
let py_class = ctx.new_class(Self::NAME, ctx.object());
Self::extend_class(ctx, &py_class);
py_class
}
}
#[cfg(test)]
mod tests {
use super::*;