Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions tests/snippets/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,34 @@ def f(x):
return x
assert_raises(ValueError, lambda: lst.sort(key=f)) # "list modified during sort"
assert lst == [1, 2, 3, 4, 5]

# __delitem__
x = ['a', 'b', 'c']
del x[0]
assert x == ['b', 'c']

x = ['a', 'b', 'c']
del x[-1]
assert x == ['a', 'b']

x = y = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15]
del x[2:14:3]
assert x == y
assert x == [1, 2, 4, 5, 7, 8, 11, 12, 14, 15]
assert y == [1, 2, 4, 5, 7, 8, 11, 12, 14, 15]

x = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15]
del x[-5:]
assert x == [1, 2, 3, 4, 5, 6, 7, 8, 10]

x = list(range(12))
del x[10:2:-2]
assert x == [0,1,2,3,5,7,9,11]

def bad_del_1():
del ['a', 'b']['a']
assert_raises(TypeError, bad_del_1)

def bad_del_2():
del ['a', 'b'][2]
assert_raises(IndexError, bad_del_2)
176 changes: 174 additions & 2 deletions vm/src/obj/objlist.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::cell::{Cell, RefCell};
use std::fmt;

use num_traits::ToPrimitive;
use std::ops::Range;

use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive, Zero};

use crate::function::{OptionalArg, PyFuncArgs};
use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol};
Expand All @@ -12,8 +15,9 @@ use super::objint;
use super::objiter;
use super::objsequence::{
get_elements, get_elements_cell, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul,
PySliceableSequence,
PySliceableSequence, SequenceIndex,
};
use super::objslice::PySliceRef;
use super::objtype;
use crate::obj::objtype::PyClassRef;

Expand Down Expand Up @@ -44,6 +48,54 @@ impl PyValue for PyList {
}
}

impl PyList {
pub fn get_len(&self) -> usize {
self.elements.borrow().len()
}

pub fn get_pos(&self, p: i32) -> Option<usize> {
// convert a (potentially negative) positon into a real index
if p < 0 {
if -p as usize > self.get_len() {
None
} else {
Some(self.get_len() - ((-p) as usize))
}
} else if p as usize >= self.get_len() {
None
} else {
Some(p as usize)
}
}

pub 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() {
// slice past start bound, round to start
0
} else {
// slice past end bound, round to end
self.get_len()
}
}

pub fn get_slice_range(&self, start: &Option<BigInt>, stop: &Option<BigInt>) -> Range<usize> {
let start = start.as_ref().map(|x| self.get_slice_pos(x)).unwrap_or(0);
let stop = stop
.as_ref()
.map(|x| self.get_slice_pos(x))
.unwrap_or_else(|| self.get_len());

start..stop
}
}

pub type PyListRef = PyRef<PyList>;

impl PyListRef {
Expand Down Expand Up @@ -310,6 +362,125 @@ impl PyListRef {
Ok(vm.ctx.not_implemented())
}
}

fn delitem(self, subscript: SequenceIndex, vm: &VirtualMachine) -> PyResult {
match subscript {
SequenceIndex::Int(index) => self.delindex(index, vm),
SequenceIndex::Slice(slice) => self.delslice(slice, vm),
}
}

fn delindex(self, index: i32, vm: &VirtualMachine) -> PyResult {
if let Some(pos_index) = self.get_pos(index) {
self.elements.borrow_mut().remove(pos_index);
Ok(vm.get_none())
} else {
Err(vm.new_index_error("Index out of bounds!".to_string()))
}
}

fn delslice(self, slice: PySliceRef, vm: &VirtualMachine) -> PyResult {
let start = &slice.start;
let stop = &slice.stop;
let step = slice.step.clone().unwrap_or_else(BigInt::one);

if step.is_zero() {
Err(vm.new_value_error("slice step cannot be zero".to_string()))
} else if step.is_positive() {
let range = self.get_slice_range(&start, &stop);
if range.start < range.end {
#[allow(clippy::range_plus_one)]
match step.to_i32() {
Some(1) => {
self._del_slice(range);
Ok(vm.get_none())
}
Some(num) => {
self._del_stepped_slice(range, num as usize);
Ok(vm.get_none())
}
None => {
self._del_slice(range.start..range.start + 1);
Ok(vm.get_none())
}
}
} else {
// no del to do
Ok(vm.get_none())
}
} else {
// calculate the range for the reverse slice, first the bounds needs to be made
// exclusive around stop, the lower number
let start = start.as_ref().map(|x| x + 1);
let stop = stop.as_ref().map(|x| x + 1);
let range = self.get_slice_range(&stop, &start);
if range.start < range.end {
match (-step).to_i32() {
Some(1) => {
self._del_slice(range);
Ok(vm.get_none())
}
Some(num) => {
self._del_stepped_slice_reverse(range, num as usize);
Ok(vm.get_none())
}
None => {
self._del_slice(range.end - 1..range.end);
Ok(vm.get_none())
}
}
} else {
// no del to do
Ok(vm.get_none())
}
}
}

