diff --git a/tests/snippets/builtin_reversed.py b/tests/snippets/builtin_reversed.py index 2bbfcb98a..261b5c326 100644 --- a/tests/snippets/builtin_reversed.py +++ b/tests/snippets/builtin_reversed.py @@ -1 +1,4 @@ assert list(reversed(range(5))) == [4, 3, 2, 1, 0] + +l = [5,4,3,2,1] +assert list(reversed(l)) == [1,2,3,4,5] diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 9779a5054..6982e0493 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -2,12 +2,13 @@ //! //! Implements functions listed here: https://docs.python.org/3/library/builtins.html +use std::cell::Cell; use std::char; use std::io::{self, Write}; use std::str; use num_bigint::Sign; -use num_traits::{Signed, Zero}; +use num_traits::{Signed, ToPrimitive, Zero}; use crate::compile; use crate::obj::objbool; @@ -684,11 +685,20 @@ fn builtin_repr(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult fn builtin_reversed(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - // TODO: fallback to using __len__ and __getitem__, if object supports sequence protocol - let method = vm.get_method_or_type_error(obj.clone(), "__reversed__", || { - format!("argument to reversed() must be a sequence") - })?; - vm.invoke(method, PyFuncArgs::default()) + if let Some(reversed_method) = vm.get_method(obj.clone(), "__reversed__") { + vm.invoke(reversed_method?, PyFuncArgs::default()) + } else { + vm.get_method_or_type_error(obj.clone(), "__getitem__", || { + format!("argument to reversed() must be a sequence") + })?; + let len = vm.call_method(&obj.clone(), "__len__", PyFuncArgs::default())?; + let obj_iterator = objiter::PySequenceIterator { + position: Cell::new(objint::get_value(&len).to_isize().unwrap() - 1), + obj: obj.clone(), + reversed: true, + }; + Ok(obj_iterator.into_ref(vm).into_object()) + } } fn builtin_round(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 9aebc85f6..0fcd078c7 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -28,6 +28,7 @@ pub fn get_iter(vm: &VirtualMachine, iter_target: &PyObjectRef) -> PyResult { let obj_iterator = PySequenceIterator { position: Cell::new(0), obj: iter_target.clone(), + reversed: false, }; Ok(obj_iterator.into_ref(vm).into_object()) } @@ -80,8 +81,9 @@ pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef { #[pyclass] #[derive(Debug)] pub struct PySequenceIterator { - pub position: Cell, + pub position: Cell, pub obj: PyObjectRef, + pub reversed: bool, } impl PyValue for PySequenceIterator { @@ -94,17 +96,22 @@ impl PyValue for PySequenceIterator { impl PySequenceIterator { #[pymethod(name = "__next__")] fn next(&self, vm: &VirtualMachine) -> PyResult { - let number = vm.ctx.new_int(self.position.get()); - match vm.call_method(&self.obj, "__getitem__", vec![number]) { - Ok(val) => { - self.position.set(self.position.get() + 1); - Ok(val) + if self.position.get() >= 0 { + let step: isize = if self.reversed { -1 } else { 1 }; + let number = vm.ctx.new_int(self.position.get()); + match vm.call_method(&self.obj, "__getitem__", vec![number]) { + Ok(val) => { + self.position.set(self.position.get() + step); + Ok(val) + } + Err(ref e) if objtype::isinstance(&e, &vm.ctx.exceptions.index_error) => { + Err(new_stop_iteration(vm)) + } + // also catches stop_iteration => stop_iteration + Err(e) => Err(e), } - Err(ref e) if objtype::isinstance(&e, &vm.ctx.exceptions.index_error) => { - Err(new_stop_iteration(vm)) - } - // also catches stop_iteration => stop_iteration - Err(e) => Err(e), + } else { + Err(new_stop_iteration(vm)) } } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index d0bccd856..e1c2cd42c 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1149,6 +1149,7 @@ where objiter::PySequenceIterator { position: Cell::new(0), obj: obj.clone(), + reversed: false, } .into_ref(vm) .into_object(),