Add slice type and use BigInts in slice payload.

This commit is contained in:
ben
2019-02-09 12:07:04 +13:00
parent f454bf36d1
commit 907dfb6770
11 changed files with 235 additions and 52 deletions

View File

@@ -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

View File

@@ -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));

View File

@@ -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<Option<i32>> = elements
let mut out: Vec<Option<BigInt>> = 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)

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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?)",

View File

@@ -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<BigInt> {
let result = self.start.clone() + self.step.clone() * index;
pub fn get<'a, T>(&'a self, index: T) -> Option<BigInt>
where
&'a BigInt: Mul<T, Output = BigInt>,
{
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
};

View File

@@ -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<usize> {
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 {

95
vm/src/obj/objslice.rs Normal file
View File

@@ -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<BigInt>) -> 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));
}

View File

@@ -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()))

View File

@@ -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<PyObjectRef>,
},
Slice {
start: Option<i32>,
stop: Option<i32>,
step: Option<i32>,
start: Option<BigInt>,
stop: Option<BigInt>,
step: Option<BigInt>,
},
Range {
range: objrange::RangeType,