fn _del_slice(self, range: Range<usize>) {
self.elements.borrow_mut().drain(range);
}

fn _del_stepped_slice(self, range: Range<usize>, step: usize) {
// no easy way to delete stepped indexes so here is what we'll do
let mut deleted = 0;
let mut elements = self.elements.borrow_mut();
let mut indexes = range.clone().step_by(step).peekable();

for i in range.clone() {
// is this an index to delete?
if indexes.peek() == Some(&i) {
// record and move on
indexes.next();
deleted += 1;
} else {
// swap towards front
elements.swap(i - deleted, i);
}
}
// then drain (the values to delete should now be contiguous at the end of the range)
elements.drain((range.end - deleted)..range.end);
}

fn _del_stepped_slice_reverse(self, range: Range<usize>, step: usize) {
// no easy way to delete stepped indexes so here is what we'll do
let mut deleted = 0;
let mut elements = self.elements.borrow_mut();
let mut indexes = range.clone().rev().step_by(step).peekable();

for i in range.clone().rev() {
// is this an index to delete?
if indexes.peek() == Some(&i) {
// record and move on
indexes.next();
deleted += 1;
} else {
// swap towards back
elements.swap(i + deleted, i);
}
}
// then drain (the values to delete should now be contiguous at teh start of the range)
elements.drain(range.start..(range.start + deleted));
}
}

fn list_new(
Expand Down Expand Up @@ -458,6 +629,7 @@ pub fn init(context: &PyContext) {
"__iadd__" => context.new_rustfunc(PyListRef::iadd),
"__bool__" => context.new_rustfunc(PyListRef::bool),
"__contains__" => context.new_rustfunc(PyListRef::contains),
"__delitem__" => context.new_rustfunc(PyListRef::delitem),
"__eq__" => context.new_rustfunc(PyListRef::eq),
"__lt__" => context.new_rustfunc(PyListRef::lt),
"__gt__" => context.new_rustfunc(PyListRef::gt),
Expand Down
22 changes: 20 additions & 2 deletions vm/src/obj/objsequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ use std::ops::{Deref, DerefMut, Range};
use num_bigint::BigInt;
use num_traits::{One, Signed, ToPrimitive, Zero};

use crate::pyobject::{IdProtocol, PyObject, PyObjectRef, PyResult, TypeProtocol};
use crate::pyobject::{IdProtocol, PyObject, PyObjectRef, PyResult, TryFromObject, TypeProtocol};
use crate::vm::VirtualMachine;

use super::objbool;
use super::objint::PyInt;
use super::objlist::PyList;
use super::objslice::PySlice;
use super::objslice::{PySlice, PySliceRef};
use super::objtuple::PyTuple;

pub trait PySliceableSequence {
Expand Down Expand Up @@ -137,6 +137,24 @@ impl<T: Clone> PySliceableSequence for Vec<T> {
}
}

pub enum SequenceIndex {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we reuse RangeIndex? We'll need a "int-or-slice" typy for most __getitem__-like functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, by the time I completed the rework, I realized that, yes, it potentially could have just used RangeIndex I only need to convert to i32 once It came time to check bounds.

fn delindex(self, index: i32, vm: &VirtualMachine) -> PyResult {
        if let Some(pos_index) = self.get_pos(index) {
//...
}

could be

fn delindex(self, index: PyInt, vm: &VirtualMachine) -> PyResult {
        if let Some(pos_index) = self.get_pos(i32::try_from_object(vm, index.into_object())?) {
//...
}

however, I felt it would be best to leave it as is until directed to merge it. it seemed a little strange to be using something called RangeIndex.

Int(i32),
Slice(PySliceRef),
}

impl TryFromObject for SequenceIndex {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
match_class!(obj,
i @ PyInt => Ok(SequenceIndex::Int(i32::try_from_object(vm, i.into_object())?)),
s @ PySlice => Ok(SequenceIndex::Slice(s)),
obj => Err(vm.new_type_error(format!(
"sequence indices be integers or slices, not {}",
obj.class(),
)))
)
}
}

pub fn get_item(
vm: &VirtualMachine,
sequence: &PyObjectRef,
Expand Down