Skip to content

Commit 64bd33e

Browse files
committed
Add built in range type (addresses RustPython#294)
1 parent 0f87d15 commit 64bd33e

5 files changed

Lines changed: 222 additions & 12 deletions

File tree

vm/src/builtins.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -655,15 +655,6 @@ pub fn builtin_print(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
655655
Ok(vm.get_none())
656656
}
657657

658-
fn builtin_range(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
659-
arg_check!(vm, args, required = [(range, Some(vm.ctx.int_type()))]);
660-
let value = objint::get_value(range);
661-
let range_elements: Vec<PyObjectRef> = (0..value.to_i32().unwrap())
662-
.map(|num| vm.context().new_int(num.to_bigint().unwrap()))
663-
.collect();
664-
Ok(vm.context().new_list(range_elements))
665-
}
666-
667658
fn builtin_repr(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
668659
arg_check!(vm, args, required = [(obj, None)]);
669660
vm.to_repr(obj)
@@ -780,7 +771,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef {
780771
ctx.set_attr(&py_mod, "pow", ctx.new_rustfunc(builtin_pow));
781772
ctx.set_attr(&py_mod, "print", ctx.new_rustfunc(builtin_print));
782773
ctx.set_attr(&py_mod, "property", ctx.property_type());
783-
ctx.set_attr(&py_mod, "range", ctx.new_rustfunc(builtin_range));
774+
ctx.set_attr(&py_mod, "range", ctx.range_type());
784775
ctx.set_attr(&py_mod, "repr", ctx.new_rustfunc(builtin_repr));
785776
ctx.set_attr(&py_mod, "set", ctx.set_type());
786777
ctx.set_attr(&py_mod, "setattr", ctx.new_rustfunc(builtin_setattr));

vm/src/obj/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mod objlist;
1616
pub mod objmemory;
1717
pub mod objobject;
1818
pub mod objproperty;
19+
pub mod objrange;
1920
pub mod objsequence;
2021
pub mod objset;
2122
pub mod objstr;

vm/src/obj/objiter.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use super::super::vm::VirtualMachine;
99
use super::objbool;
1010
// use super::objstr;
1111
use super::objtype; // Required for arg_check! to use isinstance
12+
use num_bigint::ToBigInt;
1213

1314
/*
1415
* This helper function is called at multiple places. First, it is called
@@ -101,10 +102,10 @@ fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
101102

102103
if let PyObjectPayload::Iterator {
103104
ref mut position,
104-
iterated_obj: ref iterated_obj_ref,
105+
iterated_obj: ref mut iterated_obj_ref,
105106
} = iter.borrow_mut().payload
106107
{
107-
let iterated_obj = &*iterated_obj_ref.borrow_mut();
108+
let iterated_obj = iterated_obj_ref.borrow_mut();
108109
match iterated_obj.payload {
109110
PyObjectPayload::Sequence { ref elements } => {
110111
if *position < elements.len() {
@@ -118,6 +119,18 @@ fn iter_next(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
118119
Err(stop_iteration)
119120
}
120121
}
122+
123+
PyObjectPayload::Range { ref range } => {
124+
if let Some(int) = range.get(*position as i64) {
125+
*position += 1;
126+
Ok(vm.ctx.new_int(int.to_bigint().unwrap()))
127+
} else {
128+
let stop_iteration_type = vm.ctx.exceptions.stop_iteration.clone();
129+
let stop_iteration =
130+
vm.new_exception(stop_iteration_type, "End of iterator".to_string());
131+
Err(stop_iteration)
132+
}
133+
}
121134
_ => {
122135
panic!("NOT IMPL");
123136
}

vm/src/obj/objrange.rs

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
use super::super::pyobject::{
2+
PyContext, PyFuncArgs, PyObject, PyObjectPayload, PyObjectRef, PyResult, TypeProtocol,
3+
};
4+
use super::super::vm::VirtualMachine;
5+
use super::objint;
6+
use super::objtype;
7+
use num_bigint::ToBigInt;
8+
use num_traits::ToPrimitive;
9+
10+
#[derive(Debug, Copy, Clone)]
11+
pub struct RangeType {
12+
// Unfortunately Rust's built in range type doesn't support things like indexing
13+
// or ranges where start > end so we need to roll our own.
14+
pub start: i64,
15+
pub end: i64,
16+
pub step: i64,
17+
}
18+
19+
impl RangeType {
20+
#[inline]
21+
pub fn len(&self) -> usize {
22+
((self.end - self.start) / self.step).abs() as usize
23+
}
24+
25+
#[inline]
26+
pub fn is_empty(&self) -> bool {
27+
(self.start <= self.end && self.step < 0) || (self.start >= self.end && self.step > 0)
28+
}
29+
30+
#[inline]
31+
pub fn forward(&self) -> bool {
32+
self.start < self.end
33+
}
34+
35+
#[inline]
36+
pub fn get(&self, index: i64) -> Option<i64> {
37+
let result = self.start + self.step * index;
38+
39+
if self.forward() && !self.is_empty() && result < self.end {
40+
Some(result)
41+
} else if !self.forward() && !self.is_empty() && result > self.end {
42+
Some(result)
43+
} else {
44+
None
45+
}
46+
}
47+
}
48+
49+
pub fn init(context: &PyContext) {
50+
let ref range_type = context.range_type;
51+
context.set_attr(&range_type, "__new__", context.new_rustfunc(range_new));
52+
context.set_attr(&range_type, "__iter__", context.new_rustfunc(range_iter));
53+
context.set_attr(&range_type, "__len__", context.new_rustfunc(range_len));
54+
context.set_attr(
55+
&range_type,
56+
"__getitem__",
57+
context.new_rustfunc(range_getitem),
58+
);
59+
}
60+
61+
fn range_new(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
62+
arg_check!(
63+
vm,
64+
args,
65+
required = [(cls, None), (first, Some(vm.ctx.int_type()))],
66+
optional = [
67+
(second, Some(vm.ctx.int_type())),
68+
(step, Some(vm.ctx.int_type()))
69+
]
70+
);
71+
72+
let start = if let Some(_) = second {
73+
objint::get_value(first).to_i64().unwrap()
74+
} else {
75+
0i64
76+
};
77+
78+
let end = if let Some(pyint) = second {
79+
objint::get_value(pyint).to_i64().unwrap()
80+
} else {
81+
objint::get_value(first).to_i64().unwrap()
82+
};
83+
84+
let step = if let Some(pyint) = step {
85+
objint::get_value(pyint).to_i64().unwrap()
86+
} else {
87+
1i64
88+
};
89+
90+
if step == 0 {
91+
Err(vm.new_value_error("range with 0 step size".to_string()))
92+
} else {
93+
Ok(PyObject::new(
94+
PyObjectPayload::Range {
95+
range: RangeType { start, end, step },
96+
},
97+
cls.clone(),
98+
))
99+
}
100+
}
101+
102+
fn range_iter(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
103+
arg_check!(vm, args, required = [(range, Some(vm.ctx.range_type()))]);
104+
105+
Ok(PyObject::new(
106+
PyObjectPayload::Iterator {
107+
position: 0,
108+
iterated_obj: range.clone(),
109+
},
110+
vm.ctx.iter_type(),
111+
))
112+
}
113+
114+
fn range_len(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
115+
arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]);
116+
117+
let len = match zelf.borrow().payload {
118+
PyObjectPayload::Range { ref range } => range.len(),
119+
_ => unreachable!(),
120+
};
121+
122+
Ok(vm.ctx.new_int(len.to_bigint().unwrap()))
123+
}
124+
125+
fn range_getitem(vm: &mut VirtualMachine, args: PyFuncArgs) -> PyResult {
126+
arg_check!(
127+
vm,
128+
args,
129+
required = [(zelf, Some(vm.ctx.range_type())), (subscript, None)]
130+
);
131+
let zrange = if let PyObjectPayload::Range { range } = zelf.borrow().payload {
132+
range.clone()
133+
} else {
134+
unreachable!()
135+
};
136+
137+
match subscript.borrow().payload {
138+
PyObjectPayload::Integer { ref value } => {
139+
if let Some(int) = zrange.get(value.to_i64().unwrap()) {
140+
Ok(PyObject::new(
141+
PyObjectPayload::Integer {
142+
value: int.to_bigint().unwrap(),
143+
},
144+
vm.ctx.int_type(),
145+
))
146+
} else {
147+
Err(vm.new_index_error("range object index out of range".to_string()))
148+
}
149+
}
150+
PyObjectPayload::Slice { start, stop, step } => {
151+
let new_start = if let Some(int) = start {
152+
if let Some(i) = zrange.get(int.into()) {
153+
i as i64
154+
} else {
155+
zrange.start
156+
}
157+
} else {
158+
zrange.start
159+
};
160+
161+
let new_end = if let Some(int) = stop {
162+
if let Some(i) = zrange.get(int.into()) {
163+
i as i64
164+
} else {
165+
zrange.end
166+
}
167+
} else {
168+
zrange.end
169+
};
170+
171+
let new_step = if let Some(int) = step {
172+
(int as i64) * zrange.step
173+
} else {
174+
zrange.step
175+
};
176+
177+
Ok(PyObject::new(
178+
PyObjectPayload::Range {
179+
range: RangeType {
180+
start: new_start,
181+
end: new_end,
182+
step: new_step,
183+
},
184+
},
185+
vm.ctx.range_type(),
186+
))
187+
}
188+
189+
_ => Err(vm.new_type_error("range indices must be integer or slice".to_string())),
190+
}
191+
}

vm/src/pyobject.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use super::obj::objlist;
1717
use super::obj::objmemory;
1818
use super::obj::objobject;
1919
use super::obj::objproperty;
20+
use super::obj::objrange;
2021
use super::obj::objset;
2122
use super::obj::objstr;
2223
use super::obj::objsuper;
@@ -122,6 +123,7 @@ pub struct PyContext {
122123
pub staticmethod_type: PyObjectRef,
123124
pub super_type: PyObjectRef,
124125
pub str_type: PyObjectRef,
126+
pub range_type: PyObjectRef,
125127
pub type_type: PyObjectRef,
126128
pub function_type: PyObjectRef,
127129
pub property_type: PyObjectRef,
@@ -201,6 +203,7 @@ impl PyContext {
201203
let bool_type = create_type("bool", &type_type, &int_type, &dict_type);
202204
let memoryview_type = create_type("memoryview", &type_type, &object_type, &dict_type);
203205
let code_type = create_type("code", &type_type, &int_type, &dict_type);
206+
let range_type = create_type("range", &type_type, &object_type, &dict_type);
204207
let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type, &dict_type);
205208

206209
let none = PyObject::new(
@@ -240,6 +243,7 @@ impl PyContext {
240243
dict_type: dict_type,
241244
none: none,
242245
str_type: str_type,
246+
range_type: range_type,
243247
object: object_type,
244248
function_type: function_type,
245249
super_type: super_type,
@@ -267,6 +271,7 @@ impl PyContext {
267271
objproperty::init(&context);
268272
objmemory::init(&context);
269273
objstr::init(&context);
274+
objrange::init(&context);
270275
objsuper::init(&context);
271276
objtuple::init(&context);
272277
objiter::init(&context);
@@ -317,6 +322,10 @@ impl PyContext {
317322
self.set_type.clone()
318323
}
319324

325+
pub fn range_type(&self) -> PyObjectRef {
326+
self.range_type.clone()
327+
}
328+
320329
pub fn frozenset_type(&self) -> PyObjectRef {
321330
self.frozenset_type.clone()
322331
}
@@ -882,6 +891,9 @@ pub enum PyObjectPayload {
882891
stop: Option<i32>,
883892
step: Option<i32>,
884893
},
894+
Range {
895+
range: objrange::RangeType,
896+
},
885897
MemoryView {
886898
obj: PyObjectRef,
887899
},
@@ -949,6 +961,7 @@ impl fmt::Debug for PyObjectPayload {
949961
stop: _,
950962
step: _,
951963
} => write!(f, "slice"),
964+
&PyObjectPayload::Range { range: _ } => write!(f, "range"),
952965
&PyObjectPayload::Code { ref code } => write!(f, "code: {:?}", code),
953966
&PyObjectPayload::Function { .. } => write!(f, "function"),
954967
&PyObjectPayload::Generator { .. } => write!(f, "generator"),
@@ -1038,6 +1051,7 @@ impl PyObject {
10381051
ref stop,
10391052
ref step,
10401053
} => format!("<slice '{:?}:{:?}:{:?}'>", start, stop, step),
1054+
PyObjectPayload::Range { ref range } => format!("<range '{:?}'>", range),
10411055
PyObjectPayload::Iterator {
10421056
ref position,
10431057
ref iterated_obj,

0 commit comments

Comments
 (0)