diff --git a/tests/snippets/builtin_slice.py b/tests/snippets/builtin_slice.py new file mode 100644 index 000000000..402a90133 --- /dev/null +++ b/tests/snippets/builtin_slice.py @@ -0,0 +1,45 @@ + +a = [] +assert a[:] == [] +assert a[:2**100] == [] +assert a[-2**100:] == [] +assert a[::2**100] == [] +assert a[10:20] == [] +assert a[-20:-10] == [] + +b = [1, 2] + +assert b[:] == [1, 2] +assert b[:2**100] == [1, 2] +assert b[-2**100:] == [1, 2] +assert b[2**100:] == [] +assert b[::2**100] == [1] +assert b[-10:1] == [1] + +slice_a = slice(5) +assert slice_a.start is None +assert slice_a.stop == 5 +assert slice_a.step is None + +slice_b = slice(1, 5) +assert slice_b.start == 1 +assert slice_b.stop == 5 +assert slice_b.step is None + +slice_c = slice(1, 5, 2) +assert slice_c.start == 1 +assert slice_c.stop == 5 +assert slice_c.step == 2 + + +class SubScript(object): + def __getitem__(self, item): + assert type(item) == slice + + def __setitem__(self, key, value): + assert type(key) == slice + + +ss = SubScript() +_ = ss[:] +ss[:1] = 1 diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index f8df09a62..cad71c42e 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -677,6 +677,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { ctx.set_attr(&py_mod, "repr", ctx.new_rustfunc(builtin_repr)); ctx.set_attr(&py_mod, "set", ctx.set_type()); ctx.set_attr(&py_mod, "setattr", ctx.new_rustfunc(builtin_setattr)); + ctx.set_attr(&py_mod, "slice", ctx.slice_type()); ctx.set_attr(&py_mod, "staticmethod", ctx.staticmethod_type()); ctx.set_attr(&py_mod, "str", ctx.str_type()); ctx.set_attr(&py_mod, "sum", ctx.new_rustfunc(builtin_sum)); diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 54fbf415b..b1b7952e2 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -20,7 +20,7 @@ use super::pyobject::{ PyResult, TypeProtocol, }; use super::vm::VirtualMachine; -use num_traits::ToPrimitive; +use num_bigint::BigInt; #[derive(Clone, Debug)] enum Block { @@ -262,22 +262,22 @@ impl Frame { assert!(*size == 2 || *size == 3); let elements = self.pop_multiple(*size); - let mut out: Vec> = elements + let mut out: Vec> = elements .into_iter() .map(|x| match x.borrow().payload { - PyObjectPayload::Integer { ref value } => Some(value.to_i32().unwrap()), + PyObjectPayload::Integer { ref value } => Some(value.clone()), PyObjectPayload::None => None, _ => panic!("Expect Int or None as BUILD_SLICE arguments, got {:?}", x), }) .collect(); - let start = out[0]; - let stop = out[1]; - let step = if out.len() == 3 { out[2] } else { None }; + let start = out[0].take(); + let stop = out[1].take(); + let step = if out.len() == 3 { out[2].take() } else { None }; let obj = PyObject::new( PyObjectPayload::Slice { start, stop, step }, - vm.ctx.type_type(), + vm.ctx.slice_type(), ); self.push_value(obj); Ok(None) diff --git a/vm/src/obj/mod.rs b/vm/src/obj/mod.rs index 8b93ee174..f08998fcb 100644 --- a/vm/src/obj/mod.rs +++ b/vm/src/obj/mod.rs @@ -22,6 +22,7 @@ pub mod objproperty; pub mod objrange; pub mod objsequence; pub mod objset; +pub mod objslice; pub mod objstr; pub mod objsuper; pub mod objtuple; diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 55b206511..40633e984 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -9,7 +9,6 @@ use super::super::vm::VirtualMachine; use super::objbool; // use super::objstr; use super::objtype; // Required for arg_check! to use isinstance -use num_bigint::BigInt; /* * This helper function is called at multiple places. First, it is called @@ -146,7 +145,7 @@ fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { } PyObjectPayload::Range { ref range } => { - if let Some(int) = range.get(BigInt::from(*position)) { + if let Some(int) = range.get(*position) { *position += 1; Ok(vm.ctx.new_int(int)) } else { diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index ed8e552bc..369ed9fc5 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -21,9 +21,12 @@ fn set_item( ) -> PyResult { if objtype::isinstance(&idx, &vm.ctx.int_type()) { let value = objint::get_value(&idx).to_i32().unwrap(); - let pos_index = l.get_pos(value); - l[pos_index] = obj; - Ok(vm.get_none()) + if let Some(pos_index) = l.get_pos(value) { + l[pos_index] = obj; + Ok(vm.get_none()) + } else { + Err(vm.new_index_error("list index out of range".to_string())) + } } else { panic!( "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 8d320084c..91b875ae3 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -7,6 +7,7 @@ use super::objtype; use num_bigint::{BigInt, Sign}; use num_integer::Integer; use num_traits::{One, Signed, ToPrimitive, Zero}; +use std::ops::Mul; #[derive(Debug, Clone)] pub struct RangeType { @@ -77,8 +78,11 @@ impl RangeType { } #[inline] - pub fn get(&self, index: BigInt) -> Option { - let result = self.start.clone() + self.step.clone() * index; + pub fn get<'a, T>(&'a self, index: T) -> Option + where + &'a BigInt: Mul, + { + let result = &self.start + &self.step * index; if (self.forward() && !self.is_empty() && result < self.end) || (!self.forward() && !self.is_empty() && result > self.end) @@ -199,15 +203,19 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { match subscript.borrow().payload { PyObjectPayload::Integer { ref value } => { - if let Some(int) = zrange.get(value.clone()) { + if let Some(int) = zrange.get(value) { Ok(vm.ctx.new_int(int)) } else { Err(vm.new_index_error("range object index out of range".to_string())) } } - PyObjectPayload::Slice { start, stop, step } => { + PyObjectPayload::Slice { + ref start, + ref stop, + ref step, + } => { let new_start = if let Some(int) = start { - if let Some(i) = zrange.get(int.into()) { + if let Some(i) = zrange.get(int) { i } else { zrange.start.clone() @@ -217,7 +225,7 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }; let new_end = if let Some(int) = stop { - if let Some(i) = zrange.get(int.into()) { + if let Some(i) = zrange.get(int) { i } else { zrange.end @@ -227,7 +235,7 @@ fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { }; let new_step = if let Some(int) = step { - (int as i64) * zrange.step + int * zrange.step } else { zrange.step }; diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index d2fbb2a96..bc4d46008 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -2,7 +2,8 @@ use super::super::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult, T use super::super::vm::VirtualMachine; use super::objbool; use super::objint; -use num_traits::ToPrimitive; +use num_bigint::BigInt; +use num_traits::{Signed, ToPrimitive}; use std::cell::{Ref, RefMut}; use std::marker::Sized; use std::ops::{Deref, DerefMut}; @@ -11,22 +12,35 @@ pub trait PySliceableSequence { fn do_slice(&self, start: usize, stop: usize) -> Self; fn do_stepped_slice(&self, start: usize, stop: usize, step: usize) -> Self; fn len(&self) -> usize; - fn get_pos(&self, p: i32) -> usize { + fn get_pos(&self, p: i32) -> Option { if p < 0 { if -p as usize > self.len() { - // return something that is out of bounds so `get_item` raises an IndexError - self.len() + 1 + None } else { - self.len() - ((-p) as usize) + Some(self.len() - ((-p) as usize)) } - } else if p as usize > self.len() { - // This is for the slicing case where the end element is greater than the length of the - // sequence - self.len() + } else if p as usize >= self.len() { + None } else { - p as usize + Some(p as usize) } } + + fn get_slice_pos(&self, slice_pos: &BigInt) -> usize { + if let Some(pos) = slice_pos.to_i32() { + if let Some(index) = self.get_pos(pos) { + // within bounds + return index; + } + } + + if slice_pos.is_negative() { + 0 + } else { + self.len() + } + } + fn get_slice_items(&self, slice: &PyObjectRef) -> Self where Self: Sized, @@ -34,22 +48,31 @@ pub trait PySliceableSequence { // TODO: we could potentially avoid this copy and use slice match &(slice.borrow()).payload { PyObjectPayload::Slice { start, stop, step } => { - let start = match *start { - Some(start) => self.get_pos(start), - None => 0, + let start = if let Some(start) = start { + self.get_slice_pos(start) + } else { + 0 }; - let stop = match *stop { - Some(stop) => self.get_pos(stop), - None => self.len() as usize, + let stop = if let Some(stop) = stop { + self.get_slice_pos(stop) + } else { + self.len() }; - match *step { - None | Some(1) => self.do_slice(start, stop), - Some(num) => { - if num < 0 { - unimplemented!("negative step indexing not yet supported") - }; - self.do_stepped_slice(start, stop, num as usize) + if let Some(step) = step { + match step.to_i32() { + Some(1) => self.do_slice(start, stop), + Some(num) => self.do_stepped_slice(start, stop, num as usize), + None => self.do_slice( + start, + if start == self.len() { + start + } else { + start + 1 + }, + ), } + } else { + self.do_slice(start, stop) } } payload => panic!("get_slice_items called with non-slice: {:?}", payload), @@ -78,8 +101,7 @@ pub fn get_item( match &(subscript.borrow()).payload { PyObjectPayload::Integer { value } => match value.to_i32() { Some(value) => { - let pos_index = elements.to_vec().get_pos(value); - if pos_index < elements.len() { + if let Some(pos_index) = elements.to_vec().get_pos(value) { let obj = elements[pos_index].clone(); Ok(obj) } else { diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs new file mode 100644 index 000000000..547e252ba --- /dev/null +++ b/vm/src/obj/objslice.rs @@ -0,0 +1,95 @@ +use super::super::pyobject::{ + PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol, +}; +use super::super::vm::VirtualMachine; +use super::objint; +use super::objtype; // Required for arg_check! to use isinstance +use num_bigint::BigInt; + +fn slice_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + no_kwargs!(vm, args); + let (cls, start, stop, step): ( + &PyObjectRef, + Option<&PyObjectRef>, + Option<&PyObjectRef>, + Option<&PyObjectRef>, + ) = match args.args.len() { + 0 | 1 => Err(vm.new_type_error("slice() must have at least one arguments.".to_owned())), + 2 => { + arg_check!( + vm, + args, + required = [ + (cls, Some(vm.ctx.type_type())), + (stop, Some(vm.ctx.int_type())) + ] + ); + Ok((cls, None, Some(stop), None)) + } + _ => { + arg_check!( + vm, + args, + required = [ + (cls, Some(vm.ctx.type_type())), + (start, Some(vm.ctx.int_type())), + (stop, Some(vm.ctx.int_type())) + ], + optional = [(step, Some(vm.ctx.int_type()))] + ); + Ok((cls, Some(start), Some(stop), step)) + } + }?; + Ok(PyObject::new( + PyObjectPayload::Slice { + start: start.map(|x| objint::get_value(x)), + stop: stop.map(|x| objint::get_value(x)), + step: step.map(|x| objint::get_value(x)), + }, + cls.clone(), + )) +} + +fn get_property_value(vm: &mut VirtualMachine, value: &Option) -> PyResult { + if let Some(value) = value { + Ok(vm.ctx.new_int(value.clone())) + } else { + Ok(vm.get_none()) + } +} + +fn slice_start(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); + if let PyObjectPayload::Slice { start, .. } = &slice.borrow().payload { + get_property_value(vm, start) + } else { + panic!("Slice has incorrect payload."); + } +} + +fn slice_stop(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); + if let PyObjectPayload::Slice { stop, .. } = &slice.borrow().payload { + get_property_value(vm, stop) + } else { + panic!("Slice has incorrect payload."); + } +} + +fn slice_step(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); + if let PyObjectPayload::Slice { step, .. } = &slice.borrow().payload { + get_property_value(vm, step) + } else { + panic!("Slice has incorrect payload."); + } +} + +pub fn init(context: &PyContext) { + let zip_type = &context.slice_type; + + context.set_attr(zip_type, "__new__", context.new_rustfunc(slice_new)); + context.set_attr(zip_type, "start", context.new_property(slice_start)); + context.set_attr(zip_type, "stop", context.new_property(slice_stop)); + context.set_attr(zip_type, "step", context.new_property(slice_step)); +} diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index fbdf58fc4..88561a899 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1039,11 +1039,11 @@ pub fn subscript(vm: &mut VirtualMachine, value: &str, b: PyObjectRef) -> PyResu match objint::get_value(&b).to_i32() { Some(pos) => { let graphemes = to_graphemes(value); - let idx = graphemes.get_pos(pos); - graphemes - .get(idx) - .map(|c| vm.new_str(c.to_string())) - .ok_or(vm.new_index_error("string index out of range".to_string())) + if let Some(idx) = graphemes.get_pos(pos) { + Ok(vm.new_str(graphemes[idx].to_string())) + } else { + Err(vm.new_index_error("string index out of range".to_string())) + } } None => { Err(vm.new_index_error("cannot fit 'int' into an index-sized integer".to_string())) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index d486bfaef..212757671 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -22,6 +22,7 @@ use super::obj::objobject; use super::obj::objproperty; use super::obj::objrange; use super::obj::objset; +use super::obj::objslice; use super::obj::objstr; use super::obj::objsuper; use super::obj::objtuple; @@ -136,6 +137,7 @@ pub struct PyContext { pub super_type: PyObjectRef, pub str_type: PyObjectRef, pub range_type: PyObjectRef, + pub slice_type: PyObjectRef, pub type_type: PyObjectRef, pub zip_type: PyObjectRef, pub function_type: PyObjectRef, @@ -216,6 +218,7 @@ impl PyContext { let memoryview_type = create_type("memoryview", &type_type, &object_type, &dict_type); let code_type = create_type("code", &type_type, &int_type, &dict_type); let range_type = create_type("range", &type_type, &object_type, &dict_type); + let slice_type = create_type("slice", &type_type, &object_type, &dict_type); let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type, &dict_type); let none = PyObject::new( @@ -260,6 +263,7 @@ impl PyContext { none, str_type, range_type, + slice_type, object: object_type, function_type, super_type, @@ -288,6 +292,7 @@ impl PyContext { objmemory::init(&context); objstr::init(&context); objrange::init(&context); + objslice::init(&context); objsuper::init(&context); objtuple::init(&context); objiter::init(&context); @@ -346,6 +351,10 @@ impl PyContext { self.range_type.clone() } + pub fn slice_type(&self) -> PyObjectRef { + self.slice_type.clone() + } + pub fn frozenset_type(&self) -> PyObjectRef { self.frozenset_type.clone() } @@ -920,9 +929,9 @@ pub enum PyObjectPayload { iterators: Vec, }, Slice { - start: Option, - stop: Option, - step: Option, + start: Option, + stop: Option, + step: Option, }, Range { range: objrange::RangeType,